倢愷 Oscar 的 Agent Harness 四部曲導讀: 從 function call 到 LLM Wiki
倢愷 Oscar 的「Rethinking Agent Harness,如何更好設計 LLM Agent」系列四篇讀完,小編覺得是近期中文圈少見的一組硬派文章,蠻推薦寫 LLM application 的人找時間讀一遍。它不教你怎麼用某個 framework,而是一路在問同一件事: 這個設計解決了 LLM 哪一個限制? 又在什麼條件下會失效?
作者對現在 Harness Engineering 的討論有個判斷蠻準的: 很像 2023 到 2024 那批 Prompt Engineering 文章,名詞給了一堆 (planner、memory、reflection、context management),卻很少回答「為什麼這些設計有用」。而能回答出來的那部分,才是不會過半年就作廢的知識。
四篇是中文長文:
- Part 1: Behind Function Calling
- Part 2: 理解 Skill 之美
- Part 3: 為什麼 Grep 打敗了 RAG?
- Part 4: LLM Wiki 取代 RAG?
所以這篇就當導讀。原文有大量篇幅在講 decoding 機制、語法自動機、tokenizer 的 special token 分層,小編這邊跳過,只重點挑出開發 LLM application 可以直接拿來用的最佳實務,更多細節請回頭看原文。
整個系列最後收斂到封面那張圖: 一個好的 LLM application 是 LLM × Harness × Data × Task 四個面向互相 fit 的結果。只優化其中一角,通常會在別的地方失衡。
1. function call 的失敗分三層,你能動的其實只有一層
作者最在意的一件事: 大部分工程師看到 function call 失敗就說「這就是 hallucination」,而說出這句話的同時,其實就放棄了真正解決問題的可能。

