為什麼 CI 全綠,Δ 卻是負的?
因為退出碼只看 with-arm 的分數是否達到 --threshold;Δ 會被回報,但永遠不會改變退出碼。如果要在 Δ 退步時讓流程失敗,得用 --json 輸出結果,自己寫一段檢查去讀 with 與 without 的分數再判斷。另外別忘了預設門檻是 1.0,通常要依需求調低,否則一個不完美的 case 就會讓整個流程 exit 1。
我不是工程師,只負責看預算,跑一次評測大概會花多少?
文件沒有給固定金額,因為取決於你的 case 數量與 grader 種類。可以確定的結構是:一個 case 預設六次 agent run,再加上用 judge 的 grader 的額外呼叫,全部算在方案用量或 API 帳單上。實務上先用 --max-cost-usd 設一個上限,並請團隊在日常迭代時用單邊(--ablation none)與免費 grader,把完整對照留到發布前。
一個 Skill 通過測試,不代表它有用:Claude 沒有它,可能也答得一樣好。claude Plugin eval 的設計重點就在這裡——每個測試案例除了在載入 plugin 的情況下跑,還會再在「完全不載入 plugin」的情況下重跑一遍,兩邊的分數相減,得到的差值才是 plugin 實際貢獻的部分。這個指令需要 Claude Code v2.1.269 或更新版本;如果機器上有 git,要 2.31 以上。
評測集放在 plugin 裡的 evals/ 目錄,每個 case 是一個子目錄,裡面有 prompt.md(使用者會打的那句話,加上執行上限)和一或多個 graders/ 檔案。官方建議的起點是 claude plugin eval init:它會開一個互動式 session,讀你的 plugin、問你「好結果長什麼樣」、提出應該與不應該觸發 plugin 的 prompt、設計 grader 並先試跑一次,最後每個 prompt 寫成一個 case 目錄。想手寫的話,claude plugin eval init --bare first-case 會產生空白範本。官方文件的 prompt.md 範例(針對一個產生 commit message 的 Skill):
---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---
Write me a commit message for this change: I renamed getUser to fetchUser and updated the three call sites.
注意 prompt 要寫成使用者自然會打的話,而不是直接點名 skill。另一個 grader 用來確認答案確實是由你的 skill 產生的(把 your-skill-name 換成 skill 目錄名):
---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?your-skill-name"'
---
非確定性的 agent 跑一次說明不了什麼,所以每個 case 預設跑三次。一次 run 的分數是通過的 grader 比例(可加權),case 的分數是三次的平均;分數達到 --threshold(預設 1.0)才算通過。有基準線時會得到兩個分數:WITH(有載入 plugin)和 W/OUT(沒載入),兩者相減就是 Δ。官方文件寫得直接:如果一個 case 有沒有 plugin 都是 1.0,那 plugin 就不是讓它通過的原因。
grader 共六種:regex、tool_used、tool_order、file_exists 直接從 transcript 與檔案算出,不花錢;llm 與 baseline 會呼叫 judge 模型,llm 要三票中至少兩票 PASS。沒有自訂程式碼的 grader。另外,「skill 有被呼叫」這類 tool_used: Skill 的 grader 在沒有 plugin 時永遠不可能通過,所以兩邊對比時會被排除在分數之外(標記為 scored: false),只在 with-arm 當作通過/失敗的指示。
摘要表的每一列是 CASE WITH W/OUT Δ RUNS COST NOTES,同時會產生 report.html。官方文件說最常見的第一個發現是:Δ 接近零,同時 tool_used: Skill 的 grader 失敗,意思是 Claude 在自然說法下沒有選你的 skill——這時要調整 skill 的 description,重跑再比。另一個陷阱相反:Skill grader 通過、Δ 卻是負的,要先懷疑 judge,因為小的 judge 模型可能把格式不同的正確答案判錯,可以用 --judge-model sonnet 重跑,並把 rubric 寫成具體的 PASS/FAIL 條件。
想反覆調整單一 case,可以用 claude plugin eval . --case <case-name> --runs 1 --ablation none 只跑一邊一次;但官方也提醒單次 run 很吵,任何修改都要用預設三次確認。
一個 case 預設是三次有 plugin、三次沒 plugin,共六次 agent run,再加上每個 llm 或 baseline grader 每次 run 三個簡短的 judge 呼叫。這些都用你自己的憑證,算在方案用量或 API 帳單上,指令回報的費用是 list price 的估算。官方給的省錢建議是:每次改動就跑的快速測試只用不呼叫 judge 的 grader;不需要 Δ 時用 --ablation none,成本減半;--max-cost-usd 可設費用上限。我的看法(屬於建議,不是官方規範)是:日常迭代用單邊、免費 grader,只在發布前或換模型時跑完整的兩邊比較。
官方文件的 CI 範例如下:寫出 JSON 結果存檔、用 --trust-plugin 避免卡在信任提示、固定兩個模型讓分數可以跨時間比較、報告留在本地,並設費用上限:
claude plugin eval . \
--trust-plugin \
--json results.json \
--threshold 0.8 \
--model claude-sonnet-5 \
--judge-model claude-haiku-4-5 \
--no-publish \
--max-cost-usd 20
退出碼的意義是:0=每個 case 都達到門檻且所有 case 檔案都載入成功;1=有 case 低於門檻、case 檔案載入失敗、找不到 case、無法啟動 run、plugin 目錄未信任且沒給 --trust-plugin,或選項無效;2=部分完成(撞到費用上限,或憑證被拒);130=被中斷;143=被終止(例如 CI 逾時)。
有兩點特別容易誤會:第一,門檻只比 with-arm 的分數;第二,Δ 會被回報,但永遠不會改變退出碼。也就是說,CI 全綠不代表 plugin 還在貢獻價值。如果你要擋「Δ 退步」,得自己讀 --json 的輸出再判斷。另外預設門檻是 1.0,只要有任何一個 case 不完美就會 exit 1,通常需要依實際需求調低。CI 裡的 init 因為需要終端機提問,要改用 init --bare。
partial,看起來就像退步。先看 NOTES 欄或 JSON 裡的錯誤訊息再下結論。官方文件也提到,在對話中迭代單一 skill 的 skill-creator plugin 做的是類似的比較,但使用自己的 evals/evals.json 格式,兩個工具互相讀不了對方的 case 檔案。