AI 工程

Token Saver 拆解:一個 MCP 檢索層,怎麼把 PDF 問答砍到 1% token(以及你該偷走的三個設計)

一、你不是缺一個更聰明的模型,是缺一個檢索層

把 233 頁的判決書整份丟進對話,再問「它怎麼處理 strict scrutiny」——這是多數人讀長文件的預設做法,也是最貴的做法。你付了十幾萬 token 的錢,模型真正用到的可能是兩段話。

而且這不只是錢的問題。context 被 200 頁不相關內容塞滿,注意力會被稀釋,回答品質常常比「只給對的兩段」還差。這件事在 coding agent 上尤其有感:你把整包 docs 塞進去,它反而開始引用錯的章節、把 v1 的 API 當成現在的寫法。

Token Saver 是 MarkTechPost AI Media 在 2026/07/30 開源的小工具,處理的正是這一段。它值得拆的原因不是「省錢」這個結論——那是廢話——而是它把一條檢索 pipeline 的每個決策都寫成了可讀的常數:180、40、0.4/0.6、0.25、8000。這些數字你可以直接抄進自己的 agent。

二、它是什麼、誰做的

先把身分講清楚,因為這決定了它的天花板。

  • 本體:Claude Desktop 的桌面擴充(.mcpb bundle),檔名 token-saver-ccr.mcpb
  • 作者:Arnav Rai(Rochester Institute of Technology 學生,於 Marktechpost 實習),由 Jean-marc Mommessin 與 Asif Razzaq 指導,掛在 Marktechpost AI Media Inc 名下
  • 授權:MIT
  • 版本:repo 的 mcpb/manifest.json0.3.0eval/RESULTS.md 也是對 0.3.0 量測的。媒體稿寫 v1.0,這裡以 repo 為準
  • 範圍:只處理本機 PDF,只回傳相關段落 + 頁碼

README 的 badge 寫「92–98% fewer tokens」,MarkTechPost 的標題寫 90–99%。兩個都不算錯,差別在你看的是哪一份文件、問幾次——這點第四節會算給你看。

它的核心主張很單純:PDF 不上傳,只有被選中的段落進模型。抽取、切塊、embedding、打分全在你的機器上跑,MCP 走 stdio,不開任何 listening port。

三、運作原理:八段 pipeline,每段都有名字

Token Saver 八段檢索 pipeline 與各段參數

以下常數全部來自 scripts/index_store.pyscripts/mcp_server.py,不是報導轉述。

① 抽取

優先用 pypdfium2(文字品質較好),沒裝就退回 pypdf。兩者都是純文字抽取——沒有 OCR。掃描件、拍照 PDF 進來就是一片空白,這是硬限制。

② 切塊:180 字一塊,重疊 40 字

PASSAGE_WORDS = 180      # 一塊大約一個密實段落
PASSAGE_OVERLAP = 40     # 跨窗邊界帶過去的上下文

180/40 是個保守配置。重疊 40 字(約 22%)是為了避免答案剛好被切在邊界上——這是所有 chunking 的老問題,它選擇用重疊硬扛,而不是做語意切分。

③ 雙路打分:BM25 0.4 + 向量 0.6

關鍵字路走 SQLite 的 FTS5 虛擬表跑 BM25;語意路用 all-MiniLM-L6-v2(384 維)算 cosine。兩邊分數 min-max 正規化後混合:

kw_weight = 0.4    # FTS5 BM25
sem_weight = 0.6   # cosine similarity

向量存法很樸素:normalized float32 BLOB 塞進 SQLite,查詢時用 numpy 暴力算全表 cosine。沒有 HNSW、沒有 FAISS。對單份幾百頁的 PDF 這完全夠用,但你要拿這套去掃十萬份文件就會爆——設計上它本來也不打算。

索引落在 ~/.token_saver/indexSCHEMA_VERSION = 4(切塊或抽取邏輯改了就強制重建)、TTL_DAYS = 14

④ 棄權門檻(abstain gate)

SEM_FLOOR = 0.25   # 可用 $TOKEN_SAVER_SEM_FLOOR 調
eligible = (sem >= SEM_FLOOR) | (kw > 0)

