PydanticAI ビジュアルガイド

Lesson CH09-L02

Pydantic Evals

ゴールデンデータセットでAgent出力を継続評価する。

読了目安
11 min
Colab目安
16 min
合計
27 min
前提
Logfire

関連: 公式ドキュメント

一行サマリ

Case(inputs=, expected_output=) を集めて Dataset(cases=[...], evaluators=[...]) を作り、dataset.evaluate_sync(task)Agent を全ケースに走らせて自動採点 + レポート出力。Pydantic 流の 「LLM アプリのユニットテスト」pydantic-evals パッケージで完結する。

ヒーロー: 「LLM のユニットテスト」を書く

Agent を本番投入したら 「プロンプトを 1 文字直したら別のケースが壊れた」 という回帰が必ず起きます。pydantic-evalsテストケースの集合 (Dataset) に対して 複数の評価軸 (Evaluators) を組み合わせ、スコア + レポート を出す仕組みです。pytest + 構造化レポートのイメージ。

図を読み込み中…
図1. Pydantic Evals のデータフロー

概念: 4 つの基本パーツ

パーツ役割
Case(name=, inputs=, expected_output=, metadata=)1 件のテストケース
Dataset(cases=[...], evaluators=[...])ケース集合 + 共通の評価器
Evaluator採点ロジック。IsInstance / Equals / Contains / LLMJudge / カスタム
dataset.evaluate_sync(task)task 関数を全 case に適用してレポート生成

ビルトイン Evaluator は 決定的 (確率的でない) 検証 を担当。LLMJudge は次レッスン (Ch9-L03) で詳しく扱います。

コード: 3 つのパターン

パターン 1: 最小例 — 翻訳 Agent をビルトイン Evaluator で評価

from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import IsInstance
 
translator = Agent(
    GoogleModel('gemini-3-flash-preview'),
    instructions='与えられた英語を自然な日本語に訳してください。',
)
 
cases = [
    Case(name='hello', inputs='Hello, world!', expected_output='こんにちは、世界!'),
    Case(name='cat', inputs='I have a cat.', expected_output='私は猫を飼っています。'),
    Case(name='thanks', inputs='Thank you very much.', expected_output='どうもありがとうございます。'),
]
 
dataset = Dataset(cases=cases, evaluators=[IsInstance(type_name='str')])
 
def task(inputs: str) -> str:
    return translator.run_sync(inputs).output
 
report = dataset.evaluate_sync(task)
report.print(include_input=True, include_output=True)

ポイント:

  • Caseinputs / expected_output が肝心。metadata で観察用情報を添える
  • taskinputs を受けて Agent の output を返す関数
  • IsInstancestr を返したか の最低限チェック
  • report.print(...)ケース単位の表形式レポート

パターン 2: カスタム Evaluator で「正解語を含むか」をチェック

ビルトインに無い検証は @dataclass class XxxEvaluator(Evaluator): で書きます。

from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
 
@dataclass
class ContainsExpected(Evaluator):
    """expected_output の文字列が output に部分一致するか。"""
 
    async def evaluate(self, ctx: EvaluatorContext[str, str]) -> float:
        if ctx.expected_output is None:
            return 0.0
        return 1.0 if ctx.expected_output in ctx.output else 0.0
 
dataset = Dataset(
    cases=cases,
    evaluators=[IsInstance(type_name='str'), ContainsExpected()],
)
report = dataset.evaluate_sync(task)
report.print(include_input=True, include_output=True)

EvaluatorContextctx.inputs / ctx.output / ctx.expected_output / ctx.metadata を持ちます。0.0 〜 1.0 の float を返すのが約束。

パターン 3: 構造化出力 Agent を評価

output_type=BaseModel の Agent も同じ枠組みで評価できます。

from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
from dataclasses import dataclass
 
class Sentiment(BaseModel):
    polarity: str  # positive / neutral / negative
 
agent = Agent(
    GoogleModel('gemini-3-flash-preview'),
    output_type=Sentiment,
    instructions='文の感情を判定し polarity に positive/neutral/negative を入れてください。',
)
 
@dataclass
class PolarityMatches(Evaluator):
    async def evaluate(self, ctx: EvaluatorContext[str, Sentiment]) -> float:
        return 1.0 if ctx.output.polarity == ctx.expected_output else 0.0
 
cases = [
    Case(inputs='今日の発表は最高でした!', expected_output='positive'),
    Case(inputs='残念な結果に終わりました。', expected_output='negative'),
    Case(inputs='通常通り進んでいます。', expected_output='neutral'),
]
 
dataset = Dataset(cases=cases, evaluators=[PolarityMatches()])
 
def task(inputs: str) -> Sentiment:
    return agent.run_sync(inputs).output
 
dataset.evaluate_sync(task).print(include_input=True, include_output=True)

構造化出力 (Ch4) と組み合わせれば、「fields 単位での厳密検証」 がそのまま自動化できます。

図を読み込み中…
図2. Evaluator 種別の使い分け

観察: レポート出力と CI 連携

report.print(...) はターミナル向けの表形式表示。report.cases個別のスコア / output / failure 詳細 にアクセスでき、CI で assert report.average_score > 0.8 のような閾値チェックが書けます。

report = dataset.evaluate_sync(task)
avg = sum(c.evaluators[0].score for c in report.cases) / len(report.cases)
assert avg >= 0.8, f'平均スコアが基準未満: {avg:.2f}'

Logfire 連携 (pip install pydantic-evals[logfire]) を使うと Web 上でケースごとのトレース も見られます。

まとめ

  • Case + Dataset + EvaluatorLLM アプリのユニットテスト を書ける
  • ビルトイン Evaluator (IsInstance / EqualsExpected 等) は決定的検証
  • カスタム Evaluator は @dataclass class X(Evaluator): + async def evaluate(ctx) -> float
  • 構造化出力 (Ch4) と組み合わせれば fields 単位の厳密検証
  • CI で report.average_score >= 0.8 のような閾値チェックを回す

次レッスンでは LLM-Judge — 別 LLM に採点させる主観品質評価のパターンと、その落とし穴を扱います。

Colab で実際に動かす

本レッスンの内容を Google Colab 上で実行できるノートブックを用意しています。下のボタンから自分のColab環境に開けます (要 Google アカウント / GOOGLE_API_KEY)。

Open in Colab

notebooks/ch09/02-pydantic-evals.ipynb