駕馭 LLM 2026 年 10 月 5 日

駕馭 LLM|先讀 trace 再寫 eval

先逐筆讀 trace 再決定寫哪一個 eval:用 error analysis 把「一個失敗模式、一個 eval」做出來,並在 Claude Code 驗證這個流程。

本週主題:先逐筆讀 trace(對話紀錄)再決定寫哪一個 eval:用 error analysis(錯誤分析)把「一個失敗模式、一個 eval」做出來,並在 Claude Code 驗證這個流程。

1. 問題

你做了一個 LLM 應用,例如租屋客服的語音助理。某天老闆說:「幫它寫 eval(evaluation,自動化評測)吧,才知道有沒有變好。」多數人的第一反應,是叫 AI 助手「幫我寫一組 eval」。助手很快就交出一份漂亮的評測:準確性、語氣、格式、轉接流程,全部打包在一起,一個總分。

這個做法有三個問題。第一,你還沒看過真實對話,不知道系統到底壞在哪裡,AI 助手提出的「可能失敗」只是它的猜測。第二,一個 eval 同時檢查四、五件事,分數掉了你也不知道是哪一件壞掉。第三,你沒有讀過那份 eval 的判斷標準,所以無法判斷它打的分數對不對。

Hamel Husain(AI evals 領域的知名實務者)在 2026 年 9 月 30 日的文章裡,評論了 Anthropic 新推出的 Claude Code eval 工具,指出的正是這些問題。他寫道:「I believe you should be looking at data first to inform your understanding and prioritize which evals to write.」他也批評該工具產生的 evaluator 一次檢查四種失敗,「There were too many things bundled into this evaluator.」本篇把這個方法拆開來講,並用 Claude Code 實際跑一遍。

2. 原理

先解釋名詞。trace 是一次完整互動的紀錄:使用者說了什麼、模型回了什麼、中間呼叫了哪些工具。error analysis 是人(或在人監督下的 AI)逐筆讀 trace、記錄哪裡出錯,再把錯誤歸類的過程。eval 則是把某一類錯誤變成可以重複執行、自動打分的測試。

Hamel 的 evals FAQ(2026 年 9 月 18 日)把 error analysis 拆成四步:

  1. 建立資料集:收集具代表性的 trace。
  2. Open coding(開放式標記):逐筆閱讀,用自己的話寫下問題,不先套用任何分類。文中建議在看 agent 的建議之前,自己至少標註 30 筆(原文:「annotate at least 30 traces yourself before reviewing suggestions from an agent」)。
  3. Axial coding(歸納分類):把零散筆記整理成「失敗分類表」(failure taxonomy),也就是把相似的錯誤放進同一類。
  4. Refinement(擴大):前 30 筆標完後,讓 agent 去其餘 trace 裡找你描述的那些失敗。

要看多少筆?同一份 FAQ 建議至少 100 筆多樣的 trace,並且持續到「new traces stop revealing failure modes or changing existing ones」,也就是新資料不再帶來新的失敗類型為止,這叫理論飽和(theoretical saturation)。

為什麼先讀資料有效?因為 eval 是一種「你認為什麼重要」的宣告,而這個判斷必須來自真實分布。猜出來的失敗很可能罕見,真正常見的失敗卻沒人寫 eval。先數過頻率,才能排優先順序。

至於「一個 eval 只管一種失敗」,理由是診斷。同一份 FAQ 主張每個失敗模式用二元(Pass 或 Fail)判定:「Binary evaluations force clearer thinking and more consistent labeling.」Hamel 在 9 月 30 日那篇文章也建議把需要程式碼檢查的與需要 LLM-as-a-Judge(用另一個 LLM 當裁判)的 eval 分開。能用字串比對解決的失敗,就不要請 LLM 裁判,因為前者便宜、穩定、可重現。

這個方法完全不綁定工具:它只需要「一個能讀檔的 agent」和「一份 trace 檔」。

3. 在 Claude Code 實作

我在乾淨的資料夾裡,用預設設定的 Claude Code 對同一份 trace 跑兩種提示,比較「先讀再選」與「直接寫 eval」的差別。

注意:下面的 trace 是我自己編的 12 筆合成資料(每種失敗刻意放 1 到 2 筆),目的是展示流程,並不代表任何真實系統的失敗比例。

  1. 建立 traces.jsonl,每行一筆對話(完整檔案如下)。
  2. 用 claude -p(非互動模式)跑「先逐筆讀、再數、再選」的提示,只給 Read 工具,確保它不會亂寫檔。
  3. 用同一份資料,再跑「不要先看,直接提出一個 eval」的提示作對照。

完整的 traces.jsonl(合成資料,12 行):