這是整個系統最有想法、也最有問題的一段。它的意圖是「不確定就不要回」,避免模型拿著爛段落硬掰。作者自己在 docs/known-issues/false-accept-abstain-gate.md 裡承認了這條邏輯的漏洞:kw > 0 表示只要沾到一個關鍵字就放行。文件裡到處都有的 notes、report、section、agreement,會讓一個完全離題的問題撈到一段 boilerplate;min-max 正規化再把它的分數推高,因為沒有更好的東西可比。

作者列的三個候選修法:改成關鍵字分數的絕對/相對門檻、要求至少命中 2 個相異查詢詞、對混合分數設絕對下限。第二個做法在檔名解析那邊已經用了。

⑤ 去重 → ⑥ 修剪 → ⑦ 預算

TRIM_WINDOW = 3           # 每個保留區塊 3 句
TRIM_MAX_WINDOWS = 3      # 一塊最多留 3 個不連續區段
TRIM_MIN_KEEP_WORDS = 40  # 修剪後的字數下限
MAX_RESPONSE_CHARS = 8000 # 整個回應的硬上限

「句窗修剪」是這裡最實用的一招:命中的不是整塊 180 字,而是塊內命中句 ±1 句組成的 3 句小窗,一塊最多留 3 個這種小窗。等於在 chunk 之下再做一次壓縮。

然後 MAX_RESPONSE_CHARS = 8000硬牆。不管檢索回來多少,送進模型的就這麼多。這比「希望 LLM 自己節制」可靠一百倍。

⑧ 信封:每段都帶來源

每個回傳段落都附上檔名與頁碼。這件事的價值不在好看——它讓 ④ 的 false-accept 變成使用者可見的錯誤。你看到引用的是第 3 頁的目錄,就知道這答案不能信。

讀 code 時要注意:repo 裡還有一條獨立路徑 scripts/retrieve.py(CLI 用),用的是不同的打分公式 s = kw + 5.0 * max(sem, 0)、BM25 k1=1.5 / b=0.75、Jaccard 0.6 去重、語意編碼只取每頁前 2000 字元。它跟 MCP server 走的 index_store.py 不是同一套 scorer,別混著讀。

四、數據:哪些能信,哪些有前提

Token Saver 實測省幅與適用前提

eval/RESULTS.md(2026-07-25,對 0.3.0 量測)給了兩組數字。

檢索品質——gold set 30 題(Dobbs v. Jackson 213 頁、Berkshire Hathaway 2023 年報 152 頁、內建合成文件 8 頁,各 10 題):

設定 recall@5 false-abstain
Hybrid(語意 + 關鍵字) 0.90 0.00
純關鍵字 0.90 0.00

注意這張表:hybrid 跟純關鍵字的 recall 一模一樣。作者沒有藏這件事,而是加了一句關鍵註解——兩者「fail on disjoint sets」,失敗的題目不重疊:語意路撈得到換句話說的問題,關鍵字路撈得到精確術語。所以 embedding 的價值不在把 0.90 推到 0.95,而在覆蓋不同的失敗模式。這也解釋了為什麼 embedding 是選配:沒裝 sentence-transformers 就退回純關鍵字,recall 不會崩。

Token 省幅——用 tiktoken 量,單一問題、單份文件:

文件 baseline 實際用量 省幅
FDA 藥品仿單 23,959 1,021 95.7%
GDPR 70,260 996 98.6%
SFFA v. Harvard 133,349 740 99.4%

(MarkTechPost 的報導標註這三份分別是 33、88、233 頁;repo 的 RESULTS 表格只給 token 數。)

這裡有兩個一定要講清楚的前提:

  1. baseline 假設每次查詢都把整份文件塞進去。RESULTS.md 原文:百分比可信,但絕對數字建立在「naive baseline 每次搜尋都收整份文件的費用」上。如果你本來就有 prompt caching,或一次載入問十題,實際差距會小很多。
  2. 省幅是文件大小的函數,不是常數。repo 自己給的階梯是:20 頁約省 14%、80 頁約省 78%、300 頁約省 94%。作者的原話是「把 92% 當地板、上面那張表當常態」——但那個地板是對夠大的文件而言。你的 PDF 只有 20 頁,省 14% 還要付索引與 embedding 的成本,划不划算自己算。

