呂凱崴kaiweilu.tw

研究文件知識庫:附引用的檢索問答子系統

指導教授的實驗室需要一個研究文件知識庫:文件上傳一次,同時保存原始檔與 Markdown 文字版,可檢索,並由語言模型依庫內文件回答、附引用出處。本人自願參與,獨立完成其中附引用的檢索問答子系統,並以公開的繁體中文閱讀理解資料集 DRCD 評估檢索與問答。

期間
2026-08 起(08-19 受指派,08-26 至 09-10 開發與審查修改,09-23 完成評估;持續參與實驗室開發)
角色
子系統獨立完成(5 個套件、測試與文件),於例會報告、依審查意見修改,向指導教授展示
技術
TypeScript、React、Node.js、zod、zhtw-js、markitdown(Python 子行程)、vitest
規模
5 個套件、47 項自動化測試;DRCD 測試集 3,493 題檢索評估、抽樣 200 題端對端評估
資料流與元件:入庫、人在頁面問答、其他程式透過工具查詢三條路徑;兩條問答路徑寫進同一份問答紀錄
資料流與元件:入庫、人在頁面問答、其他程式透過工具查詢三條路徑;兩條問答路徑寫進同一份問答紀錄
本頁目錄
  1. 背景與需求
  2. 三個難點
  3. 儲存與轉換
  4. 入庫時索引
  5. 檢索:TF-IDF 加中日韓雙字組
  6. 附引用的問答
  7. 頁面介面
  8. 檔案存取的安全檢查
  9. 評估
  10. 測試與文件
  11. 例會報告與審查修改
  12. 本人負責範圍
  13. 時間線
評估結果:DRCD 測試集 3,493 題,標準段落進入送給模型的 8 段摘錄 98.1%(單字斷詞對照組 87.5%);抽樣 200 題端對端問答,回答包含標準答案 96.5%、引用到正確段落 98.0%。

背景與需求

實驗室原本沒有集中管理研究文件的知識庫。子系統以外掛方式整合進實驗室既有系統,不改動既有核心;需求方訂定的規格如下:

  • 多本知識庫並存,類似 NotebookLM;
  • 每份文件存兩份:原始檔供人預覽與下載,轉換後的 Markdown 供檢索與語言模型讀取;
  • Markdown 與 PDF 在頁面上直接預覽;
  • 提供問答、結構、讀原文三個工具介面,以及供其他程式寫入的程式化入口。

三個難點

  1. 中英混寫與簡繁漂移 研究筆記常中英混寫,檢索模組設計為無外部相依,不倚賴中文斷詞器;語言模型被要求用繁體時仍可能輸出簡體,以工具查詢時也可能用簡體寫出知識庫名稱。
  2. 工具存取的邊界 呼叫端要能問答、查結構、讀原文,但整份文件不直接回傳給呼叫端;負責作答的子 agent 不能再委派問題(否則遞迴),作答時也不應寫入知識庫。
  3. 回答與文件的對應 回答要附出處,並分開量化:檢索是否找到正確段落、回答是否正確、引用是否指向正確段落,以及斷詞設計本身的貢獻。

儲存與轉換

儲存格式知識庫與資源的紀錄分存兩張資料表,以 zod schema 驗證。檔案放在資料目錄下的 library/v1/<知識庫 id>/:原始檔為 original/<資源 id>__<安全檔名>,轉換結果為 markdown/<資源 id>.md,另有 index.json、ask-log.json 兩個側車檔。紀錄只存相對檔名,搬移根目錄不必改寫紀錄;安全檔名把路徑分隔字元與控制字元換成底線,超過 120 字元時截斷並保留副檔名。

轉換管線ingest() 是唯一的程式化寫入入口,頁面上傳、貼上文字與 library_ingest 工具共用。流程是先把原始檔寫入 original/、狀態設為 converting,再走轉換接縫:轉換器以 registerConverter() 註冊,依優先度由高到低嘗試,只依檔名與媒體類型決定是否接受。

  • markitdown(優先度 10):以子行程執行 python -X utf8 -m markitdown <檔案>,涵蓋 PDF、Office、EPUB、HTML 等 16 種副檔名;逾時 120 秒、輸出上限 64 MB,空輸出視同錯誤。
  • 內建文字轉換器(優先度 0,無外部相依):Markdown 與純文字原樣保留,CSV、JSON 包成程式碼區塊,HTML 去除 script 與 style 後保留標題、段落與清單。
  • 後援與狀態:轉換器拋出錯誤時交給下一個;成功時狀態落在 ready,每個轉換器都出錯或沒有轉換器接受時落在 error 並寫入原因,原始檔保留、仍可預覽與下載。ingest() 等轉換落定並重建索引後才回傳。