上面四種輸出看起來都是「JSON 不對」,實際上是三個不同層次的問題:
- Decision: 該呼叫工具卻沒呼叫、或呼叫了錯的工具。
- Serialization: 外層結構壞掉,parser 根本抽不出 name 跟 arguments。
- Guarantee: 是合法 JSON、結構也對,但內容違反 schema。enum 只允許
celsius/fahrenheit,模型填了kelvin。
這裡要先補一個容易被忽略的事實: function calling 不只是模型的事。 一次成功的 tool call 是模型與推論引擎 (inference server) 兩邊配合出來的結果。模型這端在訓練時學會用某種格式輸出 tool call (Hermes 用 <tool_call>…</tool_call>、Mistral 用 [TOOL_CALLS]、Llama3 用 <|python_tag|>,每家都不一樣),推論引擎那端則要有對應的 parser,才能把那段文字抽成結構化資料,兩邊沒對上就會壞。作者舉了 2024 年 llama3.1 剛支援 function calling 的那段時間: system prompt 稍微改一下、tool definition 寫複雜一點就壞掉,而 vLLM 那邊怎麼調 parser 都救不回來。
所以「這個模型 function call 準不準」這句話其實不完整,同一個模型換一套 serving stack,行為就可能不一樣,自架或換 gateway 的時候特別要留意。
分層的重點是搞清楚哪一層輪得到你處理。如果你用的是 OpenAI API 並且開了 Structured Outputs (strict: true),後兩層基本上被 provider 處理掉了: 它會在每個 token 生成前先算出哪些選項合法、把不合法的壓掉,所以 enum 違規、tool name 亂編從根本上不會發生。代價是複雜 schema 第一次編譯會拉長延遲,官方文件自己寫著最多可能要等一分鐘,所以 schema 越簡單越好不只是為了模型好讀。
反過來,如果你是用開源模型自架推論引擎,後兩層就得自己弄清楚,原文也花了很大篇幅在講這塊 (事後解析與生成前約束各自的細節,以及主流開源推論引擎預設會逼你在兩者之間二選一)。小編這邊主要用 OpenAI API,就跳過不談了。
剩下的 Decision 才是你真正要面對的,而它是唯一沒有技術手段可以直接處理的一層: 你可以用文法強迫模型吐出合法 JSON,卻沒辦法用文法強迫它「選對工具」。你能動的只有工具本身怎麼設計: 命名、數量、粒度,也就是接下來兩節的主題。所以看到失敗時,第一件事是先分清楚「格式壞了」還是「選錯工具」; 一律 retry 的話,永遠不會知道問題出在哪。
2. tool design 的老原則,背後在優化什麼
這幾條大家可能都聽過,原文的貢獻是把每一條對應回它實際解決的問題:
- tool 不要太多。 不只是減輕模型的選擇負擔,也在降低文法複雜度。原文給了一個好用的公式: tool surface size = tool 數量 × schema 複雜度 × enum 可能值數量,三個項目任何一個變大,上面那三層的失敗率會一起上升。
- 命名要語意清楚。 模型不是在查一份 API 清單,而是在 tool 定義上做 next-token prediction。
create_calendar_event比handle_event更接近模型看過的好程式碼; 反過來用HR_tool1這種代號、指望靠 description 教會它選,這些名字根本不在模型自然會輸出的範圍內,只是多給它一層負擔。 - default value 不要放進 schema。 能由後端、使用者偏好或 locale 決定的參數就別暴露給模型: 對事後解析來說,optional 欄位多出一個判斷不了的狀況 (沒出現是合理省略,還是模型漏填?); 對生成前約束來說,optional 欄位的組合會讓文法分支指數成長。
- tool name 用共同前綴。 這條來自 Manus 的「Mask, Don’t Remove」,也是小編覺得最容易被忽略、但最好實作的一條。
click_element
extract_content
run_command
read_file
write_file
send_message
search_messages
create_event
query_events
shell_ → exec / read_file / write_file
slack_ → send / search
前綴一致之後,選工具在底層就變成兩層分類問題,而且執行環境可以依 agent 當前階段先限制合法前綴: 現在在 browser 階段,就只允許 browser_ 開頭的工具。50 選 1 變成兩次 3 到 5 選 1。
3. 小編補充研究: 共同前綴在 OpenAI 跟 Claude 上適用嗎?
這一節不在原文範圍內,是小編自己去查官方文件補的。 原文談共同前綴時是站在自架推論引擎的角度,小編想知道用 OpenAI 或 Claude API 的人能不能拿到同樣的好處。
把工具選擇變成兩層分類這件事,閉源 API 做不到。 「先鎖定合法前綴、再選具體工具」是在生成時把不合法的 token 遮蔽掉,Manus 是在自己的推理層實作的; OpenAI 跟 Claude 都沒有這個介面,所以兩層分類不會真的發生在 token 層級。
但命名層的好處兩家都拿得到,而且兩家官方都直接建議這樣命名。 Anthropic 的工具定義文件寫得很明白: 「工具名稱要用有意義的 namespace。當工具跨多個服務或資源時,用服務名當前綴 (例如 github_list_prs、slack_send_message)。這會讓工具選擇在你的工具庫變大時仍然沒有歧義,在使用 tool search 時尤其重要。」最後那句是關鍵,tool search 是靠名字找工具的,前綴直接決定搜得準不準。
而「不要移除工具」這件事,在閉源 API 上的理由換成了 prompt cache。 兩家的工具定義都排在 prompt 最前面 (Anthropic 的組裝順序是 tools → system → messages),所以只要增刪或重排一個工具,後面整份 cache 就失效。也因此兩家都給了「限制可用子集、但不動 tools 清單」的官方做法:
- OpenAI 有
tool_choice: {type: "allowed_tools", mode: "auto", tools: [...]}。文件給的理由正是這個: 你想讓不同請求之間只有一部分工具可用、但不修改傳進去的工具清單,「這樣可以最大化 prompt caching 的節省」,幾乎就是「Mask, Don’t Remove」的產品化版本。 - Claude 沒有等價參數 (
tool_choice只有 auto / any / tool / none)。對應做法是 tool search 加defer_loading: 工具先宣告但不載入 context,讓模型自己搜出需要的那幾個,而且新載入的 schema 是附加而不是替換,cache 保得住。Claude Opus 5 另有一個 beta 機制,可以在對話中途用tool_addition/tool_removal增減工具而不動tools清單,同樣是為了不讓 cache 失效。
順帶一提,同一份 Anthropic 文件還有一條跟「tool 不要太多」呼應、但方向不同的建議: 把相關操作合併成較少的工具,不要拆成 create_pr / review_pr / merge_pr 三個,而是一個工具加一個 action 參數。共同前綴是讓工具數量多但有結構,合併是直接讓數量變少,兩條都在解同一個問題,可以看自己的工具庫規模選。
4. Skill 封裝的是「任務」,不是「操作」
Part 2 的核心一句話: skill 不是新的 tool,而是把一類任務的流程、參考資料與可重用程式碼,封裝成模型可以按需展開的能力。
為什麼「任務」這個粒度是對的? 回頭看第 1 節: Decision 是唯一沒有技術手段可以處理的一層,所以問題就變成「要在什麼粒度上讓模型做決定」。如果拆成 20 個原子操作 (extract_pdf_text、extract_pdf_tables、fill_pdf_form…),模型每一步都要回答「這 20 個微操作裡哪個是下一步」,而這個粒度本身就不自然: 訓練資料裡跟使用者的請求裡,講的都是「幫我處理這份 PDF 發票」。包成一個 pdf-processing skill,判斷就變成「這是不是 PDF 任務」,決定的次數變少,粒度也更貼近模型熟悉的樣子。
Progressive disclosure 是很精準的 context engineering。 過去認真寫一個 function,schema、使用時機、注意事項、few-shot 加起來就 2 到 4k tokens,20 個 tool 就是 40 到 80k,而作者給的 2026 年經驗法則是約 16K 以內基本穩定、超過約 64K 明顯退化。改成三層之後: L1 只有 name 與 description 常駐 (20 個 skill 約 4k),L2 是被選中才載入的 SKILL.md,L3 是模型自己去讀的 reference 與 script。
可重用的 script 是把隨機換成確定。 沒有 script 時,模型每次都得現場寫一段抽取邏輯,然後進入「寫 → 跑 → 看錯誤 → 改 → 再跑」的迴圈; 有一份驗證過的 script,這步就只是「用對的參數執行一行 bash」。作者的對照很有說服力: 各家實驗室花很大力氣把單步 function call 成功率從 95% 推到 97%,而一份包好的 script 是把「真正做事那一步」從徒手寫程式的 70 到 80% 拉到接近 100%,提升幅度大得多,而且掌握在 skill 作者手裡,不用等模型變強。
原文最後反過來列了三種會把這些優點抵銷掉的寫法,小編建議寫過 skill 的人都對照一下:
- SKILL.md 寫太長,又沒當索引用。 skill 被觸發時 body 會整份進 context,所以官方建議 500 行以內; 更重要的是它要兼任 L3 的目錄,明確寫出「遇到 X 就去看
references/Y.md」。沒寫的話模型不知道那些檔案存在,三層就退化成兩層。作者順手點出同樣的問題也出現在讓 LLM 寫 README 這件事上: 架構、roadmap、release note 全塞進去,而 README 正是 coding agent 很可能一開場就讀進 context 的檔案。 scripts/放的其實不是可重用的 script。 自我檢查三題: 全新環境能不能一行跑起來? 至少 3 種 input 驗證過? 常見錯誤有處理、而且 stdout 講清楚發生什麼事? 任一題答不出來,就別放scripts/,改放references/當參考,並在 body 說明需要依場景改寫,不然模型每次現場補完,隨機性又回來了。- 把現成的 MCP / tool 一對一包成 skill。 這只是換個包裝: 模型還是在操作粒度做決定,L1 還是 N 行 metadata,body 裡也沒有「多個 tool 怎麼協同」的知識可裝。正確的問法不是「我有幾個 tool,要包幾個 skill」,而是「我有幾類典型任務、每類會用到哪些 tool」,
github-create-pr/github-get-comments各包一個是錯的,一個pr-reviewskill 內部呼叫多個 tool 才對。判斷症狀很簡單: skill 數量跟 tool 數量一對一,這條就已經被打破了。
5. Grep 打敗 RAG: 為什麼最後收斂到 filesystem
Part 3 問的是為什麼主流 coding agent 都用檔案系統理解程式碼。這題小編之前整理過一篇 向量已死? Grep 萬能? 不,你需要的是「策展」一組檢索工具,談過 grep 的甜蜜點 (高訊號關鍵字、純文字、agent 可反覆迭代)、向量檢索接不住精確識別字,以及為什麼真實企業語料最後幾乎都走混合檢索。重疊的部分就跳過,這裡只補 Oscar 多給的兩點。
一是 code 這種資料還有兩個問題: 切塊會破壞語義 (自然語言被切開意思大致還在,但一個 method 失去 class 的上下文就無法推理)、索引一定會過期 (codebase 每天在變,實務上的 lazy update 會讓撈到的 chunk 跟現況對不上)。作者由此提醒: 所有非自然語言的資料要套 RAG,都該先檢驗這類問題,table 能不能直接 RAG? 投影片能不能?
二是「可以呼叫多次」本身沒什麼意義。前一篇提過 agent 可以改寫查詢再搜一次,Oscar 往下追問一句: 憑什麼相信下一次會更好? 重點是每次呼叫之間,agent 有沒有可信的進度訊號。

