iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0

前言

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 的比較條件、指標、執行順序和記錄方式。


這篇要完成什麼?

這次會完成:

  1. 定義 final experiment 要回答的問題。
  2. 固定 eval dataset。
  3. 定義實驗變因。
  4. 定義不變條件。
  5. 設計 experiment matrix。
  6. 建立 evals/final_experiment_plan.json。
  7. 建立 evals/print_experiment_commands.py,把實驗指令列出來。
  8. 說明 Gemini 429 時如何保留實驗有效性。
  9. 定義 Day 26 要怎麼執行。

這一篇先不做:

  • 不大量呼叫 Gemini。
  • 不分析實驗結果。
  • 不改 dashboard。
  • 不新增複雜統計方法。
  • 不做 LLM-as-a-Judge。

這次先把實驗規格寫清楚。

Day 26 才會照這份規格執行。


final experiment 要回答什麼問題?

這個系列前面做了很多改善:

  • Day 18:prompt versioning。
  • Day 19:retry。
  • Day 20:JSON schema guardrail。
  • Day 22:tool guardrail。
  • Day 23:成本與延遲紀錄。
  • Day 24:Reliability Dashboard。

但最後真正想回答的不是:

我們做了哪些功能?

而是:

這些功能對 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 之後的評測標準變不一致。


固定 eval dataset

實驗開始前,第一件事是固定測試資料。

如果 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 先控制三個主要變因。

1. LLM Provider

目前有兩種:

provider 用途
fake 驗證本機流程、欄位與 dashboard
gemini 觀察真實 LLM 表現

fake 不適合用來判斷 Agent 能力。

它的用途是確認:

  • eval runner 能跑完。
  • retry 欄位有被記錄。
  • cost / latency 欄位有產生。
  • dashboard 可以讀到資料。

真正要看回答品質,還是要用 gemini。

2. Prompt Version

目前有兩個版本:

prompt version 說明
baseline 原始 prompt
json_strict 對 JSON 輸出要求更嚴格的 prompt

這個變因主要觀察:

prompt 是否能降低 format_error?
prompt 是否會影響其他 task type?

3. Retry Enabled

目前 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

不要稱為實際帳單。


設計 experiment matrix

接著把實驗組合列出來。

這裡先分成兩層:

  1. Local validation runs。
  2. Gemini final runs。

Local validation runs 用 fake provider,目標是確認流程能跑。

Gemini final runs 才是要拿來觀察真實 LLM 表現。

Local validation runs

ID provider prompt retry 用途
local_baseline_no_retry fake baseline off 驗證基本 eval 流程
local_baseline_retry fake baseline on 驗證 retry metrics

Gemini final runs

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 還值不值得開?

建立 final experiment plan

先新增一個設定檔,把上面的實驗矩陣固定下來。

新增 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()

這個工具很簡單。

它只做三件事:

  1. 讀取 evals/final_experiment_plan.json。
  2. 把 retry_enabled 轉成 RETRY_ENABLED=0 或 RETRY_ENABLED=1。
  3. 印出 Day 26 要執行的指令。

執行:

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 時怎麼設計實驗?

前面實作時已經遇過 Gemini 429。

這會影響 final experiment。

如果某一組實驗只跑到前幾題,後面全部 429,那這份結果就不適合拿來和其他 run 比較成功率。

因此先定義幾個規則。

規則 1:先跑 fake validation

在跑 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

這兩組不是為了得到高成功率。

它們只是確認:

  • runner 沒壞。
  • retry 分支能跑。
  • metrics 欄位有產生。
  • dashboard 能讀取。

規則 2:Gemini run 加上 case 間隔

如果你已經加入 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

這不會讓實驗更精準,但可以降低短時間連續請求造成的失敗。

規則 3:429 run 不拿來做正式比較

如果某次 run 有大量:

failure_type: execution_error
error: 429

那它應該被標記成:

invalid run for final comparison

可以保留在 data/eval_runs/ 裡,但不要把它拿來當結論。

Day 27 分析時,可以另外討論:

API quota 本身也是 production reliability 的一部分。

但不要把 429 當成 prompt 品質差。


如何判斷某個 run 是否有效?

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 的執行順序

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 是否值得

Day 26 要記錄哪些 run id?

每次 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 的規格。

完成內容包含:

  • 定義 final experiment 要回答的問題。
  • 固定 dataset。
  • 定義 provider、prompt version、retry 三個主要變因。
  • 把 schema validation 定義成固定啟用的品質門檻。
  • 建立 experiment matrix。
  • 新增 evals/final_experiment_plan.json。
  • 新增 evals/print_experiment_commands.py。
  • 定義 Gemini 429 時的處理規則。
  • 定義 Day 26 的執行順序。

這份設計要守住一個原則:

沒有固定實驗設計,就沒有可信的比較結果。

Dashboard 能幫我們看資料。

但實驗設計決定這些資料能不能被公平比較。


下一步

Day 26 會照今天的 experiment plan 執行最終實驗。

重點不是馬上解讀哪個方法最好。

Day 26 只做一件事:

把實驗資料乾淨地跑出來,並記錄哪些 run 可以拿來比較。

真正的分析和結論會留到 Day 27。


上一篇
Day 24|建立 Reliability Dashboard
系列文
從黑盒到可驗證:30 天打造 AI Agent 的 Trace、Eval 與 Guardrails 系統 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言