Day 24 我們把 Failure Dashboard 擴充成 Reliability Dashboard。
現在平台已經可以看:
success rate
failure type
latency
LLM call count
token usage
estimated cost
prompt version
retry count
觀察工具準備得差不多了,接著可以做比較實驗。
但在真的跑實驗之前,要先把實驗設計固定下來。
否則很容易出現一種狀況:
今天跑 baseline
明天改 prompt
後天開 retry
中間又改了 test cases
最後拿這些結果互相比較
這樣看起來有數據,但其實比較基準不乾淨。
Day 25 先不急著跑結果,而是:
固定 final experiment 的比較條件、指標、執行順序和記錄方式。
這次會完成:
evals/final_experiment_plan.json。evals/print_experiment_commands.py,把實驗指令列出來。這一篇先不做:
這次先把實驗規格寫清楚。
Day 26 才會照這份規格執行。
這個系列前面做了很多改善:
但最後真正想回答的不是:
我們做了哪些功能?
而是:
這些功能對 Agent 的可靠性有沒有幫助?
如果有,代價是多少?
所以 final experiment 主要回答三個問題:
| 問題 | 對應指標 |
|---|---|
| prompt 改善是否提升成功率? | success rate、failure type |
| retry 是否讓格式錯誤變少? | retry count、format_error 數量 |
| 可靠性提升是否增加代價? | latency、LLM calls、tokens、estimated cost |
Day 20 的 JSON schema validation 在 final experiment 裡會被視為固定啟用的品質門檻。
這裡不是比較:
schema validation off vs on
而是固定使用 schema validation 來判斷 JSON output 是否真的合格。
原因是目前的系統已經把 schema guardrail 放進 evaluator 流程。
如果為了實驗再把它關掉,反而會讓 Day 20 之後的評測標準變不一致。
實驗開始前,第一件事是固定測試資料。
如果 baseline 跑的是 15 題,json_strict 跑的是 16 題,那兩個結果就不能直接比較。
因此 Day 25 先決定:
final experiment 使用目前 evals/cases.json 的固定版本。
在真正執行 Day 26 之前,不要再新增、刪除或修改測試案例。
如果需要調整 test cases,要先調整完,再從頭重跑所有實驗組。
建議在文章或 README 裡記錄:
Dataset: evals/cases.json
Total cases: 15
Task types:
- calculation
- keyword_qa
- general_qa
- instruction_following
- json_output
如果你的本機 cases.json 數量不同,就以你當下固定的版本為準。
題數不必限定為 15,真正要守住的是:
每一組實驗都必須使用同一份 dataset。
這次 final experiment 先控制三個主要變因。
目前有兩種:
| provider | 用途 |
|---|---|
fake |
驗證本機流程、欄位與 dashboard |
gemini |
觀察真實 LLM 表現 |
fake 不適合用來判斷 Agent 能力。
它的用途是確認:
真正要看回答品質,還是要用 gemini。
目前有兩個版本:
| prompt version | 說明 |
|---|---|
baseline |
原始 prompt |
json_strict |
對 JSON 輸出要求更嚴格的 prompt |
這個變因主要觀察:
prompt 是否能降低 format_error?
prompt 是否會影響其他 task type?
目前 retry 用環境變數控制:
| RETRY_ENABLED | 說明 |
|---|---|
0 |
不 retry |
1 |
JSON format error 時 retry 一次 |
這個變因主要觀察:
retry 是否能救回 JSON 格式錯誤?
retry 是否增加 latency、LLM calls 和 estimated cost?
除了變因之外,其他條件要盡量固定。
這次固定:
| 條件 | 固定方式 |
|---|---|
| dataset | 使用同一份 evals/cases.json |
| schema validation | 固定啟用 |
| tool guardrail | 固定依照 test case 的 allowed_tools |
| evaluator | 使用同一份 evals/evaluators.py |
| cost estimation | 使用同一組環境變數 |
| delay | Gemini run 使用同一個 EVAL_DELAY_SECONDS |
如果你要設定示範 cost,可以固定使用:
INPUT_COST_PER_1K_TOKENS=0.0001
OUTPUT_COST_PER_1K_TOKENS=0.0004
再次提醒,這只是示範價格。
正式寫文章時要稱為:
estimated cost
不要稱為實際帳單。
接著把實驗組合列出來。
這裡先分成兩層:
Local validation runs 用 fake provider,目標是確認流程能跑。
Gemini final runs 才是要拿來觀察真實 LLM 表現。
| ID | provider | prompt | retry | 用途 |
|---|---|---|---|---|
local_baseline_no_retry |
fake | baseline | off | 驗證基本 eval 流程 |
local_baseline_retry |
fake | baseline | on | 驗證 retry metrics |
| ID | provider | prompt | retry | 用途 |
|---|---|---|---|---|
gemini_baseline_no_retry |
gemini | baseline | off | 真實 baseline |
gemini_json_strict_no_retry |
gemini | json_strict | off | 測 prompt 改善 |
gemini_baseline_retry |
gemini | baseline | on | 測 retry 效果 |
gemini_json_strict_retry |
gemini | json_strict | on | 測 prompt + retry |
如果 Gemini quota 很緊,可以先跑前三組:
gemini_baseline_no_retry
gemini_json_strict_no_retry
gemini_baseline_retry
第四組 gemini_json_strict_retry 可以列為 optional。
原因是如果 json_strict 已經讓 JSON 題目大多通過,retry 的增益可能有限。
但它仍然能幫我們確認:
prompt 已經改善後,retry 還值不值得開?
先新增一個設定檔,把上面的實驗矩陣固定下來。
新增 evals/final_experiment_plan.json:
{
"dataset": "evals/cases.json",
"schema_guardrail": "enabled",
"tool_guardrail": "enabled",
"metrics": [
"success_rate",
"failure_type",
"latency_ms",
"llm_call_count",
"total_tokens",
"estimated_cost",
"retry_count"
],
"experiments": [
{
"id": "local_baseline_no_retry",
"provider": "fake",
"prompt_version": "baseline",
"retry_enabled": false,
"required_for_report": false,
"purpose": "Validate local eval pipeline without retry."
},
{
"id": "local_baseline_retry",
"provider": "fake",
"prompt_version": "baseline",
"retry_enabled": true,
"required_for_report": false,
"purpose": "Validate retry metrics and extra LLM call count."
},
{
"id": "gemini_baseline_no_retry",
"provider": "gemini",
"prompt_version": "baseline",
"retry_enabled": false,
"required_for_report": true,
"purpose": "Measure real baseline behavior."
},
{
"id": "gemini_json_strict_no_retry",
"provider": "gemini",
"prompt_version": "json_strict",
"retry_enabled": false,
"required_for_report": true,
"purpose": "Measure prompt-only improvement."
},
{
"id": "gemini_baseline_retry",
"provider": "gemini",
"prompt_version": "baseline",
"retry_enabled": true,
"required_for_report": true,
"purpose": "Measure retry improvement and cost."
},
{
"id": "gemini_json_strict_retry",
"provider": "gemini",
"prompt_version": "json_strict",
"retry_enabled": true,
"required_for_report": false,
"purpose": "Measure prompt plus retry as an optional final run."
}
]
}
這個檔案暫時不會改變 runner 的行為。
它的用途是把實驗規格明文化。
Day 26 執行時,就照這份 plan 逐組跑。
為了避免 Day 26 手動打錯環境變數,今天可以先新增一個小工具,把實驗指令印出來。
新增 evals/print_experiment_commands.py:
import json
from pathlib import Path
PLAN_PATH = Path("evals/final_experiment_plan.json")
def bool_to_env(value: bool) -> str:
return "1" if value else "0"
def build_command(experiment: dict) -> str:
return " ".join(
[
f"RETRY_ENABLED={bool_to_env(experiment['retry_enabled'])}",
f"PROMPT_VERSION={experiment['prompt_version']}",
f"LLM_PROVIDER={experiment['provider']}",
"python3 -m evals.runner",
]
)
def main() -> None:
with PLAN_PATH.open("r", encoding="utf-8") as file:
plan = json.load(file)
for experiment in plan["experiments"]:
print(f"# {experiment['id']}")
print(build_command(experiment))
print()
if __name__ == "__main__":
main()
這個工具很簡單。
它只做三件事:
evals/final_experiment_plan.json。retry_enabled 轉成 RETRY_ENABLED=0 或 RETRY_ENABLED=1。執行:
python3 -m evals.print_experiment_commands
會得到類似:
# local_baseline_no_retry
RETRY_ENABLED=0 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
# local_baseline_retry
RETRY_ENABLED=1 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
# gemini_baseline_no_retry
RETRY_ENABLED=0 PROMPT_VERSION=baseline LLM_PROVIDER=gemini python3 -m evals.runner
這一版只列印,不自動執行。
原因是 Gemini 有 quota 和 429 問題。
我們希望 Day 26 執行時可以人工控制節奏。
前面實作時已經遇過 Gemini 429。
這會影響 final experiment。
如果某一組實驗只跑到前幾題,後面全部 429,那這份結果就不適合拿來和其他 run 比較成功率。
因此先定義幾個規則。
在跑 Gemini 前,先跑:
RETRY_ENABLED=0 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
以及:
RETRY_ENABLED=1 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
這兩組不是為了得到高成功率。
它們只是確認:
如果你已經加入 Day 23 前後討論過的 EVAL_DELAY_SECONDS,Gemini run 建議加上:
EVAL_DELAY_SECONDS=10
例如:
EVAL_DELAY_SECONDS=10 \
RETRY_ENABLED=0 \
PROMPT_VERSION=baseline \
LLM_PROVIDER=gemini \
python3 -m evals.runner
如果還是 429,就把間隔拉長。
例如:
EVAL_DELAY_SECONDS=30
這不會讓實驗更精準,但可以降低短時間連續請求造成的失敗。
如果某次 run 有大量:
failure_type: execution_error
error: 429
那它應該被標記成:
invalid run for final comparison
可以保留在 data/eval_runs/ 裡,但不要把它拿來當結論。
Day 27 分析時,可以另外討論:
API quota 本身也是 production reliability 的一部分。
但不要把 429 當成 prompt 品質差。
Day 25 先定義一個簡單標準。
一個 run 要拿來做 final comparison,至少要符合:
| 條件 | 說明 |
|---|---|
| total cases 相同 | 每組都跑同一份 dataset |
| 沒有大量 execution_error | 避免 API 狀態污染結果 |
| llm_provider 符合實驗設定 | 不要 fake 和 Gemini 混在同一組 |
| prompt_version 正確 | 確認環境變數沒有打錯 |
| retry_enabled 正確 | 確認是否開 retry |
如果某組 run 不符合,就重跑那一組。
不要只重跑失敗的後半段,除非 runner 已經支援 resume。
目前 MVP 還沒有 resume failed cases,所以最乾淨的方式是整組重跑。
Day 26 建議照這個順序執行。
第一步,列出指令:
python3 -m evals.print_experiment_commands
第二步,先跑 local validation:
RETRY_ENABLED=0 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
RETRY_ENABLED=1 PROMPT_VERSION=baseline LLM_PROVIDER=fake python3 -m evals.runner
第三步,再跑 Gemini baseline:
EVAL_DELAY_SECONDS=10 \
RETRY_ENABLED=0 \
PROMPT_VERSION=baseline \
LLM_PROVIDER=gemini \
python3 -m evals.runner
第四步,跑 Gemini prompt 改善版:
EVAL_DELAY_SECONDS=10 \
RETRY_ENABLED=0 \
PROMPT_VERSION=json_strict \
LLM_PROVIDER=gemini \
python3 -m evals.runner
第五步,跑 Gemini retry 版:
EVAL_DELAY_SECONDS=10 \
RETRY_ENABLED=1 \
PROMPT_VERSION=baseline \
LLM_PROVIDER=gemini \
python3 -m evals.runner
第六步,如果 quota 允許,再跑 prompt + retry:
EVAL_DELAY_SECONDS=10 \
RETRY_ENABLED=1 \
PROMPT_VERSION=json_strict \
LLM_PROVIDER=gemini \
python3 -m evals.runner
這個順序的原因是:
先確認本機流程
再建立真實 baseline
再看 prompt 改善
再看 retry 代價
最後才看 prompt + retry 是否值得
每次 evals.runner 跑完都會產生:
data/eval_runs/eval_run_YYYYMMDD_HHMMSS.json
Day 26 執行時,建議手動記錄一張表。
例如:
| experiment id | run id | usable? | note |
|---|---|---|---|
local_baseline_no_retry |
eval_run_... |
yes | local validation |
local_baseline_retry |
eval_run_... |
yes | retry count checked |
gemini_baseline_no_retry |
eval_run_... |
yes | no 429 |
gemini_json_strict_no_retry |
eval_run_... |
yes | no 429 |
gemini_baseline_retry |
eval_run_... |
no | many 429 |
這張表很重要。
因為 data/eval_runs/ 裡可能有很多舊資料。
如果不記錄哪些 run 是 final experiment 的一部分,Day 27 分析時很容易混到測試資料。
目前先手動記錄即可。
Day 28 或延伸功能可以再把 experiment_id 寫進 eval run metadata。
這一篇沒有急著跑模型,而是先固定 final experiment 的規格。
完成內容包含:
evals/final_experiment_plan.json。evals/print_experiment_commands.py。這份設計要守住一個原則:
沒有固定實驗設計,就沒有可信的比較結果。
Dashboard 能幫我們看資料。
但實驗設計決定這些資料能不能被公平比較。
Day 26 會照今天的 experiment plan 執行最終實驗。
重點不是馬上解讀哪個方法最好。
Day 26 只做一件事:
把實驗資料乾淨地跑出來,並記錄哪些 run 可以拿來比較。
真正的分析和結論會留到 Day 27。