我開始接手處理銀行內部的 RAG 系統時,第一個問題不是 Embedding 模型不夠新,而是沒有人能回答「現在到底準不準」。embedding、chunk 大小與 top-k 多半沿用預設值,調整後也沒有固定題目可以比較。
在這種情況下直接換模型,很容易只得到主觀印象。某幾題變好,不代表整體檢索改善;某位使用者覺得答案讀起來比較順,也不代表正確文件被找回來的機率真的提高。
我的調校順序後來固定成:先建立評測集與基準,再一次調整一個變數。以下是幾個在中文金融文件上影響較大的項目,以及實作時需要保留的限制。
先建立一組能重複執行的評測題
第一版評測集不必很大,但題目要來自真實工作。可以先整理 50 到 200 題常見查詢,為每題標記一份或多份相關文件,再用固定版本的知識庫重複執行。
檢索階段至少可以觀察兩個指標:
- Recall@k:前 k 筆結果中,是否包含應該找到的文件。
- MRR:第一份相關文件出現在多前面。
Recall@k 適合檢查「有沒有找回來」,MRR 則能看出正確文件是否總是排在後面。若系統還會生成答案,應另加答案正確性、引用品質與拒答能力等評測,不能只用檢索指標代表整個 RAG 系統。
第一版評測腳本通常不必做得太複雜。重要的是保存題目版本、文件版本、模型與參數,讓每次調整都有可比較的紀錄。對金融環境來說,這些紀錄也能說明為什麼更換模型或設定,而不是只留下「感覺比較好」。
先確認 embedding 的使用方式正確
模型選對了,不代表呼叫方式一定正確。以 multilingual-e5-large 為例,官方 model card 說明,檢索任務的查詢與文件應分別加上 query: 和 passage: 前綴;長文本也會在 512 tokens 截斷。若管線漏掉前綴或忽略長度限制,效能可能下降。
這類問題很容易被誤判成「模型不適合中文」。在更換模型前,應先核對以下項目:
- 查詢與文件是否使用模型要求的前綴或 instruction。
- pooling 與 vector normalization 是否符合 model card。
- 實際輸入是否超過模型長度限制。
- 查詢端與建索引端是否使用同一模型與處理流程。
我們後來也評估 BGE-M3。它支援多語言、最長 8,192 tokens,並提供 dense、sparse 與 multi-vector 等模式。但「支援更長輸入」不代表就適合把整份文件都塞進單一向量,實際 chunk 長度仍要用自己的資料與查詢評測。
另外,換 embedding 模型後,舊向量不能沿用。文件必須用新模型重新切分、重新編碼並建立索引,否則查詢向量與文件向量不在同一個表示空間,分數沒有可比性。
chunking 要以 token 與文件結構為準
中文的字數不能直接等同 token 數。實際比例會隨 tokenizer、文字內容、標點與英數混排而變化。如果系統只用字元數切塊,可能在送進 embedding 模型時被截斷,卻沒有留下明確錯誤。
比較可靠的做法,是用目標模型的 tokenizer 計算長度,並保留標題、章節與表格等結構。我的一組起始參數曾使用約 600 tokens、重疊 80 tokens,但這只是特定文件集的起點,不是通用答案。
chunk 太小,段落脈絡容易被切散;chunk 太大,單一向量會混入多個主題,也可能排擠真正相關的片段。評測時可以從幾組範圍開始,例如 300、600、900 tokens,固定其他條件後比較 Recall@k、MRR 與延遲。
對規章與作業文件,我也會避免把章節標題和正文分開。即使正文沒有重複標題,也可以在建立索引時把文件名稱與階層標題補到 chunk metadata 或檢索文字中,幫助模型理解片段位置。
reranker 通常比盲目增加 top-k 更有效
向量檢索適合快速找出候選文件,但對語意相近、措辭細微不同的條文,前幾名排序未必理想。這時可以在第一階段取回一批候選,例如 15 筆,再交給 cross-encoder reranker 重排,最後留下 5 筆給生成模型。
我使用過 bge-reranker-v2-m3,在當時的資料集上,精確度提升比單純提高 top-k 明顯。不過候選數、保留數、GPU 記憶體與延遲都和硬體、文件長度及併發量有關,不能把單一部署的數字當成固定配置。
要特別注意,reranker 無法補救第一階段完全沒有召回的文件。如果 Recall@15 已經不足,應先檢查 embedding、chunk、查詢改寫或混合檢索,再調整重排模型。
中文 BM25 需要合適的斷詞
BM25 對法規編號、產品名稱、專有名詞與精確詞彙很有價值,但很多預設實作以空白分詞。中文句子沒有天然空白,若直接套用,整段文字可能被當成一個 token,結果自然不理想。
是否加入 BM25,仍應由評測決定。若向量檢索對精確代碼、條號或罕見名詞的表現不足,可以加入中文斷詞或字詞 n-gram,再和 dense retrieval 做分數融合。若現有查詢多為語意型問題,混合檢索的額外複雜度未必有同等收益。
因此我不會把 BM25 當成 RAG 的必備元件,而是把它當成一項針對詞彙匹配缺口的工具。
OCR 品質會決定後續檢索的上限
掃描 PDF、表格與雙欄文件如果在匯入時就解析錯誤,後面的 embedding 和 reranker 都無法恢復不存在的文字。
Docling 等文件處理工具可以整合 OCR、版面分析與表格結構,但仍需要抽樣檢查。金融文件特別容易受小字、印章、跨頁表格與頁首頁尾干擾。除了整體字元正確率,我會另外檢查標題階層、條號、日期、金額與表格欄位是否被保留。
對重要文件,可以在匯入流程加入品質門檻:頁面文字量異常、OCR 信心過低或表格解析失敗時,先標記人工檢查,不直接進入正式索引。
頁碼不要拿去 embedding,要留在 metadata
即使 OCR 文字抽取得夠完整,RAG 做 citation 時,頁碼與定位資訊通常還是比檔名更難處理。embedding(chunk.text) 產生的是 vector,不會自動帶著原文來自哪個檔案、哪一頁或哪個座標。問題不是 vector database 存不下頁碼,而是頁碼和內文屬於不同種類的資訊:內文適合做 semantic search,頁碼則是 source location,應該用 metadata 保存與查詢。
如果只是把「第 37 頁」接在 chunk 前面一起做 embedding,也不會得到可靠的按頁定位結果。數字本身幾乎沒有足夠的語意,而且不同文件都可能有第 37 頁。當使用者明確要求「找第 37 頁」時,這類需求至少包含明確的結構化條件,不應只期待 vector similarity 自己猜中。
實務上還有幾個容易混在一起的頁碼:
- PDF page index:頁面在 PDF page objects 中的順序,不一定等於 viewer 顯示的頁碼。
- printed page label:文件頁尾實際印出的 1、2、3,或 i、ii、iii。
- chunk page span:一個 chunk 實際跨到哪些頁面。
封面、目錄、附件與空白頁都可能讓 PDF page index 和 printed page label 不一致。文件改版後,只要前面多插一頁,後面的 page index 也會全部位移。因此,只存一個 page_number 往往不夠,還要知道它指的是哪一套頁碼,以及對應哪一個文件版本。
printed page label 本身也不一定容易取得。有些 PDF 有正式的 page label,有些只是在頁尾放一段文字,掃描件甚至只剩影像。parser 若把 header 和 footer 當成雜訊移除,頁面上的「37」可能在進入 chunk 之前就消失。若 printed page label 是 citation 的必要欄位,ingestion pipeline 必須另外抽取並驗證,不能假設每種 PDF 都會自動提供。
chunking 會再增加一層問題。為了保留完整段落或表格,一個 chunk 可能跨兩頁;token-aware chunker 也可能合併同一個 heading 下的相鄰內容。若匯入流程先把 PDF flatten 成 Markdown,再從純文字重新 chunk,原本的 page anchor、document item reference 與 bounding box 也可能在這一步遺失。
我的做法是不把頁碼當成 embedding text 的一部分,而是替每個 chunk 保留一組 provenance metadata:
{ "document_id": "credit-policy", "document_version": "2026-08-20", "source_hash": "...", "chunk_id": "credit-policy-0042", "doc_item_refs": ["#/texts/128", "#/tables/7"], "page_anchors": [ { "pdf_page_index": 40, "printed_page_label": "36", "bbox_xyxy_pt": [90, 280, 506, 306], "bbox_origin": "top-left" }, { "pdf_page_index": 41, "printed_page_label": "37", "bbox_xyxy_pt": [88, 70, 510, 210], "bbox_origin": "top-left" } ]}
欄位不一定要完全相同,但至少要能回答三件事:這段內容來自哪一版文件、跨到哪些頁面,以及能不能回到原始位置。pdf_page_index 採 0-based 或 1-based 必須在 schema 中明確定義;bbox 也要定義座標系統、單位、原點,以及同一頁有多個區塊時的資料結構。若 UI 需要在 PDF 上標示引用內容,只有頁碼還不夠,還要保留 bounding box 或相等精度的 source anchor。
查詢流程也要分流。一般語意問題走 vector search,取回 chunk 後再由 metadata 組出 citation;遇到「第幾頁」「第幾章」或特定文件版本,則先解析成 filter,以 document_id、document_version 和 page fields 做精確篩選。Qdrant 這類 vector database 可以把這些欄位放在 payload,和 vector query 一起使用,但前提是 ingestion 時沒有把 provenance 丟掉。
最後顯示 citation 時,我會在兩套頁碼不一致時直接寫清楚,例如「文件標示第 37 頁(PDF 第 41 頁)」。這比只顯示一個無法對回原始文件的數字更容易驗證,也能避免文件改版後引用悄悄指到別頁。
跨語言與繁簡轉換要保持一致
多語 embedding 可以支援跨語言查詢,但不同模型和領域的效果差異很大,仍需放進評測集驗證。尤其是產品名稱、法規術語與台灣金融用語,不一定能由一般多語訓練資料完整處理。
若知識庫同時包含繁體與簡體中文,可以在查詢與建索引兩端採相同的正規化策略,例如使用 OpenCC 產生額外的檢索欄位。不過繁簡轉換可能出現一對多或語境差異,不應覆寫原文。較安全的做法是保留原始內容,將正規化版本只用於搜尋或比對,最後仍引用原文。
小型文件不一定需要 RAG,但 long context 也不是保證
如果知識範圍只有一份短文件,全文能穩定放進 context,直接提供全文可能比建立 retrieval pipeline 簡單。這樣可以移除「相關片段沒有被召回」這一類錯誤。
但 long context 不代表模型一定會正確使用每一段內容,也不保證答案百分之百正確。仍要測試 lost-in-the-middle、引用品質、拒答與成本。是否使用 RAG,應比較整體任務品質,而不是只看模型標示的最大 context window。
一次只改一個變數
RAG pipeline 包含文件 parsing、chunking、embedding、indexing、query rewriting、hybrid search、reranker 與生成模型。若一次同時更換三個元件,即使結果變好,也很難知道改善來自哪裡;結果變差時,更難回復。
我採用的順序通常是:
- 固定一組真實評測題與文件版本。
- 確認現有 embedding 的前綴、長度與 normalization 沒有用錯。
- 比較 embedding 模型,必要時完整重建索引。
- 調整 chunk 大小、重疊與文件結構。
- 加入 reranker,觀察品質、延遲與資源消耗。
- 針對詞彙型缺口評估中文 BM25。
- 確認 PDF page index、printed page label、chunk page span,以及回到原始位置所需的 source anchor(例如 bbox 或 document item reference)都有保存在 metadata。
- 把 OCR 與跨語言問題納入匯入品質檢查。
每一步都保留設定、指標與回復方式。這樣做不會讓 RAG 一夜之間變完美,但能讓每次調整都有證據,也比較適合需要稽核與長期維運的金融環境。
如果你正準備更換 embedding 或 reranker,先固定 50 題真實查詢並跑出目前基準。即使暫時不換任何元件,這組結果也會讓下一次調整更容易判斷。
常見問題
第一版 RAG 評測集需要多少題?
可以先從 50 到 200 題真實問題開始,確保涵蓋主要文件類型、常見問法與容易混淆的情境。比題數更重要的是標註品質與版本控管,之後再依錯誤紀錄持續擴充。
更換 embedding 模型一定要重建索引嗎?
是。不同模型產生的向量空間通常不相容,文件與查詢必須使用同一模型及相同前處理流程重新編碼。
OpenCC 可以解決所有繁簡中文檢索問題嗎?
不行。它可以降低字形差異,但專有名詞、語境與地區用語仍可能不同。應保留原文、雙邊使用一致策略,並以實際查詢評測效果。
資料來源與版本說明
- intfloat:multilingual-e5-large model card
- BAAI:BGE-M3 model card
- Docling:OCR 安裝與設定
- Docling:Chunking 與 metadata
- Docling Graph:Data Grounding 與 Provenance
- Qdrant:Payload 與 metadata filtering
關於作者 KJ Huang
KJ Huang(KJH)是來自台灣的軟體工程師與技術主管,擁有超過八年橫跨 AI、區塊鏈、遊戲化、資安與金融的開發及管理經驗,目前投入企業級自架 LLM 平台、RAG 檢索與 AI 治理的實務工作。更多內容請見 kjhuang.com。
本文屬於《銀行裡的 AI 工程》系列。
系列文章:銀行為什麼自架 LLM?、RAG 為什麼不適合計數。
English version: RAG Optimization: Measure First.

Response
[…] 系列文章:RAG 優化實戰、RAG 為什麼不適合計數。 […]