AI agent 自建評測:建立可追蹤的測試流程
用真實 trace 建立 golden cases,先跑便宜的 deterministic checks,再交由 LLM judge 評分,並按每個 commit 追蹤通過率與回歸原因。
什麼是 AI agent 的自建評測
AI agent 的自建評測包含 4 個部分,全部保存在自己的 repository 中:保存案例的檔案、讓 agent 執行這些案例的 script、評分每個回答的一組檢查,以及可供查詢的結果表格。這些部分都不需要依賴任何 vendor。完整流程只需要幾百行 Python 和 1 個 SQLite 檔案。
示範能正常運作,是因為 5 個輸入都是你自行挑選的。到了第 2 週卻失效,可能是 prompt 中的一行文字、model 或 tool description 改變了,而沒有任何評測涵蓋這些變更。評測流程能把「現在感覺比較差」轉換成「在 commit 4f1c9ab 上,通過率從 60 個中的 58 個降至 60 個中的 51 個」。
這個流程有 4 個步驟,本指南每個步驟各占 1 個 section:收集實際 trace、將值得關注的 trace 納入案例、在每次變更時評分所有案例,以及將通過率與產生該結果的 commit 一起儲存。無論你讓 agent 執行什麼工作,都能使用相同流程;而值得執行的自建 agent framework主要差異在於它們會免費提供多少 trace 內容。
代理在第 2 週失效的原因
代理由提示、模型、工具定義,以及執行階段擷取的內容組成。這 4 項都能在不變更應用程式程式碼的情況下變動,因此一般程式碼審查不會發現任何異常。
最常見的原因是修改提示。你加入一句話來避免回覆失禮。這句話會改變未重新測試之輸入的行為,而追蹤記錄會清楚顯示差異:上週相同問題的追蹤記錄包含 create_refund 工具呼叫,本週則完全沒有,回覆反而變成禮貌的道歉。過程中沒有引發錯誤,因此也沒有觸發警示。
第二個原因是模型。每次執行都記錄實際傳送的完整模型字串,claude-haiku-4-5-20251001,不要依賴你記在腦中的簡寫。因為只有在該列包含模型名稱時,才能診斷切換模型當天通過率下降的原因。
第三個原因是工具。重新措辭工具說明會改變模型決定呼叫工具的時機。如果工具是透過 在 VPS 上執行的 MCP 伺服器提供,schema 位於另一個程序中,因此可能在你未修改儲存庫、也沒有任何差異記錄的情況下變更。第四個原因是檢索:相同問題命中一個在夜間重新建立的索引,而答案會依據新文件產生。
從既有追蹤資料建立基準測試集
不要自行編造評測案例,應直接從網路流量中取得。如果你已經為代理程式執行 自架 Langfuse 追蹤,每個請求都會儲存輸入、工具呼叫與輸出,這正是建立案例所需的原始資料。
透過公開 API 匯出一段時間範圍內的 root observations。此 API 使用基本驗證,公開金鑰作為使用者名稱,秘密金鑰作為密碼。
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'先讀取一筆記錄,再撰寫任何解析程式。資料列會放在 data 下,但問題與回覆的欄位名稱取決於代理程式如何為 spans 加入檢測資訊。因此,應根據實際讀到的內容進行對應,不要套用預期的欄位名稱。接著手動撰寫案例,每行一個 JSON 物件,放在 evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}以下 5 項規則可讓測試集維持實用:
- 一開始準備 40 到 80 個案例即可。少於 20 個時,單一不穩定案例就可能讓通過率變動 5 個百分點;沒有合理原因而大幅跳動的數字通常會被忽略。
- 每個修正的正式環境錯誤,都應在修正當天轉成一個案例。這個習慣能讓測試集朝正確方向成長。
- 每個案例只測試一種行為。若同一案例同時檢查退款金額與語氣,失敗時便無法判斷問題所在。
id永遠不變,因為系統會依據該 ID 比較本次執行結果與上個月的結果。- 提交前先遮蔽敏感資料。此檔案會提交至 git,因此請移除客戶名稱,以及任何不屬於你的訂單編號。
先執行確定性檢查,因為這些檢查不耗費額外成本
任何有明確正確答案的項目,都使用一般斷言。不需呼叫模型、不產生成本,也沒有歧義。確定性檢查能捕捉結構性退化,而這些問題會破壞代理程式周邊的系統:JSON 無法剖析、工具根本未被呼叫、禁用片語再次出現,或答案未引用任何來源。
只有一個函式需要知道你的代理程式。測試 harness 中的其他部分都採用通用設計。
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failures將工具呼叫次數上限保留在這份清單中。代理程式今天用 3 次呼叫解決案例,明天卻需要 11 次,即使最終答案正確,也代表系統已退化,因為每次呼叫都會產生成本。
LLM 作為評審,以及它出錯的 4 種方式
通過各項斷言的結果,仍需要能閱讀內容的評分器。LLM 評審是第二次模型呼叫:它會接收問題、代理程式的回答及一項評準,然後回傳判定。這是實際評估「回答是否解決使用者所提出的問題」的唯一可行方式。
以下 4 項規則能讓評審具備實用性:
- 使用二元判定,絕不要使用 1 到 10 的分數。評分尺度幾乎對所有內容都回傳 7 或 8,因此數字不會產生有意義的變化,你也無法從中學到任何資訊。
- 每次呼叫只使用一項評準。詢問退款金額或語氣,不要同時詢問兩者。
- 如果案例有預期答案,就提供給評審。依參考答案評分,比抽象地評分容易得多。
- 強制限定輸出格式,並嚴格剖析結果。
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)接下來是失效模式。每一項都有可在今天下午執行的測試。執行這些測試很重要,因為未經檢查的評審會產生看似精確、實際上毫無意義的數字。
長度偏誤。 較長的回答較常通過。測試方式:取 10 個被評審判定失敗的回答,為每個回答加入 2 個不含新事實、但語氣自信的段落,然後重新評分。任何因而從失敗變成通過的判定,都表示存在長度偏誤,而需要修正的是評分規則。
自我偏好。 評審通常對來自相同模型系列的輸出,比對其他模型系列的輸出更寬容。測試方式:使用 2 個不同模型系列的評審,為相同的 30 個回答評分,並逐一比較判定結果。若兩者不一致,請自行閱讀該案例。
位置偏誤。 如果使用評審比較 2 個回答 A 和 B,請交換順序後再次執行。如果判定結果因交換順序而改變,表示針對該評分規則,成對比較尚不可靠。
評分規則偏移。 模糊的評準會產生一味表示同意的評審。「回答是否有幫助」幾乎任何內容都會通過。「回答是否以美元說明退款金額」則只會讓符合要求的內容通過。請改寫每一項評準,直到其中明確指出要檢查的事實。
有一項防護措施能涵蓋以上 4 種問題。保留 30 個由人工標註的案例,並在每次變更評審模型或評審提示時,將評審結果與人工標註比較。如果每 10 個案例中有超過 1 個與你的判定不一致,請先修正評分規則,再信任它產生的任何通過率。評審也是程式碼,因此應像程式碼一樣進行版本控管與審查。
讓低價模型先評分,必要時再升級到頂尖模型
每次提交都用最昂貴的模型評估所有案例,會讓評估費用成長到超過受測 agent 本身。請依評分模型的價格排序,答案一旦明確就停止評估。
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]這些數字假設每次評分模型呼叫約使用 1,200 個輸入 token 和 120 個輸出 token。對於一個問題、一個答案及一項評準而言,這是合理的規模。在 Claude Haiku 4.5 上評估 1,000 個案例的費用為 1.80 美元,在 Claude Opus 5 上則為 9.00 美元。差距看似不大,但累積後就很可觀。一組 60 個案例若每次提交都進行評估,每週提交 40 次,則每週會產生 2,400 次評分模型呼叫,還沒計入每晚執行的工作。
評估工作可直接套用兩項折扣,而且可以同時使用。評估執行不是互動式工作,因此 Batch API 會將輸入與輸出價格都減半,以非同步方式提供結果;這是圖表中的第一列。每次呼叫使用的評分規準與指示逐位元組完全相同,因此適合使用 prompt caching:快取讀取費用是基本輸入價格的十分之一,5 分鐘快取寫入費用是基本輸入價格的 1.25 倍,因此快取只要命中一次就能回本。這些是截至 August 2026 的 Anthropic 牌價;Sonnet 5 的導入價格維持到 31 August 2026,因此第三個長條會在該日期後上升。
評估階梯如下,依序執行:
- 每個案例都執行決定性檢查。不產生 API 費用。
- 對通過這些檢查的案例使用小型模型評分。
- 只有在小型模型判定失敗,或判定通過但信心度低時,才使用頂尖模型評分。
- 每週一次,抽取少量案例進行人工審查。
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]這種方式會以部分評分準確度換取成本降低,因此應測量兩者的取捨,不要直接假設結果。每月一次,使用嚴格的評分模型評估整組案例,並比較兩欄結果。如果有超過少數案例的結果不一致,表示評分規準對小型模型而言不夠明確;應修正的是評分規準。控制 agent 本身的支出是另一項工作,請參閱 VPS 上 AI agent 的成本控制。
隨時間追蹤自有系統的通過率
無法對應至 commit 的通過率只是一種感覺。每次執行的每個案例各儲存一列,並將 commit 與模型一併寫入該列。
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;使用 sqlite3 evals/results.db < evals/schema.sql 載入 schema,再使用 sqlite3 -box evals/results.db < evals/passrate.sql 讀取趨勢。一年每天執行一次、每次 60 個案例,約會產生 22,000 列,因此儲存區不會反過來成為獨立專案。在 VPS 上於正式環境執行 SQLite 說明了檔案需要在多台機器之間共用時,開始需要注意的設定。
runner 也會為人員列印相同資訊:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amount在可能使 agent 失效的變更上執行測試套件,也就是 prompt 編輯、模型變更及工具變更,而不是對儲存庫中的每個 commit 都執行。pre-push hook 可涵蓋快速子集:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-push完整執行速度較慢,適合排程執行。每晚執行的 VPS 上的 systemd service 與 timer 會針對已部署的 prompt 執行完整集合。這能捕捉來自儲存庫外部的變更,例如託管工具的行為發生變化。
人工審查,抽樣而非全面檢查
評審器是以人工標註結果校準的,因此必須有人產生這些標註。每週閱讀一份樣本:評審器判定失敗的所有案例,再隨機挑選 10 個通過案例。隨機挑選的通過案例尤其重要,因為評審器若已悄悄開始放過不良答案,任何根據自身判定結果建立的儀表板看起來都會正常。
每週審查 15 個案例、每個 3 分鐘,共需 45 分鐘。這能找出你與評審器意見不一致時應修改的評分規範,也能為原先未設想到的失敗類型新增案例。將人工判定結果寫入同一張資料表,並將 graded_by 設為 human,如此一來,評審器與人工審查者是否一致就能透過查詢取得,不必依賴記憶。
評估測試工具本身的故障點
anthropic.RateLimitError 出現在第一次完整執行時。 同時分派 60 個案例會超過你所屬層級的請求數或 token 限制。將並行數限制為 4 個 worker,並將每晚執行工作移至 Batch API。
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 由 judge 傳回。 模型以散文格式回覆,或將 JSON 包在 code fence 中。重試 1 次,然後將該案例記錄為錯誤。解析失敗絕不能算為通過,因為會將錯誤轉為通過的測試套件,會在 agent 變差的同時逐步逼近 100%。
不穩定的案例。 相同輸入在一次執行中通過,下一次卻失敗,原因是 agent 會對輸出進行抽樣。將不穩定的案例執行 3 次,並記錄通過比例,不要刪除案例。某案例在 3 次執行中通過 2 次,代表確實存在穩健性問題,客戶也會發現。
Golden set 逐漸失真。 有人修改預期答案,讓測試套件顯示為綠燈。檢查 evals/cases.jsonl 的差異時,應與檢查 agent 的差異同樣仔細,因為該檔案是你對「正確」的書面定義。
永遠不會失敗的測試套件。 通過率連續 1 個月維持在 100%,表示測試集已停止反映產品狀態。擷取最近的 10 筆追蹤記錄,找出 agent 處理不佳的案例,並將它們加入測試集。接著刻意破壞某項功能,確認執行結果變成紅燈;這就是 mutation testing 適用於測試套件 的檢查方式,也是確認測試集仍具有效性的唯一方法。
FAQ
AI agent 評估集需要多少案例?
先從 40 到 80 個案例開始,再根據實際失敗案例擴充。少於約 20 個案例時,單一不穩定結果就會讓通過率變動 5 個百分點,因此這個數字不再具備參考價值。超過幾百個案例後,每次執行都會耗費實際的金錢與時間,而新增案例帶來的涵蓋率有限。真正重要的不是案例數量,而是已知的正式環境失敗類型中,有多少比例至少出現一次於評估集中。
我可以信任 LLM judge 為我的 agent 評分嗎?
只有在使用你自己的標註進行測量後,才能信任它。保留 30 個由人工評分的案例;每當更換 judge model 或 judge prompt 時,就使用這些案例評估 judge。Judge 可能出現長度偏誤,讓填充較多的答案更容易通過;也可能出現自我偏好,對來自相同模型家族的輸出給予較寬鬆的評分。這兩種偏誤都能測試:為失敗答案加入填充內容後重新評分,或使用不同模型家族的 judge 評分相同答案。如果 judge 在每 10 個案例中有超過 1 個案例與你的標註不一致,表示評分規範過於模糊,無法使用。
哪個 model 應該為 eval 評分?
先使用低成本模型,必要時再升級。確定性判斷不需成本,因此先對每個案例執行。小型模型負責處理結果明確通過的案例。只有失敗案例與低信心判定才交由 frontier model 處理。以 2026 年 8 月的牌價計算,使用 Claude Haiku 4.5 為 1,000 個案例評分,成本約為 1.80 美元;使用 Claude Opus 5 則約為 9.00 美元。由於 eval 執行採非同步方式,Batch API 會將上述任一成本降低一半。
eval 能取代正式環境監控嗎?
不能,因為兩者回答的是不同問題。Eval suite 可告訴你即將發布的變更,是否讓固定案例集的結果變好或變差。Tracing 與監控則告訴你目前實際使用者遇到的情況,包括沒有任何案例涵蓋的輸入。兩者會互相提供資料:traces 提供新案例,而 eval suite 判斷修正是否確實有效。