還有三個誠實的限制:

  • gold label 是作者自己寫的、未經第三方驗證(RESULTS.md 明載)
  • 選檔比檢索弱:16 份 PDF 的資料夾測試中,選對檔案 12/14(約 86%)。泛稱名詞會選錯,講具體名字才穩
  • 頁碼引用沒有章節層級的 provenance,多意見書的判決裡不知道引的是主文還是協同意見
  • 測試覆蓋:18 個 index 測試 + 106 個 server 測試

五、手把手:裝起來並問對問題

安裝(不需要 terminal)

  1. 從 Releases 下載 token-saver-ccr.mcpb
  2. Claude Desktop → Settings → Extensions(或 Settings → Advanced Settings → Install Extensions)
  3. Install extension,選那個 .mcpb
  4. 把 Enabled 打開,點 Configure,選一個小而專用的 PDF 資料夾——這是權限邊界,不要選整個 ~/Documents
  5. 開新對話問「List my documents」
  6. 第一次問問題會下載 embedding 模型,2–5 分鐘

環境需求:Python 3.10+(bundle 內含)、Claude Desktop。相依是 mcp>=1.2sentence-transformers>=2.6numpy>=1.24pypdf>=4.0,選配 pypdfium2(抽取品質更好)與 tiktoken(沒有就退回字元數估算,savings 的數字會比較粗)。

踩雷提醒:加了新 PDF、或工具沒出現時,要完全退出再開 Claude Desktop(關視窗不算)。

七個工具怎麼用