入庫時索引

每次內容變動(建立知識庫、入庫落定、刪除資源)時,知識庫服務整份重建該知識庫的 index.json,內容包括每份資源的 id、名稱、類別與狀態,前 12 個一至三級標題組成的大綱,開頭 280 字元的純文字摘要,以及依標題切好、附預先統計詞頻表的 chunk。structure() 與 search() 都只讀這一個檔再計分,查詢時不讀文件、不重新斷詞;單元測試在入庫後改寫磁碟上的 Markdown,確認結構與檢索仍由索引正確供應。

重建策略讀取時以 zod 驗證;檔案不存在、無法解析、不符 schema,或與持久紀錄不一致(知識庫標題、資源數,或任一資源的 id、名稱、類別、狀態不同)時,第一次讀取就自動重建並寫回;早於索引功能的知識庫、手動修改過的檔案與改名因此自我修復,一般情況仍只需一次讀檔。

採用側車檔索引可由原始資料重建,問答紀錄只會附加;兩者放在文件旁的 JSON 檔,不必新增資料表、處理資料表的版本相容,檔案缺失或過期時也能自我修復。

問答紀錄ask-log.json 保留最新 200 筆,每筆記錄提問來源(頁面或工具)、問題、回答、是否有依據、出處與時間;頁內問答與透過工具的問答寫進同一份紀錄。

檢索:TF-IDF 加中日韓雙字組

chunk 切法Markdown 在一至三級標題處切段,每個 chunk 記下最近的標題供引用顯示;超過 1,800 字元的段落在空行處再切,單一過長的段落保持完整,不在句中切斷。

斷詞轉小寫後取兩類詞:拉丁字母與數字組成的詞(底線視為分隔,gl_FragColor 成為 gl 與 fragcolor);連續漢字切成相鄰兩字的重疊雙字組,例如「入庫時索引」得到入庫、庫時、時索、索引,孤立的單一漢字保留為單字詞。不需斷詞器與詞典,中英混寫的查詢同時命中兩類詞。

計分Q 為查詢詞集合,N 為該知識庫的 chunk 數,df(t) 為含詞 t 的 chunk 數,tf(t) 為詞在 chunk 內的次數,L 為 chunk 的字元數:

score = Σ_{t∈Q} tf(t) × (1 + ln(N ÷ (1 + df(t)))) ÷ √(1 + L ÷ 100)

罕見詞權重較高,長度項避免長段落因字多佔優;分數為 0 者不列入,問答取前 8 段為摘錄。沒有關鍵詞命中的總覽式提問(例如「簡單介紹一下」)改以各文件的前兩個 chunk 為依據;知識庫沒有可讀內容時直接婉拒,不呼叫模型。

簡繁處理工具的知識庫參數先比對 id,再比對經 zhtw-js 正規化(去頭尾空白、轉繁體)的標題,以簡體寫出的名稱也能對到繁體標題;頁內問答的回答以 zhtw-js 確定性轉為繁體(臺灣用語)。

附引用的問答

頁內問答ask() 把前 8 段摘錄編號,附上文件名與標題後連同問題送給語言模型;系統 prompt 要求只根據摘錄作答、以行內引用標出處、摘錄沒有答案時直說。回答上限 2,048 token,期限 60 秒並與瀏覽器端的取消訊號合併;回傳的出處即送進模型的摘錄,依資源與標題去重、依相關度排序。

透過工具提問其他程式以 library_ask 提問時,工具啟動一個全新的檢索子 agent:

  1. 委派 prompt 帶入知識庫標題、id 與問題,指示先以 library_structure 看資源、大綱與摘要,再以 library_read 讀相關文件,作答時以與資源名稱完全相同的 [名稱] 標出處;
  2. 子 agent 只帶入委派 prompt,不繼承呼叫端的內容;讀到的全文留在子 agent 自己的對話中,呼叫端只收到回答與出處;
  3. 出處由回答中的 [名稱] 對照資源名稱回收,整筆問答以工具來源寫入紀錄;子 agent 未正常完成時回報為工具錯誤。