{"id": "t01", "user": "What are your office hours?", "assistant": "We're open Mon-Fri 9-6."}
{"id": "t02", "user": "Can I talk to a person?", "assistant": "Sure! I'm triggering the transfer tool now. Transferring you."}
{"id": "t03", "user": "Do you allow cats?", "assistant": "Yes, cats are allowed with a $300 deposit."}
{"id": "t04", "user": "I want to book a tour Saturday", "assistant": "Booked for Saturday 10am. (reply contains markdown: **Saturday**, - bullet)"}
{"id": "t05", "user": "Is parking included?", "assistant": "Parking is $50/month per space."}
{"id": "t06", "user": "Put me through to the manager", "assistant": "Transferring you now."}
{"id": "t07", "user": "What's the rent for 2BR?", "assistant": "$2,400 for the 2BR."}
{"id": "t08", "user": "Human please", "assistant": "Would you like me to transfer you? ... Just to confirm, transfer you? ... Transferring."}
{"id": "t09", "user": "Do you have a gym?", "assistant": "Yes, the gym is open 5am-11pm. **Free** for residents."}
{"id": "t10", "user": "Can I speak to leasing?", "assistant": "Yes. Triggering handoff function now."}
{"id": "t11", "user": "Pet policy for dogs?", "assistant": "Dogs under 40 lbs, $400 deposit."}
{"id": "t12", "user": "Transfer me to maintenance", "assistant": "Transferring you now."}

第一次執行(先讀再選)的指令與提示:

$ cd ~/demo && claude -p "Read traces.jsonl (12 chat traces of a leasing voice assistant). Do NOT write any files. Step 1: read every trace and list each failure you see, one line per trace id, with a short free-form label. Step 2: count traces per label. Step 3: say which single failure to write an eval for first and why." --model sonnet --setting-sources project --strict-mcp-config --disable-slash-commands --allowedTools "Read"

旗標說明:--setting-sources project 只載入專案層設定,不載入個人 plugins、hooks、CLAUDE.md;--strict-mcp-config 不載入任何 MCP server;--disable-slash-commands 停用 skills 與斜線指令;--allowedTools "Read" 只放行讀檔。實測輸出(節錄,有刪減):

I read all 12 traces and wrote no files.

| t02 | leaks tool mechanics ("triggering the transfer tool") |
| t04 | markdown in a voice reply (`**Saturday**`, `- bullet`) |
| t08 | redundant confirmation loop on a handoff (...) |
| t09 | markdown in a voice reply (`**Free**`) |
| t10 | leaks tool mechanics ("Triggering handoff function") |

- Markdown in a voice reply: 2 (t04, t09)
- Leaks tool mechanics: 2 (t02, t10)
- Redundant handoff confirmation: 1 (t08)
- Clean: 7

The first two labels tie at 2. ...
**Step 3: write an eval for "leaks tool mechanics" first**
...
- **How to check it:** it can be a deterministic string check, with no LLM judge needed.

這份輸出有三個值得注意的地方。第一,它先逐筆列出,再計數,所以你可以對照 traces.jsonl 驗證每一行;第二,它只選一個失敗;第三,它自己判斷這個失敗可以用字串比對處理,不需要 LLM 裁判,正好符合上面「程式碼檢查優先」的原則。它也誠實地指出:「I can't verify the facts (prices, hours, policies) from the traces alone」,沒有硬判定事實對錯。

第二次執行(對照組:不要先看,直接寫一個涵蓋全部問題的 eval),提示是:

$ claude -p "Read traces.jsonl (12 chat traces of a leasing voice assistant). Do NOT write any files. Immediately propose one eval that covers everything wrong with this assistant, without listing per-trace findings first. Be brief." (旗標同上)
**Proposed eval: "Voice-safe, grounded, one-shot handoff"**

Run each trace's user turn through the assistant and have an LLM judge, with a few code checks, score the reply pass/fail on four criteria. A reply passes only if all four hold.
1. **Voice-safe output (code check)** ...
2. **Grounded facts (judge, with a reference KB)** ...
3. **Handoff behavior (trace check)** ...
4. **Action honesty (judge and tool-call check)** ...

兩次結果的差別很清楚。對照組產出一個「四合一」eval:只要任何一項不過,整筆就算失敗;它還引入了「參考知識庫」(KB)與「動作誠實」兩項,而這兩項在 12 筆 trace 裡根本沒有證據顯示是問題,是模型自己猜出來的。這正是 Hamel 批評的情況:多件事綁在一起,而且部分議題連資料都沒看過。相對地,先讀再選的流程,產出的是一個你能直接實作、直接驗證的小目標。

要把第一次的結論變成真正的 eval,下一步是請 agent 寫一個只檢查「回覆是否洩漏工具機制」的字串檢查,再回頭跑 traces.jsonl 確認 t02 與 t10 失敗、其他通過。這一步我沒有在本次測試中執行。

⚠️ 未實測:把 eval 寫成程式並回頭驗證 t02、t10 這一步(只測了「選哪個失敗」的判斷,沒有測 eval 程式本身)。

⚠️ 未實測:Anthropic 的 claude-api plugin 內的 build_eval 與 hill-climb 指令。安裝 plugin 會改動本機設定,不在本次測試的安全範圍內;關於該工具的描述均引自 Hamel 的文章,並非我親自操作。

測試環境:Claude Code 2.1.288,預設設定(不含個人 plugins、hooks、CLAUDE.md),測試日期 2026-10-05。

