Pydantic Evals 评测框架实战指南:从 Dataset 到 EvaluationReport 的全流程解析
【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai
Pydantic Evals 是 Pydantic AI 生态(本仓库pydantic_evals包)中用于系统化测试与评估 AI 系统的评测框架,覆盖从简单 LLM 调用到复杂多智能体应用的各种场景。本文基于仓库文档 docs/evals.md 的完整脉络展开,并结合 pydantic_evals 源码 与测试示例(examples/pydantic_ai_examples/evals)深入讲解:读完你将掌握 Dataset/Case/Evaluator/Experiment 的数据模型、内置与自定义评估器的编写方式、evaluate_sync实验执行的全部关键参数,以及评测结果在终端与 Logfire 中的呈现方式。
一、设计哲学:Code-First 的评测方式
Pydantic Evals 遵循Code-First(代码优先)哲学:所有评测组件——数据集、实验、任务、用例(Case)和评估器(Evaluator)——都用 Python 代码定义,或以 Python 代码加载的序列化数据形式存在。这与依赖 Web 控制台配置评测的平台不同:你在代码中编写并运行评测,结果可以写盘、在终端直接查看,或发送到 Pydantic Logfire 的 Web 界面中可视化。
文档同时强调一个现实判断:评测是一门新兴的实践(emerging practice)。与单元测试不同,没有人能确切告诉你评测该如何定义;官方因此把 Pydantic Evals 设计得“灵活有用但不过度持立场(flexible and useful without being too opinionated)”。
在仓库中,pydantic_evals是一个独立的顶层包(见 pyproject.toml),它不依赖pydantic-ai,仅在你需要把评测接入 OpenTelemetry / Logfire 时才需要可选依赖logfire。这一点让它可以单独用于任意“随机函数”(stochastic function)的评估——包 docstring(pydantic_evals/pydantic_evals/__init__.py)明确将其定位 toolkit 描述为“评估任意随机函数执行的工具箱”。
二、安装
pip install pydantic-evals # 或使用 uv uv add pydantic-evals如需在评测中使用 OpenTelemetry 追踪(如 span 行为断言),或将结果发送到 Logfire,则安装 logfire 扩展:
pip install 'pydantic-evals[logfire]' # 或使用 uv uv add 'pydantic-evals[logfire]'注意:pydantic-evals本体不依赖pydantic-ai;[logfire]为可选依赖,仅在使用追踪类评估器或 Logfire 集成时才必需。
三、数据模型:Dataset、Case、Experiment 与 Evaluator
Pydantic Evals 围绕一个简洁的数据模型构建:
Dataset (1) ──────────── (Many) Case │ │ │ │ └─── (Many) Experiment ──┴─── (Many) Case results │ └─── (1) Task │ └─── (Many) Evaluator关键关系:
- Dataset → Cases:一个 Dataset 包含多个 Case;
- Dataset → Experiments:同一个 Dataset 可以随时间被用于多个实验(对比不同实现、追踪版本变化);
- Experiment → Case results:一次实验执行每个 Case 后生成结果;
- Experiment → Task:一次实验评估一个定义好的任务函数;
- Experiment → Evaluators:一次实验使用多个 Evaluator;Dataset 级评估器对全部 Case 生效,Case 级评估器只对所属 Case 生效。
数据流(执行时发生的事):
- 创建 Dataset:在 YAML/JSON 或直接以 Python 代码定义 cases 与 evaluators;
- 执行实验:调用
dataset.evaluate_sync(task_function); - Case 运行:每个 Case 在 Task 上执行;
- 评估:评估器对每个 Case 的 Task 输出打分;
- 结果汇总:所有 Case 结果被收集为一份汇总报告(
EvaluationReport)。
单元测试类比
一个有用的(虽非完美)隐喻是把评测理解为单元测试框架:
| 单元测试 | Pydantic Evals |
|---|---|
| 测试函数 | Case+Evaluator(各自定义一个待测场景,包含输入与期望结果) |
| 测试套件 | Dataset(把相关用例组织在一起,定义共享的评估标准) |
运行测试(pytest) | Experiment(dataset.evaluate_sync(my_ai_function)) |
| 测试报告 | EvaluationReport |
assert | 返回bool的 Evaluator |
与传统单元测试的关键区别在于:AI 系统是概率性的。类型检查仍能得到简单的 pass/fail,但文本输出的评分往往是定性、分档甚至需要人工/模型裁决的。
上述概念在 docs/evals/core-concepts.md 中有更详细的展开,包括实验内部五阶段流程(Setup → Execution → Case Evaluation → Report Evaluation → Reporting)。
四、Dataset 与 Case:一切从测试数据开始
在 Pydantic Evals 中,一切始于Dataset与Case:
- Dataset:为评估特定任务/函数而设计的一组测试 Case 集合;
- Case:单个测试场景,对应 Task 的输入,可带可选的期望输出、元数据与 Case 专属评估器。
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])(该示例完整可运行。)
Case 字段详解
结合 pydantic_evals/pydantic_evals/dataset.py 中Case的 dataclass 定义,各字段语义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name | str \| None | Case 名,用于在报告中标识与过滤;不填时报告里会显示为Case N |
inputs | 泛型InputsT | 传给 task 的输入,可以是任意类型(字符串、dict、Pydantic 模型等) |
metadata | 泛型MetadataT \| None | 供评估器通过EvaluatorContext访问的任意元数据 |
expected_output | 泛型OutputT \| None | 期望输出,供EqualsExpected等评估器比较 |
evaluators | list[Evaluator] | 仅对该 Case 生效的评估器(在 Dataset 级评估器之外追加执行) |
几个值得注意的实现细节(来自源码):
- 重复名校验:
Dataset.__init__会检查 Case 名字,出现重复时抛出ValueError: Duplicate case name: ...(见 dataset.py#L254-L260); expected_output与None的陷阱:None表示“未提供”。若某 Case 的期望输出本身是None,EqualsExpected会跳过该 Case(相当于不产生断言);要断言 task 返回None,应使用显式取值比较的Equals(value=None);- 类型安全:
Dataset泛型化于InputsT、OutputT、MetadataT三个类型参数,可保存/加载为 YAML 或 JSON。
Dataset 级 vs Case 级评估器
评估器可定义在两个层级:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import EqualsExpected, IsInstance dataset = Dataset( name='case_level_evaluators', cases=[ Case( name='special_case', inputs='test', expected_output='TEST', evaluators=[ EqualsExpected(), # 只在这个 Case 上运行 ], ), ], evaluators=[ IsInstance(type_name='str'), # 对所有 Case 运行 ], )从源码看,Dataset.add_evaluator(evaluator, specific_case=None)提供了动态追加能力:specific_case为None时追加到 Dataset 级列表;传入 Case 名时只追加到该 Case 的evaluators上,找不到对应 Case 会抛ValueError(见 dataset.py#L507-L533)。
数据集的保存与加载、生成等完整指南见 docs/evals/how-to/dataset-management.md;Dataset.from_file(path, fmt=...)支持从 YAML/JSON 文件加载,并可通过custom_evaluator_types参数反序列化自定义评估器(见 dataset.py#L556-L595)。
五、Evaluators:如何给结果打分
Evaluator 负责分析与打分 Task 在某个 Case 上的表现。评估器有两类:
- 确定性代码检查:如用正则验证模型输出格式、检测 PII/敏感数据;
- 非确定性输出评估:评估准确性、precision/recall、幻觉、指令遵循等质量维度。
两类都有价值,但传统代码检查比需要人工或模型审查的检查更便宜、更容易——这是官方文档明确的成本判断。
评估器返回类型
评估器evaluate方法的返回类型决定其在报告中的呈现形式(来自 docs/evals/core-concepts.md):
| 返回类型 | 用途 | 示例 |
|---|---|---|
bool | 断言(Assertion)——pass/fail 检查 | True→ ✔,False→ ✗ |
int/float | 分数(Score)——数值质量指标 | 0.95、87 |
str | 标签(Label)——类别结果 | "correct"、"hallucination" |
此外,评估器还可以返回带说明文字的EvaluationReason(如EvaluationReason(value=True, reason='Exact match')),或在print(include_reasons=True)时展示理由;也可以返回“名字 → 值”的字典一次产出多项结果。
EvaluatorContext:评估器看到什么
每个评估器都收到一个EvaluatorContext,包含:
name:Case 名(可选)inputs:任务输入metadata:Case 元数据(可选)expected_output:期望输出(可选)output:task 的实际输出duration:任务执行耗时(秒)span_tree:OpenTelemetry spans(配置 logfire 时可用)attributes/metrics:自定义属性与指标字典
完整示例:内置评估器 + 自定义评估器
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')) # (1)! @dataclass class MyEvaluator(Evaluator): async def evaluate(self, ctx: EvaluatorContext[str, str]) -> float: # (2)! if ctx.output == ctx.expected_output: return 1.0 elif ( isinstance(ctx.output, str) and ctx.expected_output.lower() in ctx.output.lower() ): return 0.8 else: return 0.0 dataset.add_evaluator(MyEvaluator())- 通过
Dataset.add_evaluator添加内置评估器; - 该自定义评估器根据输出与期望输出的匹配程度返回一个分数。
(示例完整可运行。)
内置评估器还支持更简洁的同步evaluate实现——Evaluator基类会把同步方法包装进异步执行路径,因此上面示例既可以写async def evaluate,也可以直接写def evaluate(见 docs/evals.md 第六节的MyEvaluator写法)。
内置评估器速查
当前版本从 pydantic_evals.evaluators 导出的常用评估器:
| 评估器 | 用途 | 返回类型 | 成本 | 速度 |
|---|---|---|---|---|
EqualsExpected | 与expected_output精确相等 | bool | 免费 | 即时 |
Equals | 等于指定值(可断言value=None) | bool | 免费 | 即时 |
Contains | 包含值/子串(支持字符串、列表成员、dict 键值对) | bool+ reason | 免费 | 即时 |
IsInstance | 类型校验(按type_name匹配 MRO) | bool+ reason | 免费 | 即时 |
MaxDuration | 执行时长阈值(SLA 检查) | bool | 免费 | 即时 |
LLMJudge | LLM 按 rubric 评主观质量 | bool和/或float | 高 | 慢 |
GEval | G-Eval 思维链打分(整数score_range) | int+ reason | 高 | 慢 |
HasMatchingSpan | 基于 OTel span 的行为检查(需 logfire) | bool | 免费 | 快 |
报告级评估器(作用于整次实验的结果集,经Dataset(report_evaluators=...)传入):ConfusionMatrixEvaluator(混淆矩阵)、PrecisionRecallEvaluator(PR 曲线 + AUC)、ROCAUCEvaluator、KolmogorovSmirnovEvaluator。
完整的参数与行为说明见 docs/evals/evaluators/built-in.md,各类型的选型指南见 docs/evals/evaluators/overview.md,基于 OTel 轨迹的智能体行为评估(工具调用、执行流)见 docs/evals/evaluators/span-based.md。
最佳实践(源自官方文档):把快速确定性检查放在前面,昂贵的 LLM 评估放在后面,先捕获格式/结构问题,再评估质量。另注意一个安全相关的实现细节:源码中pydantic_evals.evaluators模块显式移除了Python评估器(出于安全原因),访问它会抛出带说明的ImportError(见 evaluators/__init__.py#L71-L76)。
六、运行实验:完整示例与输出
执行评估的过程就是“用数据集里的全部 Case 运行一个任务”,也就是运行一次实验(experiment)。把前文的 Dataset 与 Evaluator 示例合并,使用Dataset更声明式的evaluators参数:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import Evaluator, EvaluatorContext, IsInstance case1 = Case( # (1)! 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 ( isinstance(ctx.output, str) 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()], # (2)! ) async def guess_city(question: str) -> str: # (3)! return 'Paris' report = dataset.evaluate_sync(guess_city) # (4)! report.print(include_input=True, include_output=True, include_durations=False) # (5)! """ Evaluation Summary: guess_city ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Case ID ┃ Inputs ┃ Outputs ┃ Scores ┃ Assertions ┃ ┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ │ simple_case │ What is the capital of France? │ Paris │ MyEvaluator: 1.00 │ ✔ │ ├─────────────┼────────────────────────────────┼─────────┼───────────────────┼────────────┤ │ Averages │ │ │ MyEvaluator: 1.00 │ 100.0% ✔ │ └─────────────┴────────────────────────────────┴─────────┴───────────────────┴────────────┘ """- 创建测试 Case;
- 创建包含 Case 与评估器的
Dataset; - 待评估的任务函数(这里用一个返回固定字符串的协程模拟 LLM 调用);
- 用
evaluate_sync运行评估:该函数会把 task 跑过数据集里的全部 Case,返回一个EvaluationReport对象; - 用
report.print(...)打印报告。示例中关掉 duration 只是为了让输出在多次运行间保持稳定。
(示例完整可运行。)
实验执行参数(源码级)
Dataset.evaluate(异步)与Dataset.evaluate_sync(同步包装)共享同一套参数(见 dataset.py#L281-L324):
| 参数 | 说明 |
|---|---|
task | 待评估的可调用对象,接收 Case 的inputs并返回输出,支持同步或异步函数 |
name | 实验名;缺省时依次回退到task_name、task 函数名 |
max_concurrency | 并发 Case 数上限;None表示全部并发。实现上通过anyio.Semaphore限流 |
progress | 是否显示进度条(默认True),基于 rich 的Progress |
retry_task/retry_evaluators | task 执行与评估器执行的RetryConfig重试配置 |
task_name | 覆盖报告中显示的任务名 |
metadata | 实验级元数据字典,会写入报告与 span 属性 |
repeat | 每个 Case 重复运行次数(>1 时结果按原 Case 名分组聚合,报告名形如case [1/3]),默认 1,小于 1 抛ValueError |
lifecycle | CaseLifecycle类或工厂,用于每 Case 的 setup/teardown 钩子 |
从源码结构看,evaluate的整体流程是:在一个logfire_span('evaluate {name}')中(携带gen_ai.operation.name='experiment'等属性),用任务组并发执行所有 Case;每个 Case 内部由_run_task_and_evaluators依次完成任务执行、计时、Dataset 级 + Case 级评估;成功的结果进入ReportCase,失败的进入ReportCaseFailure。全部 Case 完成后,若配置了report_evaluators,会在整份EvaluationReport上运行它们,产出实验级分析(混淆矩阵、PR 曲线、标量指标、表格等)。
并发控制、批量执行性能调优见 docs/evals/how-to/concurrency.md;重试策略见 docs/evals/how-to/retry-strategies.md。
一个 Dataset,多次实验:对比不同实现
同一 Dataset 可反复用于不同 task 实现,这是评测追踪回归与 A/B 对比的基础(示例引自 core-concepts):
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import EqualsExpected dataset = Dataset( name='comparison_test', cases=[Case(inputs='hello', expected_output='HELLO')], evaluators=[EqualsExpected()], ) def task_v1(text: str) -> str: return text.upper() def task_v2(text: str) -> str: return text.upper() + '!' report_v1 = dataset.evaluate_sync(task_v1) report_v2 = dataset.evaluate_sync(task_v2) avg_v1 = report_v1.averages() avg_v2 = report_v2.averages() print(f'V1 pass rate: {avg_v1.assertions if avg_v1 and avg_v1.assertions else 0}') #> V1 pass rate: 1.0 print(f'V2 pass rate: {avg_v2.assertions if avg_v2 and avg_v2.assertions else 0}') #> V2 pass rate: 0典型用途:跨版本对比实现、追踪性能随时间变化、A/B 测试不同方案、部署前验证改动。
七、EvaluationReport:报告结构与程序化访问
EvaluationReport是实验的最终产物,包含运行 task 与全部评估器的所有数据。其结构(来自 core-concepts,字段与 reporting 模块对应):
name:实验名cases:成功执行的 Case 结果列表failures:执行失败的列表analyses:报告评估器产出的实验级分析(混淆矩阵、PR 曲线、标量、表格)experiment_metadata:实验元数据trace_id/span_id:OpenTelemetry 追踪标识(配置 logfire 时可用,由evaluate从当前 span context 提取,见 dataset.py#L377-L414)
每个成功 Case 结果(ReportCase)包含:Case 数据(name、inputs、metadata、expected_output、output)、评估结果(scores、labels、assertions)、性能数据(task_duration、total_duration)、自定义metrics/attributes、追踪信息(trace_id、span_id)以及evaluator_failures错误列表。
程序化访问示例:
for case in report.cases: print(f'{case.name}: {case.scores}') #> Case 1: {}report.print()的常用开关包括include_input、include_output、include_durations、include_reasons等;report.averages()返回聚合统计(含断言通过率),可跨报告对比。
八、Logfire 集成:可视化与协作分析
使用 Pydantic Logfire 时,实验结果会自动出现在 Logfire Web 界面中,用于可视化、对比与协作分析。定位上,Logfire 是可观测层:你在代码中编写并运行评测,在 Web UI 中查看与分析结果。
结合仓库中 docs/evals/how-to/logfire-integration.md 的说明,接入方式是安装pydantic-evals[logfire]扩展并初始化 Logfire;此时:
- 每次实验产生一个
evaluate <name>span,携带dataset_name、n_cases、gen_ai.operation.name='experiment'等属性; EvaluatorContext.span_tree可用,从而支撑HasMatchingSpan与基于轨迹的评估器(如ToolCorrectness、TrajectoryMatch、MaxToolCalls、MaxModelRequests——这些“agentic”评估器从 evaluators/agentic.py 导出);- 报告对象带
trace_id/span_id,可将终端报告与 Web 端 trace 关联。
九、进阶主题与延伸阅读
围绕 docs/evals.md 的“Quick Navigation”,仓库提供了成体系的子文档,可作为主线之后的深入阅读路径:
- 入门:Quick Start(
uppercase_text完整示例与report.print()输出)、Core Concepts; - 评估器专题:Overview(类型选型与 Case 级评估器)、Built-in、LLM as a Judge、Custom、Span-Based、Report Evaluators;
- How-To:Dataset Management、Dataset Serialization、Concurrency & Performance、Retry Strategies、Metrics & Attributes(
increment_eval_metric/set_eval_attribute,二者在 pydantic_evals/__init__.py 中公开导出)、Case Lifecycle Hooks; - 示例:Simple Validation;仓库内还有一组可运行的端到端示例,包括数据集生成、自定义评估器、与单元测试结合、模型对比(examples/pydantic_ai_examples/evals/agent.py、custom_evaluators.py、models.py);
- API 参考:docs/api/pydantic_evals/dataset.md(Dataset 全部类、方法与配置项)。
仓库测试目录 tests/evals 提供了上述功能的测试验证,如 test_dataset.py、test_evaluators.py、test_llm_as_a_judge.py、test_online.py 等,可作为行为契约的参照。
十、小结
Pydantic Evals 的核心可以概括为三句话:用代码定义“要测什么”(Dataset + Case + Evaluator),用evaluate_sync/evaluate回答“现在怎么样”(Experiment → EvaluationReport),用终端报告或 Logfire 消费结果。它的价值在于:类型安全的数据模型、Dataset 级与 Case 级两层评估器、免费且即时的确定性检查与昂贵的 LLM 评估可自由组合、内置并发/重试/重复执行控制,以及与 OpenTelemetry/Logfire 的追踪打通——使评测既能像单元测试一样纳入工程流程,又能覆盖概率性输出所需的打分与标签维度。
【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考