無法啟動子 agent 時改走頁內問答的直接路徑,同樣記為工具來源。出處採名稱比對回收,結果是確定性的,子 agent 不必輸出結構化 schema。

子 agent 的工具權限委派時以工具過濾移除 library_ask 與 library_ingest,知識庫工具只留唯讀的 library_structure 與 library_read:子 agent 對知識庫只能讀、不能寫入,也無法把問題再委派給自己,不會遞迴。子 agent 的角色設定另外明文禁止再委派,且固定套用,呼叫端只能在其後附加說明。

工具介面問答與結構以整本知識庫為單位,讀原文時再以結構清單中的資源 id 指定文件;library_read 回傳轉換後的 Markdown,上限 20,000 字元,library_ingest 與頁面上傳共用 ingest()。找不到知識庫時,錯誤訊息會列出現有知識庫讓呼叫端自行更正;回傳值都有不允許額外欄位的 JSON schema。

頁面介面

  • 三區版面:來源欄(上傳、貼上、拖放,列出類型、大小、日期或轉換狀態)、問答主區(持久討論串,透過工具的提問另加標示)與可收合的預覽面板。
  • 引用:頁內問答的出處取自送進模型的摘錄,每份文件一個可點的標籤,點選即在預覽面板開啟被引文件,網址同步更新。
  • 預覽:面板分 Markdown 與原始檔兩種模式;原始檔以 iframe 載入,PDF 預設開原始檔,文字與圖片也可直接預覽。
  • 深連結:頁面狀態與網址 hash 雙向同步;開啟頁面與 hashchange 時由網址還原,狀態改變時以 history.replaceState 改寫網址,不增加瀏覽紀錄。貼上連結即可開到指定知識庫與文件的預覽。

檔案存取的安全檢查

知識庫管理、問答與紀錄走 JSON 介面;上傳、行內預覽與附件下載另走資料路由,檢查如下:

  1. 每次請求重新驗證:啟用身分驗證時,每個資料路由請求都重新驗證,未通過回 401。
  2. 檔案路徑不取自用戶端:只從網址取資源 id,實際路徑由伺服器依紀錄組成(根目錄、知識庫 id、入庫時產生的 <UUID>__<安全檔名>);未知 id 回 400。
  3. 上傳上限預設 100 MB:先檢查宣告的 Content-Length,讀取串流時再累計實際位元組,超過即回 413 並中斷連線。
  4. 回應標頭:Content-Type 取自入庫時記錄的媒體類型,Content-Disposition 區分行內與附件並以 RFC 5987 格式編碼檔名,另加 nosniff 與 no-store。

評估

方法於執行前寫定,執行後只附加結果。受測對象為子系統最新版本建置後的程式,組合方式與實驗室部署相同,子系統採正式預設值,模型為實驗室主機上的語言模型。

資料集與語料DRCD v1.3 測試集:台達研究院釋出的通用領域繁體中文機器閱讀理解資料集,段落取自維基百科(CC BY-SA 3.0),共 378 篇文章、1,000 個段落、3,493 題,每題附標準答案與出處段落。每篇文章寫成一份 Markdown,每個段落放在自己的 ## <段落 id> 標題下;段落都短於 1,800 字元,每段恰好成為一個 chunk。378 份文件全部經 ingest() 放進同一本知識庫,檢索由入庫時索引供應。

實驗一:檢索(全部 3,493 題)每題呼叫 search(知識庫, 問題, 100),記錄標準段落的名次。對照組為單字 TF-IDF:chunk、拉丁詞處理與計分公式完全相同,只把漢字雙字組換成逐字單字,差距只來自斷詞設計。以匯出的計分函式對 index.json 重新計分,3,493 題名次與線上 search() 完全一致,確認量測的就是部署中的程式路徑。Hit@k 為標準段落排進前 k 名的題目比例(k = 8 即 ask() 送給模型的摘錄數);MRR@100 為名次倒數的平均,未進前 100 名記 0;比例附 Wilson 95% 信賴區間。

指標(3,493 題)本子系統(中日韓雙字組)對照組(單字)
標準段落排第一(Hit@1)76.8%(95% 信賴區間 75.4–78.2)53.7%
標準段落進入前 5 名(Hit@5)96.3%82.5%
標準段落進入送給模型的 8 段(Hit@8)98.1%(95% 信賴區間 97.5–98.5)87.5%
標準文章進入前 8 段98.7%90.6%
MRR@1000.8530.662