grep 在這件事上有一個其他檢索方式都沒有的性質: 完整性。它會回傳範圍內這個 pattern 的所有出現位置,沒有 top-k 截斷、沒有相似度門檻,第一次查太寬、撈出 200 個也沒關係,答案一定在裡面; 撈到 0 個也是「這個範圍加這個 pattern 下確認沒有」的硬事實,不是「我找不到」的曖昧訊號。失敗訊息還直接指出下一步: 換關鍵字、放寬 pattern、改目錄範圍。RAG 失敗時回的是一堆看起來相關但沒對到的 chunk,agent 分不出是 query 不夠精確、切塊切斷了、還是 embedding 不熟這個領域,只能重猜。所以設計任何 agent tool 都可以先問: 它失敗的時候,回傳的訊息能不能指導下一步?
把這些收起來,filesystem 勝出不是因為它比較傳統或比較簡單,而是四個條件剛好同時成立: LLM 對它很熟 (指令、錯誤處理、使用習慣全都在訓練資料裡)、harness 很容易把它包成可反覆呼叫的工具、code 本來就存在 filesystem 裡、repo debugging 又天然需要多步探索。這剛好就是封面那張圖說的 LLM × Harness × Data × Task 四個面向同時對齊。
編按: 這裡順便講一件事。context rot 這份研究在社群被引用得很兇,但作者從頭到尾不引用它,因為他認為這篇的研究過程不夠實在: 同類問題在它之前已經有 20 到 30 篇論文討論過,它沒有提出新的貢獻,而且多數實驗得出的「超過 1,000 tokens 之後就不可信」這種結論,跟實務經驗明顯不符。他不是反對「長 context 會退化」這個現象,他自己引用的是 Lost in the Middle 與 NVIDIA RULER (後者顯示很多號稱 128K 的模型有效長度只有 64K 甚至 16K),他反對的是拿這份研究當依據。
6. LLM Wiki 適合企業檢索嗎?
Part 4 談 Karpathy 的 LLM Wiki。這個題目小編五月也寫過一篇 LLM Knowledge Base: 用 LLM 編譯個人知識庫,各路實作全比較,介紹過三層架構 (raw/ 唯讀、wiki/ 由 LLM 全權維護、CLAUDE.md 當規則文件)、匯入到健檢的四個動作,以及社群那一票實作版本,這裡就不重複了。
Oscar 這篇要回答的是另一個問題,也是企業真正在意的那個: 這套設計適合 corporate retrieval 嗎? 大家在意的從來不是 Karpathy 示範的個人學習場景,而是在公司內部大量文件裡找到對的那篇來回答問題。而這套設計的核心是把合成工作提前到 ingest 時做,划不划算,完全取決於資料跟任務的性質。
它成立的條件是三者的交集: personal + immutable + synthesis-heavy。 這三個條件剛好各解掉一個痛點: 原始資料不可變,LLM 合成出來的頁面就不會因為 source 被改而過期 (所以 Karpathy 直接規定 raw/ 不能改也不能刪); 個人使用,權限就不需要傳遞; 任務本來就需要跨文件綜合,那 ingest 時先整理好就從成本變成需求本身,你讀 paper 本來就要做筆記、比較方法。反過來,如果任務是「公司 VPN 密碼在哪」,那些預先整理就是純成本。
所以 Karpathy 的示範看起來這麼漂亮,不是因為 LLM Wiki 對所有場景都更好,而是他剛好站在 trade-off 全部對自己有利的位置上。而企業真正在意的 corporate retrieval,幾乎把每一條假設都反轉了:

