Pydantic Evals:在 Python 里定义 dataset,给 LLM 输出和工具调用轨迹打分
Pydantic Evals 是一个评测框架,用来系统性地测试和评估 AI 系统,范围从单次 LLM 调用到复杂的多 agent 应用。它打分的对象有两层:agent 的最终输出,以及 trajectory——工具调用的序列和参数。参照物可以是代码里定义的 dataset,也可以用 online evaluation 拿一部分线上真实流量采样来对照。
- 项目:
- 文档:
设计哲学
Code-First。 dataset、experiment、task、case、evaluator 全部在 Python 里定义,跟基于 web 配置的评测平台走的是两条路。eval 在代码里写、在代码里跑,结果可以落到磁盘,也可以在终端或 Pydantic Logfire 里看。
文档里还有一个标题写得很直接:Evals are an Emerging Practice。eval 不像单元测试,它还是新兴的技艺/科学,没有成型的共识。所以框架刻意保持灵活、不过度带观点——任何声称确切知道你的 eval 该怎么定义的人,都可以放心无视。
Data Model
Dataset (1) ── (Many) Case
│ │
└── (Many) Experiment ──┴── (Many) Case results
│
└── (1) Task
│
└── (Many) Evaluator
- Dataset → Cases:一个 Dataset 包含多个 Case
- Dataset → Experiments:一个 Dataset 可以跨时间用于多次 Experiment
- Experiment → Case results:一次 Experiment 执行每个 Case 生成结果
- Experiment → Task:一次 Experiment 评估一个已定义的 Task
- Experiment → Evaluators:一次 Experiment 用多个 Evaluator;Dataset 级 Evaluator 对所有 Case 跑,Case 专属的 Evaluator 只对各自的 Case 跑
数据流:定义 dataset(YAML/JSON,或直接在 Python 里)→ 运行 experiment(dataset.evaluate_sync(task_function))→ 每个 Case 对着 Task 执行 → Evaluator 给每个 Case 的输出打分 → 汇总成报告。
一个不完美但有用的类比:把 evals 想成单元测试框架。Cases + Evaluators 相当于单个单元测试,Datasets 像 test suite,Experiments 像跑完整个测试套件拿报告。关键区别在于 AI 系统是概率性的:类型检查仍然是简单 pass/fail,但文本输出的分数往往是定性的或分类的,更依赖解读。
Datasets 和 Cases
Dataset 是为评估某个特定 task/function 设计的一组 test Case;Case 是单个测试场景,对应 Task 的输入,可选期望输出、metadata,以及 case 专属的 evaluator。
from pydantic_evals import Case, Dataset
case1 = Case(
name='simple_case',
inputs='What is the capital of France?',
expected_output='Paris',
metadata={'difficulty': 'easy'},
)
dataset = Dataset(name='capital_quiz', cases=[case1])
Evaluators
Evaluator 分析 Task 结果并打分。既可以是确定性的、基于代码的检查(正则测输出格式、查 PII/敏感数据),也可以评估非确定性模型输出的质量,比如准确率、精确率/召回、幻觉、指令遵循。
代码类测试比需要人工或机器审阅输出的测试更便宜、也更简单。
内置 evaluator 和自定义 evaluator 都挂在同一个 dataset 上:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
from pydantic_evals.evaluators.common import IsInstance
from simple_eval_dataset import dataset
dataset.add_evaluator(IsInstance(type_name='str'))
@dataclass
class MyEvaluator(Evaluator):
async def evaluate(self, ctx: EvaluatorContext[str, str]) -> float:
if ctx.output == ctx.expected_output:
return 1.0
elif (
ctx.expected_output is not None
and ctx.expected_output.lower() in ctx.output.lower()
):
return 0.8
else:
return 0.0
dataset.add_evaluator(MyEvaluator())
运行 Experiments
评测就是让 task 对着 dataset 里所有 case 跑一遍,也就是跑一次 experiment。
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext, IsInstance
case1 = Case(
name='simple_case',
inputs='What is the capital of France?',
expected_output='Paris',
metadata={'difficulty': 'easy'},
)
class MyEvaluator(Evaluator[str, str]):
def evaluate(self, ctx: EvaluatorContext[str, str]) -> float:
if ctx.output == ctx.expected_output:
return 1.0
elif (
ctx.expected_output is not None
and ctx.expected_output.lower() in ctx.output.lower()
):
return 0.8
else:
return 0.0
dataset = Dataset(
name='capital_quiz',
cases=[case1],
evaluators=[IsInstance(type_name='str'), MyEvaluator()],
)
async def guess_city(question: str) -> str:
return 'Paris'
report = dataset.evaluate_sync(guess_city)
report.print(include_input=True, include_output=True, include_durations=False)
evaluate_sync 让函数对着 dataset 里所有 test case 跑一遍,返回 EvaluationReport 对象;report.print 打印结果表,包含 Case ID / Inputs / Outputs / Scores / Assertions,以及一行 Averages。
安装
pip install pydantic-evals
# 或
uv add pydantic-evals
pydantic-evals 不依赖 pydantic-ai,但对 logfire 有可选依赖:pip install 'pydantic-evals[logfire]'。装上之后可以在 eval 里用 OpenTelemetry traces,或把评测结果发到 logfire。
进阶方向
- Built-in Evaluators:exact match、instance checks 等即用型检查
- LLM as a Judge:用 LLM 评估主观质量、复杂标准、自然语言输出
- Custom Evaluators:自己的领域打分逻辑
- Span-Based Evaluation:基于 OpenTelemetry trace 评估 agent 内部行为(工具调用、执行流)。当正确性取决于「答案怎么来的」而不只是最终输出时,这一点很关键,也能保证 eval 断言与生产遥测对齐
- Agentic Evaluators:给 agent 的 trajectory(工具调用的序列和参数)打分,而不只是最终输出
- Online Evaluation:把 evaluator 挂到生产或 staging 流量上,每次调用或按采样子集在后台被打分
- Logfire Integration:可视化结果;Dataset Management:保存/加载/生成数据集
框架把抽象层铺好了,剩下的判断——什么算好输出、哪些轨迹值得扣分——仍然得自己下。eval 还是新兴实践。