向量資料庫、GraphRAG、檔案搜尋 Agent,一個比一個新。結果一篇剛上 arXiv 的論文,把 51 萬份企業文件丟進同一場測試,撐到最後的卻是 1990 年代就出現的 BM25。
看到標題先別急著把向量資料庫砍掉。
這篇論文測的是一套虛構企業語料,reader 固定,干擾文件又有大量「主題很像、事實卻錯」的內容。BM25 約在 1,000 萬 corpus tokens 附近反超檔案搜尋 Agent,到了 6.01 億 tokens 仍保持領先。這個位置是相鄰量測層之間的概略標記,沒有精確到能當部署門檻。
我帶回實務的結論是:先排好全域候選,再讓 Agent 推理。
這次至少把比較條件鎖住了
多數 RAG 比較很難讀。BM25 用一套資料,GraphRAG 用另一套;reader、chunk size 和問題也不同,最後把分數排成一張表,卻說不清差距到底從哪裡來。
《BM25 Wins at Scale》做法比較乾淨。研究團隊用同一套虛構企業語料,建出 28 層互相包含的 corpus:
- 1,144 到 511,959 份文件
- 1.7M 到 600.8M corpus tokens
- 500 個問題,含基本查詢、完整性、資訊衝突與找不到答案
- 相關文件、錯誤版本與誘餌從最小層就固定存在
- BM25、DenseRAG 與 HippoRAG 2 共用 1,200-token chunk、100-token overlap
- 所有方法都用 Qwen3.6-27B 當 reader;適用的 retrieval pipeline 取 top-5 chunks
語料來自 wiki、email、ticket、會議紀錄、CRM、code review 等九種來源,另外混入 7.7% 放錯位置、近似重複或含舊資訊的文件。這種資料不像乾淨的 FAQ,比較接近公司知識庫真的會長成的樣子。
它畢竟是 synthetic benchmark。最難的 traps 有一部分來自 BM25 top-10,以及 BM25 top-200 候選經 dense reranking 後挑出的錯誤版本,資料建構本身含 lexical proposal path。作者另外做了直接 dense retrieval、問題改寫與 top-10 control;在論文量測的 control 裡,主要排序沒有翻轉。這些補強仍取代不了真實流量測試。
研究團隊比較七條 pipeline:
| 類型 | 方法 |
|---|---|
| 關鍵字檢索 | BM25 |
| 向量檢索 | DenseRAG |
| 圖式檢索 | HippoRAG 2、LinearRAG、MS-GraphRAG、LightRAG |
| Agent 搜尋 | File-System Agent |
這裡的 File-System Agent 很像 coding agent 查 codebase:它能列目錄、搜尋文字、讀檔,再根據前一次結果決定下一步。每題最多 80 次 LLM 呼叫。
「很像」不等於一般 coding agent。它的 list_dir 最多回 200 個名稱,grep 只能做 fixed-string search 且最多回 30 條路徑,read_doc 只讀前 8,000 字元。它沒有 regex、glob、語言索引或任意 shell。後面的結果只能外推到這種受限工具組。
小資料時 Agent 領先,資料一大就反過來
最小的 1,144 份文件裡,File-System Agent 拿到 77.4 分,BM25 是 74.7。兩者 95% 信賴區間重疊,還不能說 Agent 穩定勝出,但點估計確實較高。
每個 method/question/tier 只執行一次。95% 信賴區間來自對題目做 10,000 次 bootstrap,量的是問題樣本變動,沒有包含 Agent 多跑幾次的執行變異。
語料增加到約 1,000 萬 tokens 後,兩條曲線交叉。完整 511,959 份文件的分數變成:
| 方法 | Combined score |
|---|---|
| BM25 | 50.5 |
| File-System Agent | 30.7 |
| DenseRAG | 29.9 |
BM25 的 50.5 看起來也不高。這不是一套「誰都做得很好,BM25 稍微贏」的 benchmark;資料規模放大後,三種方法都掉分,只是 Agent 和 DenseRAG 掉得更快。
這張主實驗表涵蓋全部 500 題。論文 Table 1 同列的 5.8K、226K 與 4.9K query tokens 是最小 1,144 份文件時的描述值,不能拿來當完整語料成本。完整語料的 token 數出現在另一組 150 題 control,兩組分數也不能直接相減。
GraphRAG 的問題更早發生在建索引。HippoRAG 2 與 LinearRAG 做到 131,876 份文件就停了,MS-GraphRAG 停在 8,750,LightRAG 只完成 2,254。論文依實測速度外推,LightRAG 跑完整語料可能需要 102B generative tokens 與約四個單機年。
這些是外推值,不是實際等了四年。平行化也能縮短日曆時間。但它把常被藏起來的成本攤開了:有些 GraphRAG 在 demo corpus 很漂亮,搬到十萬份文件時,問題可能不是答得準不準,而是索引根本蓋不完。
BM25 贏在先看到全域,不只贏在關鍵字
File-System Agent 每次只能從局部線索往外走。文件少時,它可以讀幾份、換個關鍵字再查,靠推理修正方向。文件到了 51 萬份,目錄分支和近似結果一起增加,逐步探索越來越難碰到正確候選。
論文裡最關鍵的 control 是 Agent+BM25。
研究團隊另取固定 150 題,在獨立 scoring session 裡保留 Agent model、harness、80-call budget 與 judge,把 raw tree tools 和對應操作指令換成 BM25 search tool,再讓 Agent 讀候選 chunk。這組數字不能和前面的 500 題主實驗直接比較:
| 方法 | Combined score | 文件召回率 | 每題 LLM calls | 每題 tokens |
|---|---|---|---|---|
| 原始檔案 Agent | 36.9 | 36.8 | 36.12 | 895K |
| 原生 BM25 | 54.8 | 65.6 | 1.00 | — |
| Agent+BM25 | 69.4 | 72.4 | 5.79 | 101K |
原生 BM25 的 score 與 recall 是這 150 題共同 rejudge 的結果;論文只提供 5.8K 的完整 500 題描述性 token 平均,我沒有硬塞進表內。兩個 Agent 變體則來自同一組 control:換掉候選發現方式後,分數多了 32.5,token 剩約九分之一。模型與 budget 沒變,差距主要來自搜尋介面。
這也解釋 DenseRAG 為什麼沒有自然勝出。企業問題常含專案代號、人名、日期、錯誤碼與產品名稱;干擾文件又刻意做到「語意相近、版本錯誤」。向量很容易把同主題的舊決策排進來,BM25 對精確詞彙反而有優勢。
但別把這段讀成「向量搜尋沒用」。論文只用 Qwen3-Embedding-0.6B 代表 dense retrieval,固定 top-5,也沒測 BM25 加向量的 hybrid。換更強 embedding、reranker、query expansion 或不同 chunk strategy,排序可能改變。
先跑一個 BM25 baseline
如果你還沒建立檢索基準,先做一個便宜版本。下面是整理過、方便重現的最小範例,中文斷詞用 jieba,排序用 rank-bm25:
1 | python -m pip install jieba rank-bm25 |
1 | import jieba |
這段適合驗證流程,不適合直接扛 51 萬份文件。rank-bm25 專案自己也建議,大規模 production 換成更高效能的檢索套件;實際系統也可以直接用搜尋引擎。Elasticsearch 的文字欄位預設就是 BM25 similarity,不必先調 k1、b 才能開始。
論文說 BM25 build cost 是零,指的是零 model tokens。CPU、儲存空間、斷詞與倒排索引建置一樣要付,別把 token accounting 讀成零成本。
評估時別只看平均分
你至少要準備一小組人工標記的 query 與相關文件。下面這個標準函式不依賴任何套件,可以先算每題的 Recall@5:
1 | def recall_at_k( |
relevant_ids 為空時,Recall@K 沒有定義,所以範例直接報錯。對「資料不存在」的問題要另算拒答率、false-positive rate 或 answerability accuracy,不要塞一份假的 relevant document 讓公式能跑。
不要把所有 query 混成一個漂亮平均值。至少分開看:
- 精確識別碼、錯誤碼與專案名稱
- 自然語言改寫
- 多文件彙整
- 新舊版本衝突
- 資料不存在,系統應該拒答
這篇論文也出現同樣分化。在 42,587 份文件時,File-System Agent 在 intra-document、project-related、completeness 與 conflicting-information 四類領先;BM25 在另外五類最佳或打平,miscellaneous 則由 graph system 領先。你需要的是自己流量的分布,不是抄論文的總分。
向量搜尋該放在哪裡
BM25 抓精確字詞,向量搜尋補語意改寫。兩邊各取一份 ranking,再用 Reciprocal Rank Fusion(RRF)合併,是比直接砍掉其中一邊更務實的做法。
RRF 不直接混兩套尺度不同的分數,只看名次。下面的版本可以直接執行:
1 | from collections.abc import Sequence |
Elasticsearch 官方也建議用 RRF 做 hybrid search。它能把標準文字檢索與 kNN retriever 放在同一個請求裡。不過 hybrid 不保證必勝;RRF 的 rank_window_size、兩邊取多少候選、chunk 粒度都要用同一組標記資料重跑。
範例假設同一份 ranking 內沒有重複 ID。正式接資料時先去重,避免單一 retriever 替同一份文件重複加分。
Agent 放最後一段,工作反而更像 Agent
我會把 production RAG 切成三層:
- BM25 做全域候選發現,先保住識別碼、日期與專有名詞。
- 向量搜尋補改寫與概念相近的 query,必要時用 RRF 合併。
- Agent 只在縮小後的候選裡重查、比對衝突、跨文件整理證據,再由 reader 產生答案。
這個順序看起來把 Agent 綁住了,實際上是把 token 留給它擅長的部分。讓模型在 51 萬份文件裡猜路徑,不叫自主,叫沒有索引。
候選數可以先從 20 到 50 估,但這只是起始假設。最後仍要用標記資料掃過不同 K 值,觀察 recall、延遲與 token 成本。
還有一個邊界要留著:論文的 Agent+BM25 每題仍花 101K tokens。它的 69.4 分值得注意,不代表每個查詢都該啟動 Agent。精確查詢一次 retrieval 加 reader 就能答,多文件彙整或衝突判斷才值得付那筆帳。
我原本以為這篇論文的看點是老演算法打臉新方法。讀完後更像一堂資料結構課:先建立能看見全域的索引,再談模型要怎麼行動。
下次遇到 RAG 找不到明明存在的文件,我會先查 Recall@5 和候選清單。先別急著換更大的模型。