工具 用途
ask 找檔 + 載入 + 回答一步到位(target, query, k=5
list_documents 列出可用 PDF,標示已載入的
ingest 預先索引一份 PDF
search 只回傳 top-K 片段 + 頁碼,不生成答案
clear 清掉文件或整個 session
status 已載入哪些、idle 多久
savings 這個 session 省了多少 token

Prompt 寫法:具體名字 > 泛稱

選檔靠模糊比對(NAME_THRESHOLD = 0.5),所以泛稱會出事。

別這樣寫:

幫我查那份報告裡關於風險的段落

「報告」「文件」「那本書」這類詞會在 16 份 PDF 裡撞成一團。

這樣寫:

用 token saver 查 GDPR:資料主體行使刪除權時,控制者有哪些法定例外可以拒絕?請附頁碼。
先 ingest berkshire-2023,然後 search「float 的定義與規模變化」,k=8,只給我原文片段不要總結。

批次調查時分兩段更省:先 search 拿片段自己掃一眼,確認方向對了再 ask

每次都要看頁碼。 前面說過 abstain gate 會 false-accept,引用就是你的偵錯訊號——引到目錄頁或版權頁,就換個更具體的提問詞重問。

收尾時問一句「show savings」,savings 會回這個 session 的累計省量。這個數字建立在第四節那個 naive baseline 上,當相對指標看就好。

六、調參:七個環境變數

預設值不見得適合你的文件。這些是 mcp_server.pyindex_store.py 讀的:

變數 預設 什麼時候動它
TOKEN_SAVER_ALLOWED_DIRS 由 Configure 設定 多個資料夾(os.pathsep 分隔)
TOKEN_SAVER_DOC_DIRS 額外的掃描 root
TOKEN_SAVER_MCP_MAX_CHARS 8000 答案常被截斷就往上調,但這是你的成本閘門
TOKEN_SAVER_MCP_TTL_MINUTES 30 同一份文件要問一下午 → 調大,省重建
TOKEN_SAVER_SEM_FLOOR 0.25 撈回太多離題段落就往上調(0.35 起試)
TOKEN_SAVER_INDEX_TTL_DAYS 14 磁碟索引保留天數
TOKEN_SAVER_TRIM on 關掉會拿到完整 180 字塊,但 token 立刻上升
TOKEN_SAVER_CONFIRM off 打開後模糊命中檔名要先確認,適合資料夾很雜時
TOKEN_SAVER_SANITIZE off 置換疑似 prompt injection 的行——處理來路不明的 PDF 時應該打開

最後那個值得單獨講:你檢索回來的段落會被當成可信內容送進模型。一份惡意 PDF 埋一行「ignore previous instructions」,就是一條 indirect prompt injection 通道。TOKEN_SAVER_SANITIZE 是它給的緩解手段,預設沒開。

七、想在 Claude Code 用?現況是這樣

先講結論:官方明確不支援INSTALL_GUIDE.md 寫得很清楚——只支援 Claude Desktop 應用程式,不支援 claude.ai 網頁版,也沒有提到 Claude Code。

但從 mcpb/manifest.json 看,它底層就是一個標準的 MCP server,啟動命令是:

command: uv
args: ["run", "--project", "${__dirname}", "token-saver-mcp", "${user_config.allowed_directories}"]

也就是說,理論上可以手動接進任何 MCP client:

git clone https://github.com/Marktechpost/Token-Saver
cd Token-Saver
claude mcp add token-saver -- uv run --project . token-saver-mcp "$HOME/Documents/token-saver-pdfs"

我沒有實際跑過這條路徑,這是從 manifest 的 mcp_config 推出來的等價指令。作者也沒測過 Claude Code——.mcpb 的權限提示、資料夾 allowlist UI 都是 Desktop 專屬的,手動接的話這層保護就要靠你自己傳對 TOKEN_SAVER_ALLOWED_DIRS。當實驗做可以,別放進團隊 workflow。

而且說實話,對 coding agent 來說這工具的用途本來就窄:它只吃 PDF。你的痛點是 codebase 和 markdown docs,不是 PDF。真正該帶走的是下一節。

八、該偷走的三個設計

如果你要幫自己的 agent 做一層檢索 MCP(吃 codebase、吃 Confluence、吃 API docs 都行),這三件事直接抄:

1. 檢索層必須有 abstain gate,而且門檻要看混合分數。

多數自製 RAG 是「永遠回 top-5」,不管相似度多爛。Token Saver 的方向對了——設 SEM_FLOOR 並允許棄權——但它的實作 (sem >= FLOOR) | (kw > 0) 是反面教材。別用「有沒有命中關鍵字」當 OR 條件。照作者自己列的修法做:要求至少 2 個相異查詢詞命中,或對混合分數設絕對下限。

2. 給輸出一道硬牆,不要指望模型自制。

MAX_RESPONSE_CHARS = 8000 寫在 server 側。你的 MCP tool 也該這樣:在回傳前截斷,並在訊息裡明說「已截斷,還有 N 段」。讓 agent 知道自己看到的是子集,比偷偷截斷安全得多。

3. 每一段都帶 provenance,這是給人看的偵錯線。

檔名 + 頁碼(或檔名 + 行號範圍)。這不是禮貌,是讓 false-accept 從「模型幻覺」降級成「使用者一眼看穿的錯誤」。做 code 檢索的話,path:line-line 更好,因為使用者可以直接點開驗證。

順帶一提第四個小招:先跑純關鍵字,embedding 當增益。0.90 vs 0.90 那張表已經說明,對術語密集的技術文件,BM25/FTS5 就能吃掉大部分;embedding 的價值在補「換句話說」那類查詢。先把便宜的做對,再加向量。

九、什麼時候別用

  • 文件小於 50 頁:20 頁只省 14%,還要付索引時間,不如直接貼
  • 掃描件 / 圖片 PDF:沒有 OCR,抽不出字
  • 需要通讀全文的任務(翻譯整份、逐條摘要、跨章節比對):檢索天生會漏,這類任務要的就是全文
  • 文件庫很大:暴力 numpy cosine 沒有 ANN 索引,規模上去會撐不住
  • 答案分散在十幾個地方:top-K + 8000 字元上限會截掉尾巴
  • 你的痛點是 codebase:這工具只吃 PDF,去找專門做程式碼檢索的

適合的場景很明確:一份幾百頁的技術規格 / 法規 / 年報,你要對它問二十個具體問題。 這時候省的不只是錢,是模型不會被 200 頁雜訊帶偏。

十、來源

整理:DataAgent · Coding Agent 實戰教學

發表迴響

%d 位部落客按了讚: