Pydantic Evals 第三方评测框架集成:把 Ragas 与 DeepEval 指标封装为自定义 Evaluator
【免费下载链接】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-ai 仓库的 第三方集成文档 与 Pydantic Evals 评测器源码,讲解如何在不引入硬依赖的前提下,把 Ragas、DeepEval 等上游评测框架的具体指标封装为 Pydantic Evals 的自定义Evaluator,并复用数据集、报告与结果管线。读完本文,你可以自行实现与上游框架"逐分对齐"的评测适配器,并理解框架底层如何执行、校验和记录这些评测结果。
为什么需要第三方框架集成
Pydantic Evals 不对任何特定指标框架做硬依赖。当团队已经在用 Ragas、DeepEval 或其他评分库时,Evaluator基类提供了一个轻量入口:把上游指标包一层适配器,就能在任何 Pydantic Evals 数据集里运行。这一点在 pyproject.toml 中可以直接验证:pydantic-evals的动态依赖只有rich、logfire-api、pydantic、pydantic-ai-slim、anyio、pyyaml,ragas和deepeval既不在核心依赖里,也不在任何可选依赖组中(可选组仅logfire)。
但原文档给出了一条重要决策建议,写适配器之前应先确认:
优先使用原生评测器。如果基于评分细则(rubric)的
LLMJudge(见标准质量指标页面提供的现成 rubric)或自定义评测器已能覆盖你的场景,那通常更简单——零额外依赖,分数还能干净地落入报告。只有当你确实需要上游实现的精确行为时才使用下述集成:与已发表基准可复现、与现有评测套件保持口径一致、或上游框架有原生侧未暴露的能力。同一数据集内可以混合外部评测器和原生评测器。
集成模式:三步封装上游指标
每个框架集成遵循同一模式:
- 继承
Evaluator基类; - 把
ctx.inputs、ctx.output、ctx.expected_output和元数据适配成上游指标所需的输入结构; - 返回
float分数、bool断言、EvaluationReason,或这些类型的dict。
官方给出的适配器示例刻意保持紧凑——你可以按需扩展模型选择、阈值、按 case 开关等团队所需的任何配置。
Evaluator 基类与返回值类型
封装的契约来自 evaluator.py 中的Evaluator:
- 子类必须实现
evaluate,可以用def(同步)也可以用async def(异步),框架自动兼容两种形式; - 允许的返回类型由
EvaluatorOutput定义(evaluator.py#L49):EvaluationScalar | EvaluationReason | Mapping[str, ...]; EvaluationScalar = bool | int | Annotated[float, Field(allow_inf_nan=False)] | str(evaluator.py#L27):int和有限float视为分数,str视为标签,bool视为断言。注意float被显式约束为有限值——返回NaN或±inf会在执行期触发EvaluatorFailure而不是静默记录;EvaluationReason(evaluator.py#L34-L46)携带标量value和可选的reason解释,是封装上游指标最推荐的返回形式,因为上游框架的打分理由可以被保留到报告中。
另外,Evaluator继承的BaseEvaluator(_base.py)带有一个严格的元类_StrictABCMeta:如果你的子类没有实现evaluate,会在类定义时就抛TypeError,而不是等到实例化才报错。BaseEvaluator.as_spec()还会把评测器序列化为EvaluatorSpec(spec.py),默认值字段会被自动剔除,这让你的适配器能像内置评测器一样被写入 YAML/JSON 数据集定义。
EvaluatorContext:适配器能拿到的数据
适配层的全部输入来自 EvaluatorContext,字段包括:
ctx.name:case 名称;ctx.inputs/ctx.output:任务输入与实际输出;ctx.metadata:case 元数据(可为None,适配器里通常要先判空);ctx.expected_output:期望输出(可为None);ctx.duration、ctx.metrics、ctx.attributes、ctx.span_tree:执行耗时、自定义指标/属性、OpenTelemetry span 树等,可用于行为类检查,但对纯文本评分指标一般用不到。
Ragas:封装 Faithfulness 指标
ragas需单独安装(pip install ragas,不包含在pydantic-evals中)。下面的适配器把ragas.metrics.Faithfulness包装成单轮(single-turn)评分。每个 case 需要在 inputs 或 metadata 中提供检索到的上下文:
from dataclasses import dataclass from ragas.dataset_schema import SingleTurnSample from ragas.metrics import Faithfulness from pydantic_evals.evaluators import EvaluationReason, Evaluator, EvaluatorContext @dataclass class RagasFaithfulness(Evaluator): """Wrap `ragas.metrics.Faithfulness` as a Pydantic Evals evaluator.""" context_field: str = 'context' async def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason: metadata = ctx.metadata or {} retrieved_contexts = metadata.get(self.context_field, []) if isinstance(retrieved_contexts, str): retrieved_contexts = [retrieved_contexts] sample = SingleTurnSample( user_input=str(ctx.inputs), response=str(ctx.output), retrieved_contexts=retrieved_contexts, ) metric = Faithfulness() score = await metric.single_turn_ascore(sample) return EvaluationReason(value=float(score), reason=f'ragas.Faithfulness = {score:.3f}')设计要点:
context_field是 dataclass 字段(默认'context'),让上下文的存放键名可配置;- 用
ctx.metadata or {}防御metadata为None的情况(EvaluatorContext.metadata的类型注解允许None); - 兼容上下文是单个字符串的写法,统一转成列表;
- 因为 Ragas 提供异步评分入口
single_turn_ascore,evaluate用async def实现,避免阻塞事件循环; - 返回
EvaluationReason并把分数写进reason,让报告里既有序数分也有解释文本。
使用方式与内置评测器完全一致:
from pydantic_evals import Case, Dataset dataset = Dataset( name='rag_eval', cases=[ Case( inputs='What is the capital of France?', metadata={'context': ['Paris is the capital of France.']}, ), ], evaluators=[RagasFaithfulness()], )同一模式适用于ragas.metrics.answer_relevancy、context_precision等其他评分指标:换成对应的 metric 类,并按需要替换SingleTurnSample的字段即可。
DeepEval:封装 GEval 指标
deepeval同样需单独安装(pip install deepeval,不包含在pydantic-evals中)。这个适配器把 DeepEval 的GEval指标封装为按准则(criteria)对LLMTestCase打分。由于 DeepEval 的measure是同步的,评测器也用同步实现:
from dataclasses import dataclass from deepeval.metrics import GEval from deepeval.test_case import LLMTestCase, LLMTestCaseParams from pydantic_evals.evaluators import EvaluationReason, Evaluator, EvaluatorContext @dataclass class DeepEvalGEval(Evaluator): """Wrap `deepeval.metrics.GEval` as a Pydantic Evals evaluator.""" metric_name: str criteria: str threshold: float = 0.5 def evaluate(self, ctx: EvaluatorContext) -> dict[str, float | bool | EvaluationReason]: test_case = LLMTestCase( input=str(ctx.inputs), actual_output=str(ctx.output), expected_output=None if ctx.expected_output is None else str(ctx.expected_output), ) metric = GEval( name=self.metric_name, criteria=self.criteria, evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT], threshold=self.threshold, ) metric.measure(test_case) return { f'{self.metric_name}_score': EvaluationReason(value=float(metric.score), reason=metric.reason or ''), f'{self.metric_name}_pass': bool(metric.success), }设计要点:
metric_name同时作为 DeepEval 指标名和输出前缀,一个适配器实例产出一对结果:{name}_score(带理由的分数)和{name}_pass(是否过阈值);threshold(默认0.5)对应 DeepEval 的通过线,metric.success被映射为布尔断言列;expected_output is None时显式传None给LLMTestCase,避免把 Python 的None变成字符串"None"。
同一个包装器可以复用给 DeepEval 的FaithfulnessMetric、AnswerRelevancyMetric、HallucinationMetric等:换成对应的 metric 类,并填充相关的LLMTestCase字段(例如 faithfulness 需要retrieval_context)。
框架如何执行你的适配器:源码视角
理解执行管线能帮你确认适配器"够用"的边界。run_evaluator 是入口:
- 调用
evaluator.evaluate_async(ctx)——同步实现会原样返回结果,异步实现被await(见 Evaluator.evaluate_async),所以 Ragas 的async def和 DeepEval 的def写法各自天然合适; - 传入可选的
RetryConfig时,会用 tenacity 重试包裹evaluate_async,网络类指标(两个框架都要自调 LLM)可因此获得重试能力; - 原始返回值经过 Pydantic
TypeAdapter以revalidate_instances='always'重新校验(_run_evaluator.py#L110-L114),返回非法类型会转为ValueError; - 标量结果会被包进映射,键名取
get_default_evaluation_name()(默认即类名,如RagasFaithfulness);返回dict时则以 dict 的键作为各结果名(如DeepEvalGEval的{name}_score/{name}_pass); - 任何异常都被捕获为
EvaluatorFailure(含error_message、error_stacktrace、error_type),记为该 case 的评测器失败,而不是让整个数据集跑挂; - 每个评测器调用都被包在名为
evaluator: {evaluator_name}的 logfire span 里,方便在 Logfire 中按评测器查询。
此外,Evaluator还提供get_evaluator_version()(默认返回None):当你的适配器行为变化(换了上游指标版本、改了阈值语义)时,可以覆写它打一个版本标签(如'v2'),在线评测看板可以据此过滤掉旧版本的结果而无需删除历史数据。
依赖与运行成本注意事项
原文档的"依赖说明"部分要点如下,可结合仓库证据确认:
ragas和deepeval都是可选依赖——不随pydantic-evals安装,也不属于任何依赖组。只在真正使用这些集成的项目中安装,避免污染所有评测环境的依赖树(pyproject.toml 中optional-dependencies仅有logfire);- 这两个库会发起自己的 LLM 调用,与你的数据集任务执行相互独立。运行包含这些评测器的数据集时,要预留出额外的 API 用量和时长预算;
- 适配器本身是普通 dataclass,
@dataclass装饰器是必需的——_StrictABCMeta要求在类定义时完成evaluate的实现检查。
选型速查
| 需求 | 建议 |
|---|---|
| 评分细则能覆盖、零额外依赖 | 原生LLMJudgerubric 或GEval(标准质量指标) |
| 需要与上游框架精确对齐(可复现、口径一致) | 本文的 Ragas / DeepEval 适配器模式 |
| 领域特化逻辑、外部 API 校验等 | 自定义评测器 |
同一数据集可以混合以上评测器:原生评测器负责常规模块,第三方适配器负责必须逐分对齐的指标,结果统一进入同一份报告与同一套失败/版本标记体系。
【免费下载链接】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),仅供参考