實驗二:端對端問答(抽樣 200 題)以 mulberry32 亂數產生器、種子 20260923 洗牌後取前 200 題,逐題依序呼叫 ask(),即頁內問答的完整路徑(檢索、模型作答、轉繁體)。答案命中指任一標準答案出現在回答中,比對前兩邊都經 NFKC 正規化、轉繁、轉小寫,並去除空白、標點與引用標記,以全部 200 題為分母;引用到正確段落指編號引用 [k] 對應的第 k 段摘錄就是標準段落,以取得回答的 198 題為分母;另依標準段落是否在摘錄內分組,區分錯誤出在檢索或生成。

指標結果
回答包含標準答案96.5%(193/200,95% 信賴區間 93.0–98.3)
標準段落進入 8 段摘錄99.5%(197/198)
標準段落在摘錄中時的答案命中98.0%(193/197)
回答附有引用100%(198/198)
引用到正確段落98.0%(194/198)
回答延遲中位數9.4 秒

評估報告附資料集雜湊、逐題名次與回答紀錄,以及可重跑的程式。

測試與文件

47 項自動化測試於 2026-09-23 重跑,全數通過。核心服務與工具的測試組起真實的儲存層(暫存目錄中的 JSON 後端),不以 mock 取代;工具測試經真實的工具執行路徑驅動子 agent,子 agent 由腳本化的替身扮演;介面測試以 jsdom 與 Testing Library 執行。

範圍項數涵蓋
核心服務:切段、斷詞、計分11標題切段、超長段落再切、雙字組斷詞、罕見詞排序、大綱
核心服務:轉換8格式判斷,Markdown、CSV、HTML 轉換,安全檔名
核心服務:入庫、索引與問答13雙份保存、轉換出錯時保留原始檔、空知識庫婉拒、總覽式提問、索引寫入與重建、紀錄上限
工具介面6委派 prompt 與工具過濾、紀錄寫入、子 agent 異常結束、兩條退回路徑、簡體標題解析
頁面介面9hash 位址雙向轉換、頁首與討論串、Markdown 與 PDF 預覽、引用標籤導回預覽

文件:子系統文件與 6 份套件 README 皆有三語版本,以語言對應紀錄檔追蹤一致性,子系統文件含由原始碼產生的 API 章節;2 份設計決策紀錄記載問題、決策、替代方案、影響與驗證。

例會報告與審查修改

第一版在第三次例會報告,依回饋調整頁面版面與問答輸入框,並在實測以工具查詢後加入簡繁不敏感的標題比對。第四次例會由需求方審查,確認儲存形式與入口,會後提出四項修改要求:

審查要求完成方式
介面依既有設計語言調整移除頁內重複的知識庫欄,側欄成為唯一切換入口;改用既有的對話框與元件;三區版面;引用改為可點的標籤
Markdown 與 PDF 分開預覽預覽面板提供 Markdown 與原始檔兩種模式
入庫時生成結構與索引每次內容變動重建 index.json,缺失或過期時自動重建
問答、結構、讀原文三個存取介面問答委派檢索子 agent,並排除問答與寫入工具;結構直讀索引;讀原文直讀 Markdown

同一輪另完成問答紀錄的保存與合流、深連結、三語文件與設計決策紀錄,完成後逐項回報。

本人負責範圍

  • 附引用的檢索問答子系統由本人獨立完成:5 個套件,涵蓋儲存與轉換、入庫時索引、檢索、問答與子 agent 委派、檔案存取與頁面介面;47 項自動化測試;三語文件與 2 份設計決策紀錄。
  • 自願參與;於第三、第四次例會報告並回應審查提問,完成四項審查要求並逐項回報。
  • 依展示腳本向指導教授展示操作流程。
  • 以 DRCD 對實際部署的組合做評估,並回報結果。
  • 產品方向與驗收條件來自需求規格與審查意見;實作與評估把 AI 程式助理納入流程,由本人審閱、測試與提交。

時間線

日期事件
2026-08-19知識庫需求建立並指派給本人
2026-08-26提交第一版(5 個套件、26 項單元測試);第三次例會報告
2026-08-30第四次例會審查;提出四項修改要求
2026-09-09四項修改完成並逐項回報
2026-09-10調整子系統的組合設定;完成展示腳本
2026-09向指導教授展示
2026-09-23DRCD 評估完成並回報;47 項測試重跑全數通過