幾個實務建議,延續 Hamel 的觀察:

  • 人先標,AI 後擴大。本次實驗我讓 AI 直接讀完 12 筆,是因為資料很小、只為了展示流程。真實專案請依照 FAQ 的順序:自己先標前 30 筆,再讓 agent 去找類似案例。
  • 要求逐筆列出證據。提示裡寫「one line per trace id」,之後你才能抽查。沒有逐筆證據的總結,無法驗證。
  • 限定工具。分析階段只給 Read,可以避免 agent 在你還沒決定之前就開始產生檔案與程式碼。
  • 讀 judge 的提示。Hamel 的建議是「it always pays to read the prompt」。如果最後選了需要 LLM 裁判的失敗,裁判的 prompt 必須由你親自讀過。

4. 搬到其他工具

這個方法的核心是流程,不是某個產品。我查了其他工具的官方文件,結果如下:

  • OpenAI(Evaluation best practices):官方指南提到「Log everything: Log as you develop so you can mine your logs for good eval cases」,也就是先累積日誌、再從日誌挖 eval 案例,方向與本文一致。但我在該頁沒有找到「一個 eval 只管一種失敗」或明確的 error analysis 步驟(官方文件)。
  • OpenAI Codex CLI:我搜尋了官方文件,Codex 以 AGENTS.md 提供持續性指引,但在搜尋結果中沒有找到專門針對 evals 或 error analysis 的官方說明,因此這一項我沒有可引用的對應做法。你仍可用同樣的做法:把 trace 檔放進專案,請 Codex 逐筆讀並依序輸出標籤與計數(這是流程的移植,並非官方文件寫的功能)。
  • Cursor:我檢視了官方文件首頁,沒有看到 evals、error analysis 或檢視 trace 的相關說明(官方文件)。同樣地,方法本身只需要「能讀檔的 agent」,可直接移植。
  • 與工具無關的來源:Hamel 的 evals FAQ 對 open coding、axial coding、至少 100 筆 trace 與二元判定有完整說明(AI Evals: Everything You Need to Know)。

結論:這是個「方法大於工具」的主題。OpenAI 的官方指南有相近的精神(先累積日誌再挖案例),其他兩個工具的文件我沒找到對應說明。所以不要等任何工具內建這個流程,你只需要一個能讀檔的 agent 加上本文的提示結構,就能做到。

5. 限制與代價

  • 合成資料只能展示流程。本次的 12 筆 trace 是我編的,每種失敗各 1 到 2 筆,頻率並不真實。真實專案請至少看 100 筆,並看到不再出現新失敗為止。
  • 測試只做了兩次。LLM 輸出有隨機性,同一個提示再跑一次,選出的第一個失敗可能不同(本次兩個失敗並列為 2 筆,選擇「洩漏工具機制」有一部分是 agent 的判斷)。選哪個先做,仍然需要人決定。
  • AI 讀完不等於你讀完。Hamel 的核心立場是你自己必須看資料;agent 可以幫你找問題,但「which failures deserve attention」要由你決定。省掉這一步,你會得到看起來完整、實際上沒有根據的 eval。
  • 人工閱讀有成本。逐筆讀 100 筆以上的 trace 要花人力,這是這個方法最大的代價。如果資料量很少,可以先從 FAQ 建議的前 30 筆開始。
  • 二元判定不是萬能。有些品質面向(例如語氣是否親切)很難用 Pass/Fail 表達;這類問題需要更多領域判斷,本文沒有涵蓋。
  • 我沒有測試 Anthropic 的 eval 外掛。文中對該工具的描述全部來自 Hamel 的文章。Hamel 也提到作者表示會依回饋更新外掛,所以該文的細節可能很快過時。

6. 延伸閱讀


📌 本週短訊

Claude Code 更新

版本變更對讀者的影響
2.1.289修正 Read deny 規則對「透過 symlink 被 @ 提及、變更或在 IDE 選取的檔案」沒有生效的問題若你用 deny 規則保護敏感檔案,升級後 symlink 路徑也會被擋下
2.1.288修正 PreToolUse 與 PermissionRequest hooks 在比對失敗或工具輸入無法序列化成 JSON 時被略過的問題;現在這種情況會直接封鎖該次呼叫用 hooks 做防護的人,原本可能在特殊情況下被繞過;升級後預設改為封鎖
2.1.288修正 path-scoped .claude/rules 與巢狀 CLAUDE.md,在 Write 或 Edit 建立或修改其範圍內檔案時沒有載入的問題(過去只有 Read 會載入)寫檔時也會套用對應資料夾的規則,規則與實際行為更一致
2.1.288新增 --max-findings <n>|all 給 /code-review,可調整回報的發現數量;選擇會沿用到你傳入 --max-findings default 為止想要更完整的審查時,不必改提示詞就能放寬上限
2.1.288將 claude project purge 改名為 claude purge,舊名稱仍可使用並會顯示提示腳本不必立刻改,但之後建議改用新名稱
End of article
0
Would love your thoughts, please comment.x
()
x