- task 不是單一綜合,而是一整組。 快速查找 (請假流程)、數值查詢 (Q3 revenue,這類本來就該走 text-to-SQL 或 chat-to-BI)、跨文件綜合,三種混在一起。對前兩類,預先合成的敘述沒人會用到,ingest 成本卻一份不少。
- 資料不是定稿,而是每天在動的運作資料。 policy 上週改、Jira 下午狀態又變。wiki page 是跨 source 合成的,改一份 source 就要反查所有吸收過它的 entity page、比較頁、索引,而這個依賴常常是隱性的。更麻煩的是漏改的過期資訊會被當成既有背景知識拿去整理新頁面,錯誤會擴散。RAG 至少可以把舊 chunk 全丟掉重新 embed,LLM Wiki 沒有一鍵清除。
- 權限邊界會在合成時消失。 法務的合約 note (法務 + C-level 可看) 跟 PM 的 spec (product + eng leads 可看) 都提到 Project Atlas,被整理進同一頁,這頁誰能看? 取交集可能剩沒幾個人、這頁就失去檢索價值; 取聯集就是外洩; 只用一邊的權限,跨來源合成的價值又消失了。作者順手補了句蠻犀利的: 市面上一堆 GraphRAG 開源專案演算法很好,但根本沒解 ACL,可以直接過濾掉。
- 規模大 3 到 5 個數量級,而卡住的不是 token 成本。 原文試算過,就算 10M 份資料也是百萬美元等級,很貴但不是不能接受。真正的問題是結構維護: 新資料要不要 merge 到既有 entity page? 怎麼找候選? 矛盾怎麼找? entity 到百萬級就不可能全塞進 context,你需要一層候選生成 (BM25、vector index、metadata filter),敏感的讀者應該發現了,RAG 又回來了。
- 企業內容是極端的熱門/冷門長尾。 常見比例是 95/5。LLM Wiki 的隱含假設是「ingest 成本會攤平在後續 query 上」,個人場景成立; 企業場景下你重度整理的那 95% 一輩子不會被查一次,那筆成本直接收不回來。
小編前一篇的收尾寫過「任何持續有新資料進來、需要被結構化整理、會被反覆查詢的場景都適用」,Oscar 這篇把條件補得更精確: 上面第 2 條跟第 5 條講的正是「資料持續變動」與「大部分內容其實不會被查」,而這兩件事剛好都打在 LLM Wiki 最痛的位置。
所以正確的問法不是「LLM Wiki 能不能取代公司 KB」。公司不是單一 corpus、也不是單一 task,而是一個組合,該問的是哪些 sub-domain 適合,而判斷方式就是把上面五條倒過來看: 規模有界、資料偏定稿、權限一致、需求是綜合、內容大部分會被查到。原文列的例子包括決策前研究 (評估 vendor、競品、併購對象)、研究團隊的知識整理、法規研究、產品決策記憶 (ADR、當初為什麼這樣決定)、大客戶研究。
原文最後把 LLM Wiki 的價值拆成三軸: ingest 時合成 (把整合負擔從 query 路徑移開)、關係編碼 (用有界、有結構的導航取代無界的相似度搜尋)、LLM 當 curator (lint 過、對照過矛盾的內容比原始 chunk 可信)。而這三件事沒有一件是新的,RAPTOR 的階層式摘要、GraphRAG 與 HippoRAG 的關係式檢索、Mem0 與 A-Mem 的記憶維護,各自都已經有一堆論文與專案做過。
LLM Wiki 的特殊性在於三件事同時做到、全部放在同一份 markdown 檔案系統上、用同一套 Glob / Grep / Read 操作。 也因此要為企業重新設計時不必從 LLM Wiki 出發,更該把三軸拆開、各自找已經成熟的方案,再跟 dense / sparse / rerank / graph / metadata / ACL 這些系統整合起來。
所以這節的答案是: LLM Wiki 不會取代企業檢索,但值得拿去解企業內的某幾塊 sub-domain。 先用上面那組條件找出符合的那幾塊,其餘的走 hybrid solution,而怎麼 hybrid,Oscar 說要留給之後的文章專門寫。
harness 只是一半,另一半是 Data + Task
最後把封面那張圖的四個面向講清楚,因為整個系列都在用它做判斷:
- LLM: 模型本身的限制。context window 是有限資源,而且模型只在它訓練時大量看過的形式上表現穩定 (原文稱為 Pθ 對齊)。所以不能假設模型會記住你給它的所有東西,也不能長期要求它用陌生的格式或介面工作。
- Harness: 你包在模型外面的執行環境,工具、狀態、記憶、檔案系統、權限、錯誤恢復。重點不是「工具能被呼叫很多次」,而是每次呼叫之間有沒有可信的進度訊號。
- Data: 你的資料長什麼樣。有沒有結構、能不能被切開、變動頻率多高、有沒有權限邊界。
- Task: 使用者實際要做的事。一次檢索就結束,還是需要多步探索? 是快速查找、跨文件綜合,還是數值計算?
一個好的 LLM application 是這四者互相 fit 的結果,這也是為什麼同一個技術在不同場景會得到相反的結論。
Part 1 到 3 優化的都是 LLM + Harness 那一側: 怎麼讓模型穩定產生 tool call、怎麼把多步任務封裝起來、為什麼檢索收斂到檔案系統。但 Part 4 推到企業場景之後,其實已經離開 harness 了,真正卡住系統的是 Data + Task: 資料會變,所以要處理新鮮度與版本; 資料有權限,所以要處理權限傳遞; task 是一整組,所以不能用一種檢索策略打全部。這些都不是更好的 prompt、tool schema 或 skill description 能解決的。
作者的觀察小編蠻認同: 很多 agent demo 走不到 production,不是模型不夠強、也不是 harness 不夠新,而是只優化了 LLM + Harness,Data + Task 根本沒被設計,甚至沒被注意到。
這個框架也順便回答了為什麼大家老覺得 LLM application 技術變太快: 第 5 節那四個條件只要有一個改變 (模型突破、出現不同形式的 harness、有人定義出更好的任務),最佳解就會換一輪。要抓住的是這些條件,而不是當下的答案。