Day 26 我們照實驗設計跑完四組 Gemini eval run。
資料收齊後,接著分析結果。
但這裡的分析不是只挑最高分的那一組,然後說它最好。
我們要回到這個系列一直在處理的問題:
Agent 的可靠性有沒有提升?
提升可靠性需要付出什麼代價?
剩下的失敗到底是 Agent 問題,還是 evaluator 問題?
Day 27 會一起看:
這篇的目標是:
用 Day 26 的實驗結果,整理出哪些改善值得保留,哪些問題應該留到下一階段處理。
分析分成幾個部分:
這次先不做:
分析時守住一個原則:
把現有結果解讀清楚。
Day 26 得到四組可比較的 Gemini run。
| experiment | run id | passed | failed | success rate | LLM calls | total latency |
|---|---|---|---|---|---|---|
| baseline / no retry | eval_run_20260924_184555 |
10 | 5 | 66.7% | 15 | 88262.05 ms |
| json_strict / no retry | eval_run_20260924_185011 |
13 | 2 | 86.7% | 15 | 80131.38 ms |
| baseline / retry | eval_run_20260924_185518 |
11 | 4 | 73.3% | 18 | 105118.41 ms |
| json_strict / retry | eval_run_20260924_185945 |
12 | 3 | 80.0% | 15 | 79742.16 ms |
先看表面數字,json_strict / no retry 是這次表現最好的設定。
它的成功率最高:
13 / 15 = 86.7%
而且它沒有增加 LLM calls:
Total LLM calls = 15
它沒有靠 retry 補救,第一次回答就比較符合格式要求。
先只看成功率。
| experiment | success rate |
|---|---|
| baseline / no retry | 66.7% |
| json_strict / no retry | 86.7% |
| baseline / retry | 73.3% |
| json_strict / retry | 80.0% |
這裡有幾個觀察。
第一,json_strict / no retry 比 baseline 高:
86.7% - 66.7% = +20.0%
Day 18 的 prompt versioning 除了記錄版本,也驗證了 json_strict 確實讓 JSON output 類型的任務更穩定。
第二,baseline / retry 也比 baseline 高:
73.3% - 66.7% = +6.6%
但提升幅度比 json_strict 小。
第三,json_strict / retry 這次反而低於 json_strict / no retry。
這不代表 retry 讓系統變差。
因為這一組的 Total LLM calls 仍然是 15,表示沒有真的觸發 retry。
差異主要來自 LLM 回覆本身的非確定性。
這裡只能下保守的結論:
單次 eval run 可以提供線索,但不能被當成永久真理。
接著不要只看 passed / failed。
要看失敗類型。
依照 Day 26 的輸出,可以整理成:
| experiment | wrong_answer | format_error | execution_error |
|---|---|---|---|
| baseline / no retry | 2 | 3 | 0 |
| json_strict / no retry | 2 | 0 | 0 |
| baseline / retry | 4 | 0 | 0 |
| json_strict / retry | 3 | 0 | 0 |
比起 success rate,這張表更直接地指出:
baseline 的主要問題之一是 JSON format_error。
baseline / no retry 失敗的 5 題裡,有 3 題是 JSON 格式錯誤:
case_013
case_014
case_015
但到了 json_strict / no retry,format_error 變成 0。
這表示 json_strict prompt 對 JSON output 任務有效。
同樣地,baseline / retry 也把 format_error 變成 0。
只是它是透過多一次 LLM call 修正。
現在可以比較兩種修正 JSON format error 的方式。
json_strict / no retry 的結果:
format_error = 0
LLM calls = 15
它的意思是:
靠更明確的輸出規則,讓模型第一次就產生合法 JSON。
baseline / retry 的結果:
format_error = 0
LLM calls = 18
它的意思是:
第一次錯了沒關係,再送一次修正 prompt。
兩者都能修 format_error。
但代價不同。
| 方法 | format_error | LLM calls | 說明 |
|---|---|---|---|
| json_strict | 0 | 15 | 事前降低錯誤 |
| baseline + retry | 0 | 18 | 事後補救錯誤 |
這次實驗裡,prompt 改善比 retry 更划算。
因為它同時做到:
更高成功率
沒有增加 LLM calls
總 latency 也沒有變高
retry 不是沒用。
從 baseline / retry 可以看到:
Total LLM calls: 18
而 baseline / no retry 是:
Total LLM calls: 15
所以 retry 多了:
18 - 15 = 3 calls
這 3 次剛好對應 JSON 題目:
case_013
case_014
case_015
retry 的效果是:
format_error 從 3 變成 0
但它的代價是:
LLM calls 增加 20%
latency 從 88262.05 ms 增加到 105118.41 ms
換算平均每個 case latency:
| experiment | avg latency |
|---|---|
| baseline / no retry | 5884.14 ms |
| baseline / retry | 7007.89 ms |
多了大約:
1123.75 ms / case
所以 retry 的定位應該是:
當 prompt 還沒辦法穩定避免格式錯誤時,retry 是有用的補救機制。
但它不應該取代 prompt 改善。
直覺上可能會期待:
json_strict + retry
應該是最強組合。
但這次結果不是。
| experiment | passed | LLM calls |
|---|---|---|
| json_strict / no retry | 13 | 15 |
| json_strict / retry | 12 | 15 |
先看 LLM call 數量:
json_strict / retry 的 Total LLM calls 還是 15。
retry 實際上沒有發生。
既然沒有 retry,兩組差異只能歸因於 LLM 回覆本身的波動。
例如某次 run 裡,case_010 可能剛好包含關鍵字「可靠」。
另一次 run 裡,模型可能改用「穩定、安全、準確」這類語意接近但不包含指定關鍵字的說法。
對人來說,這可能是合理回答。
對目前的 rule-based evaluator 來說,這就是 failed。
這個現象也說明:
評測結果不只反映 Agent 能力,也反映 evaluator 的設計。
四組實驗的 format_error 都可以被 json_strict 或 retry 解掉。
剩下比較麻煩的是 wrong_answer。
以 json_strict / no retry 為例,失敗的是:
case_008
case_009
其中 case_008 期待輸出包含:
任務
但模型回答:
AI Agent 是一種能夠自主感知環境、進行思考決策,並採取行動以完成特定目標的智慧代理系統。
這句話其實語意上接近正確。
只是它用了「目標」而不是「任務」。
case_009 期待輸出包含:
紀錄
但模型回答:
Trace 是指記錄與追蹤一個請求在系統中穿梭於各元件間的完整執行路徑與歷程,用以分析系統效能並排查錯誤。
這裡其實有一個很細的問題。
如果 evaluator 期待的是「紀錄」,但模型輸出的是「記錄」,對人來說幾乎等價。
但對 simple keyword contains 來說,它們是不同字串。
所以這類失敗不能直接解讀成:
Agent 不懂 Trace。
更精準的說法是:
目前 evaluator 對同義詞、近義詞和繁簡用字差異不夠寬容。
baseline / retry 裡還有一個很適合拿來討論的案例。
case_001 期待:
3780
模型輸出:
135 * 28 = **3,780**
它數學上是對的。
但 evaluator 使用 contains 檢查:
Expected output to contain '3780'
模型輸出裡是:
3,780
中間多了一個逗號。
所以被判定 failed。
這不是 Agent 算錯。
這是 evaluator 太字面。
這個例子很適合說明 rule-based evaluator 的優缺點。
優點是:
缺點是:
目前我們是手動整理表格。
但如果 eval run 變多,手動整理很容易出錯。
這裡可以新增一個小工具,用固定 run id 產生 summary。
新增 evals/analyze_final_runs.py:
import json
from pathlib import Path
EVAL_RUNS_DIR = Path("data/eval_runs")
FINAL_RUNS = [
{
"experiment": "baseline / no retry",
"run_id": "eval_run_20260924_184555",
},
{
"experiment": "json_strict / no retry",
"run_id": "eval_run_20260924_185011",
},
{
"experiment": "baseline / retry",
"run_id": "eval_run_20260924_185518",
},
{
"experiment": "json_strict / retry",
"run_id": "eval_run_20260924_185945",
},
]
def load_eval_run(run_id: str) -> dict:
path = EVAL_RUNS_DIR / f"{run_id}.json"
with path.open("r", encoding="utf-8") as file:
return json.load(file)
def count_failure_types(results: list[dict]) -> dict[str, int]:
counts: dict[str, int] = {}
for result in results:
if result.get("passed"):
continue
failure_type = result.get("failure_type") or "unknown"
counts[failure_type] = counts.get(failure_type, 0) + 1
return counts
def summarize_run(label: str, eval_run: dict) -> dict:
results = eval_run.get("results", [])
total_cases = eval_run.get("total_cases", len(results))
passed = sum(1 for result in results if result.get("passed"))
failed = total_cases - passed
success_rate = passed / total_cases if total_cases else 0
total_latency_ms = eval_run.get("total_latency_ms", 0)
avg_latency_ms = total_latency_ms / total_cases if total_cases else 0
return {
"experiment": label,
"run_id": eval_run.get("run_id"),
"prompt_version": eval_run.get("prompt_version"),
"retry_enabled": eval_run.get("retry_enabled"),
"passed": passed,
"failed": failed,
"success_rate": success_rate,
"failure_types": count_failure_types(results),
"llm_calls": eval_run.get("total_llm_call_count", 0),
"total_tokens": eval_run.get("total_tokens", 0),
"total_latency_ms": total_latency_ms,
"avg_latency_ms": avg_latency_ms,
"estimated_cost": eval_run.get("total_estimated_cost", 0.0),
}
def main() -> None:
summaries = []
for item in FINAL_RUNS:
eval_run = load_eval_run(item["run_id"])
summaries.append(summarize_run(item["experiment"], eval_run))
for summary in summaries:
print(f"Experiment: {summary['experiment']}")
print(f" run_id: {summary['run_id']}")
print(f" prompt_version: {summary['prompt_version']}")
print(f" retry_enabled: {summary['retry_enabled']}")
print(f" passed: {summary['passed']}")
print(f" failed: {summary['failed']}")
print(f" success_rate: {summary['success_rate']:.1%}")
print(f" failure_types: {summary['failure_types']}")
print(f" llm_calls: {summary['llm_calls']}")
print(f" total_tokens: {summary['total_tokens']}")
print(f" total_latency_ms: {summary['total_latency_ms']:.2f}")
print(f" avg_latency_ms: {summary['avg_latency_ms']:.2f}")
print(f" estimated_cost: {summary['estimated_cost']:.8f}")
print()
if __name__ == "__main__":
main()
執行:
python3 -m evals.analyze_final_runs
這個腳本會印出每組實驗的 summary。
它沒有取代 dashboard。
它只是讓 Day 27 的分析過程可以重現。
綜合來看,這次最值得保留的設定是:
PROMPT_VERSION=json_strict
RETRY_ENABLED=0
理由如下。
第一,它的 success rate 最高:
86.7%
第二,它把 JSON format_error 降到 0。
第三,它沒有增加 LLM calls。
它是在錯誤發生前改善輸出,不是等錯誤出現後再補救。
retry 則應該保留,但不一定預設開啟。
比較合理的定位是:
當 prompt 改善後仍偶爾出現格式錯誤,再開 retry 作為備援。
而不是一開始就靠 retry 撐成功率。
這次實驗後,主要剩下兩類問題。
例如:
3780 vs 3,780
紀錄 vs 記錄
任務 vs 目標
可靠 vs 穩定、安全、準確
這些都不是單純 prompt 能完全解決的問題。
可以考慮幾個改善方向:
但這些都不應該在 Day 27 直接硬塞進去。
它們比較適合放到 Day 29 的限制與未來工作。
目前只有 15 題。
這對 MVP 很適合。
但如果要做更穩定的結論,應該擴充到更多題目,並讓每個 task type 有更多樣本。
例如:
| task type | 目前問題 |
|---|---|
| calculation | 題目太少,且格式差異會影響評分 |
| general_qa | keyword evaluator 太嚴格 |
| json_output | 適合 schema validation,但樣本仍少 |
| instruction_following | 目前只測簡單 exact match |
所以這次結論要保守。
我們可以說:
在目前這組 eval dataset 上,json_strict 是最有效的改善。
不要說:
json_strict 對所有 Agent 任務都最好。
Day 26 的四組 final experiment 分析完成。
得到幾個結論:
json_strict / no retry 在這次實驗中表現最好。json_strict 把 JSON format_error 從 3 降到 0。baseline + retry 也能修正 format_error,但多了 3 次 LLM call。這次實驗最務實的建議是:
預設使用 json_strict prompt。
保留 retry 作為格式錯誤的備援。
接下來改善 evaluator,而不是繼續只調 prompt。
Day 28 會開始整理整個專案。
到目前為止,我們已經有:
接下來要把這些整理成一個可以展示的專案。
Day 28 會補 README、專案架構說明、執行方式,以及系統流程圖。