Pydantic AI Model Evaluations 模型評估
在上一篇文章中,我們討論了如何使用 TestModel 進行單元測試 (Unit Testing)。單元測試的重點在於檢查「程式邏輯 (Code Logic)」是否正確,例如:工具是否被呼叫、參數傳遞是否正確、系統提示有沒有寫錯字。
然而,單元測試無法告訴你:「AI 寫的這篇總結文章夠不夠好?」或是「AI 有沒有產生幻覺 (Hallucination) 提供錯誤的財務建議?」
這時候,我們需要另一種測試維度,稱為 模型評估 (Evaluations,簡稱 Evals)。
為什麼需要 Evals?
大型語言模型 (LLM) 是一個充滿不確定性的黑盒子。當你修改了系統提示(加了一句 "請用更禮貌的語氣"),或是將底層模型從 gpt-4o-mini 升級為 gpt-4o 時,你怎麼知道這些改動是整體提升了品質,還是顧此失彼造成了其他情境退步?
Evals 的目的就是透過系統化、自動化的方式,對 AI 代理人在大量測試案例 (Dataset) 上的表現進行「打分」。只有擁有可靠的 Evals 系統,開發團隊才能充滿信心地反覆迭代系統提示與模型參數。
認識 Pydantic Evals
Pydantic Evals 是 Pydantic AI 生態圈中專門負責模型評估的子模組。
它提供了以下幾項核心功能,幫助你建立自動化的評估管線:
- 資料集管理 (Dataset Management): 幫助你載入與管理測試題庫(例如數百個過去的真實客服對話紀錄,以及對應的完美人類解答)。
- 各式評估器 (Evaluators): 提供了多種打分機制。有些評估器是基於傳統規則(如字數長度、是否包含特定關鍵字),而更進階的評估器則是利用另一個強大的 LLM 來當作裁判 (LLM Judge)。
- 線上評估 (Online Evaluation): 與 Pydantic Logfire 深度整合,讓你不只在開發階段跑測試,還能在系統上線後,持續對真實使用者的生產環境對話進行即時抽樣打分,監控模型品質是否隨著時間衰退 (Drift)。
- 並發與效能 (Concurrency): 當你有 1,000 筆測試案例需要評估時,Pydantic Evals 提供了高效的非同步執行引擎,大幅縮短跑完全部 Evals 所需的時間。
安裝 Pydantic Evals
Pydantic Evals 是一個獨立的套件,你需要額外安裝它:
pip install pydantic-evals
如果需要與 Logfire 整合來記錄評估結果,可以安裝包含 Logfire 的版本:
pip install 'pydantic-evals[logfire]'
核心概念與快速上手
要使用 Pydantic Evals,你需要了解幾個核心概念:
- 資料集 (Dataset):一組測試案例的集合,也可以包含共用的評估器。
- 測試案例 (Case):單一個測試情境,包含輸入值 (inputs)、預期輸出 (expected_output),以及該案例專屬的評估器。
- 評估器 (Evaluator):用來對任務輸出結果進行打分或驗證的函數。
- 評估報告 (EvaluationReport):執行評估後產生的詳細結果報告。
確定性驗證 (Deterministic Validation) 範例
雖然 Evals 通常用於測試 AI 系統,但這個框架可以測試任何函數。以下是一個簡單的文字轉換函數評估範例,展示了如何使用內建的規則評估器:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Contains, EqualsExpected
# 1. 建立包含測試案例的資料集
dataset = Dataset(
name='uppercase_tests',
cases=[
Case(
name='uppercase_basic',
inputs='hello world',
expected_output='HELLO WORLD',
),
Case(
name='uppercase_with_numbers',
inputs='hello 123',
expected_output='HELLO 123',
),
],
evaluators=[
EqualsExpected(), # 檢查輸出是否與 expected_output 完全一致
Contains(value='HELLO', case_sensitive=True), # 檢查輸出是否包含 "HELLO"
],
)
# 2. 定義待測函數 (這裡以簡單的 Python 函數為例,實務上會是你的 AI Agent)
def uppercase_text(text: str) -> str:
return text.upper()
# 3. 執行同步評估
report = dataset.evaluate_sync(uppercase_text)
# 4. 印出評估報告
report.print()
執行後,你會看到類似以下的精美終端機輸出報告,顯示每個測試案例是否通過了所有的斷言 (Assertions) 以及執行時間:
Evaluation Summary: uppercase_text
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Case ID ┃ Assertions ┃ Duration ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩
│ uppercase_basic │ ✔✔ │ 10ms │
├─────────────────────────┼────────────┼──────────┤
│ uppercase_with_numbers │ ✔✔ │ 10ms │
├─────────────────────────┼────────────┼──────────┤
│ Averages │ 100.0% ✔ │ 10ms │
└─────────────────────────┴────────────┴──────────┘
各式評估器 (Evaluators) 實作
在 Pydantic Evals 中,評估器 (Evaluator) 是負責對 AI 模型的輸出結果進行「打分」的元件。根據任務的性質,Pydantic Evals 支援多種不同的評估策略。
內建與自定義的規則評估器 (Built-in & Custom Evaluators)
最簡單的評估方式是基於傳統的程式邏輯(Deterministic 確定性的規則)。Pydantic Evals 內建了許多實用的評估器,例如前面範例用到的 EqualsExpected 和 Contains,或是檢查型別的 IsInstance:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Contains, IsInstance
dataset = Dataset(
name='dict_validation',
cases=[
Case(inputs={'data': 'required_key present'}, expected_output={'result': 'success'}),
],
evaluators=[
IsInstance(type_name='dict'), # 檢查輸出是否為字典型別
Contains(value='required_key'), # 檢查是否包含特定內容
],
)
除了內建評估器,你也可以寫自定義的 Python 函數來當作評估器。這類評估器的優點是執行速度極快,且不需額外花費 API 成本。但在面對諸如「語氣是否友善」、「內容是否具有創意」這類主觀問題時,規則評估器就無能為力了。
大語言模型裁判 (LLM Judge)
這正是 LLM Judge 登場的時刻。LLM Judge 的概念非常聰明:我們使用一個更聰明、能力更強的大語言模型(例如 o1-preview 或是 gpt-4o)來作為裁判,去評分另一個較小或較便宜的模型(例如 gemini-1.5-flash 或本地模型)的輸出結果。
在 Pydantic Evals 中,你可以直接使用內建的 LLMJudge 評估器,輕鬆將主觀的評分標準 (Rubric) 交給強大的模型來判斷:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import LLMJudge
dataset = Dataset(
name='llm_judge_test',
cases=[
Case(inputs='法國的首都哪裡?', expected_output='巴黎'),
],
evaluators=[
# 使用 LLM Judge 進行主觀評估
LLMJudge(
rubric='回答必須準確且具有幫助性', # 評分標準
include_input=True,
model='openai:gpt-4o', # 指定裁判模型
)
],
)
透過 LLM Judge,你可以讓自動化評估覆蓋到那些難以用傳統程式碼量化的「模糊地帶」。當你對待測 Agent 的系統提示做出了微調後,只要跑一次 LLM Judge 的批量測試,就能立刻從幾十個案例的分數變化中,客觀地得知這個改動是好是壞。搭配 Pydantic Logfire 將每次的評分結果與原因可視化,團隊就能建立起數據驅動 (Data-Driven) 的 Prompt Engineering 流程。
效能測試 (Performance Testing)
除了驗證輸出的正確性,Pydantic Evals 也允許你針對系統的回應時間進行效能測試。你可以使用 MaxDuration 評估器來確保你的 AI 應用程式符合效能要求:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import MaxDuration
dataset = Dataset(
name='performance_test',
cases=[
Case(inputs='test input', expected_output='test output'),
],
evaluators=[
MaxDuration(seconds=2.0), # 限制執行時間不得超過 2 秒
],
)
基於區段的評估 (Span-Based)
在某些進階場景中,我們不僅要評估「最終答案」,還要評估「過程」。傳統的評估器只看輸入與輸出,如果 Agent 靠「瞎猜」或是「呼叫了錯誤的工具但剛好得到正確答案」,傳統評估器是無法發現的。
Pydantic Evals 的 Span-Based (基於區段) 評估,允許你透過分析執行過程中產生的 OpenTelemetry Spans(區段紀錄)來評估系統的行為。這通常需要搭配 logfire 來捕捉這些執行軌跡。
例如,一個 RAG 系統 (檢索增強生成) 的流程包含:
- 提取關鍵字
- 搜尋向量庫
- 統整資料並生成答案
你可以使用 HasMatchingSpan 評估器來確保 Agent 確實執行了「搜尋向量庫」這個步驟,而不是直接產生幻覺回答:
import logfire
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import HasMatchingSpan
# 1. 設定 logfire 以捕捉執行區段 (Spans)
logfire.configure(send_to_logfire='if-token-present')
# 2. 建立包含 Span 評估器的資料集
dataset = Dataset(
name='rag_span_test',
cases=[Case(inputs='請幫我總結最新的公司休假規定')],
evaluators=[
# 確保執行過程中,有呼叫到名稱包含 'vector_search' 的區段 (例如搜尋工具)
HasMatchingSpan(
query={'name_contains': 'vector_search'},
evaluation_name='retrieved_docs', # 評估項目名稱:是否有檢索文件
),
# 確保執行過程中,沒有發生錯誤
HasMatchingSpan(
query={'not_': {'has_attributes': {'error': True}}},
evaluation_name='no_errors', # 評估項目名稱:無錯誤發生
),
],
)
這種細緻的評估方法能幫助你精準驗證 Agent 的行為合約(例如:是否呼叫了特定工具、是否在規定時間內完成、是否發生了重試),而不僅僅是檢查最終輸出的文字。