Day 8 我們先定義了 Agent Evaluation 的概念。
Evaluation 的主要想法是:
不要只手動測幾題,而是用固定測試集重複測量 Agent 表現。
但在真的開始批次測試之前,我們需要先回答一個很基本的問題:
一筆測試案例應該長什麼樣子?
如果 test case 格式沒有先設計好,後面會很容易混亂。
例如有些任務是精確答案,有些任務只要包含關鍵字,有些任務要求 JSON 格式,有些任務需要使用工具。這些任務的評分方式不一樣,所以不能只存一個 input。
Day 9 就先設計第一版 test case 格式,並建立前 5 筆測試案例。
會完成:
id。input。expected。grading_method。task_type。今天先不做:
今天只先把測試資料格式定下來。Day 10 會再把 5 筆案例擴充成第一組完整 eval dataset。
Evaluation 不是只有「把問題丟給 Agent」而已。
如果我們只準備這樣的資料:
{
"input": "請計算 135 * 28"
}
那系統其實不知道該怎麼判斷 Agent 是否成功。
它不知道:
所以一筆好的 test case 至少要包含「任務」和「驗證方式」。
也就是:
input:要 Agent 做什麼
expected:期待看到什麼結果
grading_method:要怎麼判斷是否通過
task_type:這是哪一類任務
有了這些欄位,後面才能實作 evaluator。
今天新增 evals/ 資料夾,用來放 evaluation 相關檔案。
agent-testing-platform/
app.py
trace_viewer_app.py
agents/
__init__.py
simple_agent.py
prompts.py
fake_llm.py
tools/
__init__.py
calculator.py
tracing/
__init__.py
models.py
storage/
__init__.py
database.py
schema.sql
ui/
__init__.py
trace_viewer.py
evals/
__init__.py
cases.json
data/
agent_traces.db
新增:
| 檔案 | 用途 |
|---|---|
evals/__init__.py |
讓 evals 成為 Python package |
evals/cases.json |
存放 eval test cases |
今天先採用這個最小格式:
{
"id": "case_001",
"input": "請計算 135 * 28",
"expected": "3780",
"grading_method": "contains",
"task_type": "calculation"
}
這個 schema 不複雜,但已經足夠支援第二週的基本評測。
欄位說明如下。
| 欄位 | 說明 |
|---|---|
id |
測試案例的唯一編號 |
input |
要交給 Agent 的任務 |
expected |
預期答案或預期條件 |
grading_method |
評分方式 |
task_type |
任務類型 |
接著逐一說明這些欄位。
id 是每一筆 test case 的唯一編號。
例如:
{
"id": "case_001"
}
它的用途是讓後面的 evaluation result 可以對應回原本的測試案例。
未來一筆結果可能會長這樣:
{
"case_id": "case_001",
"passed": true,
"actual": "The result is 3780"
}
如果沒有 id,當測試案例變多時,就很難追蹤是哪一題失敗。
input 是要交給 Agent 的任務。
例如:
{
"input": "請計算 135 * 28"
}
它會在後面的 Batch Evaluation Runner 中被傳進:
agent.run(test_case["input"])
也就是說,input 就是使用者平常會輸入給 Agent 的內容。
設計 input 時要注意一件事:任務描述要足夠明確。
例如這樣比較好:
請計算 135 * 28
這樣比較不穩:
算一下這個
Evaluation 的目標是測 Agent,不是測 test case 本身是否寫得模糊。所以早期測試案例應該盡量清楚。
expected 是預期答案或預期條件。
例如計算題:
{
"expected": "3780"
}
如果是關鍵字問答:
{
"expected": "台北"
}
如果是 JSON 格式任務,expected 也可以先用物件表示:
{
"expected": {
"answer": 15
}
}
這表示 expected 不一定只能是字串。
不同任務可能需要不同型態:
第二週先保持簡單,等 Day 13 做 JSON 格式驗證時,再處理比較完整的 structured output。
grading_method 用來描述這題要怎麼評分。
今天先定義三種:
| grading_method | 說明 | 適合情境 |
|---|---|---|
exact_match |
實際輸出必須和預期答案完全相同 | 很短且固定的答案 |
contains |
實際輸出只要包含預期文字即可 | Agent 回答可能有補充說明 |
json_exact |
實際輸出必須是 JSON,且欄位值符合預期 | structured output 任務 |
例如計算任務目前比較適合用 contains:
{
"input": "請計算 135 * 28",
"expected": "3780",
"grading_method": "contains"
}
因為目前 Agent 回答會是:
The result is 3780
如果使用 exact_match,就會失敗,因為它不等於單純的:
3780
所以 test case 的評分方式要和 Agent 目前的輸出型態搭配。
task_type 用來標記任務類型。
例如:
{
"task_type": "calculation"
}
它不一定會影響單題評分,但對後續統計很重要。
未來我們會想看:
calculation 任務成功率是多少?
keyword_qa 任務成功率是多少?
json_output 任務成功率是多少?
tool_use 任務成功率是多少?
如果沒有 task_type,就只能看整體成功率,看不出 Agent 哪一類任務最弱。
第二週先定義幾種簡單類型:
| task_type | 說明 |
|---|---|
calculation |
計算任務 |
keyword_qa |
關鍵字問答 |
json_output |
JSON 格式輸出 |
general_qa |
一般問答 |
tool_use |
需要使用工具的任務 |
新增 evals/__init__.py:
這個檔案今天可以先留空,不需要放任何程式碼。
它的作用是讓 Python 把 evals 資料夾視為 package。雖然今天還不會匯入它,但 Day 11 實作 Batch Evaluation Runner 時會用到。
新增 evals/cases.json:
[
{
"id": "case_001",
"input": "請計算 135 * 28",
"expected": "3780",
"grading_method": "contains",
"task_type": "calculation"
},
{
"id": "case_002",
"input": "請計算 72 + 19",
"expected": "91",
"grading_method": "contains",
"task_type": "calculation"
},
{
"id": "case_003",
"input": "請回答台灣的首都是哪裡",
"expected": "台北",
"grading_method": "contains",
"task_type": "keyword_qa"
},
{
"id": "case_004",
"input": "請用 JSON 格式回傳 10 + 5 的答案,欄位名稱使用 answer",
"expected": {
"answer": 15
},
"grading_method": "json_exact",
"task_type": "json_output"
},
{
"id": "case_005",
"input": "請用一句話說明什麼是 AI Agent",
"expected": "任務",
"grading_method": "contains",
"task_type": "general_qa"
}
]
這 5 筆案例先涵蓋幾種不同任務。
| id | 類型 | 目的 |
|---|---|---|
case_001 |
calculation | 測試乘法計算 |
case_002 |
calculation | 測試加法計算 |
case_003 |
keyword_qa | 測試答案是否包含關鍵字 |
case_004 |
json_output | 測試 structured output |
case_005 |
general_qa | 測試一般概念回答 |
其中 case_004 目前很可能不會通過。
原因是現在的 SimpleAgent 還沒有能力穩定輸出 JSON:
The result is 15
不一定會變成:
{
"answer": 15
}
這是正常的。
Evaluation dataset 不只需要成功案例,也應該包含可能失敗的案例。這樣後面做 format accuracy、schema validation 和 Guardrails 時,才有東西可以比較。
今天還沒有寫 Python 程式,但可以先確認 evals/cases.json 的格式正確。
如果你的電腦有 python3,可以在專案根目錄執行:
python3 -m json.tool evals/cases.json
如果 JSON 格式正確,終端機會印出排版後的 JSON。
如果格式錯誤,例如少了一個逗號或括號,會看到類似:
Expecting ',' delimiter: line 10 column 3 (char 180)
這個檢查很重要。
因為 Day 11 的 Batch Evaluation Runner 會讀取 evals/cases.json。如果 JSON 檔案本身格式錯誤,runner 還沒開始測 Agent 就會失敗。
你可能會想,既然已經有 test case schema,是不是應該直接建立 Python class?
例如:
@dataclass
class TestCase:
id: str
input: str
expected: str
grading_method: str
task_type: str
這件事可以做,但今天先不做。
原因是 Day 9 的重點是資料格式,而不是程式載入流程。
如果今天同時做:
TestCase class範圍會太大,也會和 Day 10、Day 11 重疊。
所以今天先把 cases.json 設計好,後面再逐步把它讀進 Python。
整理一下今天的設計原則。
不建議寫這種案例:
{
"input": "請介紹 AI"
}
因為它沒有 expected,後面無法自動判斷通過或失敗。
比較好的寫法是:
{
"input": "請用一句話說明什麼是 AI Agent",
"expected": "任務",
"grading_method": "contains"
}
雖然這不是完美評分,但至少可以先檢查回答是否包含主要概念。
如果 Agent 會回答:
The result is 3780
就不適合用:
{
"grading_method": "exact_match",
"expected": "3780"
}
因為字串不會完全相同。
這時可以先用:
{
"grading_method": "contains",
"expected": "3780"
}
Evaluation 不是要讓所有題目都通過。
如果測試集太簡單,所有版本都 100%,就很難看出改善效果。
例如 case_004 的 JSON output 任務,就是為了後面觀察 format error。
task_type 不只是分類好看而已。
後面 Reliability Dashboard 會需要它來統計不同任務類型的成功率。
所以一開始就加上 task_type,可以避免之後補資料。
今天完成後,專案新增了:
evals/__init__.py
evals/cases.json
cases.json 目前有 5 筆 test cases,包含:
目前還沒有:
今天重點是設計 test case 格式。
一筆最小 test case 包含:
id
input
expected
grading_method
task_type
其中:
input 是要交給 Agent 的任務。expected 是預期答案或條件。grading_method 決定如何評分。task_type 用來支援後續統計。有了這個格式,第二週後面的工作才有基礎。
Day 10 會把今天的 5 筆案例擴充成第一組 Eval Dataset。
下一篇會新增更多測試案例,讓 dataset 覆蓋:
Day 10 還不會實作 batch runner,重點會放在讓測試資料更完整,並確認每一題都有合理的預期結果。