pydantic-ai 中的 LLM-as-a-Judge 深度实践:LLMJudge 评估器完整指南
【免费下载链接】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 仓库中的评估子系统)提供的LLMJudge评估器展开,覆盖适用场景判断、rubric 写法、上下文控制、裁判模型选择、四种输出模式、RAG 与多 Case 差异化评估等实战用法,并结合仓库源码剖析其提示词构造与结果命名的底层机制。读完本文,你可以为 Agent 的主观质量维度(事实准确性、语气合规、RAG 接地性等)搭建一套可运行、可调试、可对比的 LLM 裁判评估流水线。
何时使用 LLM-as-a-Judge
LLMJudge评估器用一个 LLM 依据 rubric(评分准则)来评判输出的主观质量。它适合评估“需要理解和判断”的维度:
适合的场景:
- 事实准确性(Factual accuracy)
- 帮助性与相关性(Helpfulness / relevance)
- 语气与风格合规(Tone / style compliance)
- 回答完整性(Completeness)
- 复杂指令遵循(Following complex instructions)
- RAG 接地性(回答是否真正使用了提供的上下文)
- 引用准确性(Citation accuracy)
不适合的场景:
| 场景 | 应改用 |
|---|---|
| 格式/类型校验 | IsInstance(见 docs/evals/evaluators/built-in.md) |
| 精确匹配 | EqualsExpected |
| 性能检查 | MaxDuration |
| 确定性逻辑 | 编写自定义评估器(见 docs/evals/evaluators/custom.md) |
原则是:凡是用确定性检查能完成的事,就不要花一次 LLM 调用的钱与时间。
基本用法
最小示例只需一个 rubric:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge dataset = Dataset( name='factual_accuracy', cases=[Case(inputs='test')], evaluators=[ LLMJudge(rubric='Response is factually accurate'), ], )LLMJudge是一个 dataclass,其完整字段定义位于 common.py:
@dataclass(repr=False) class LLMJudge(Evaluator[object, object, object]): rubric: str model: models.Model | models.KnownModelName | str | None = None include_input: bool = False include_expected_output: bool = False model_settings: ModelSettings | None = None score: OutputConfig | Literal[False] = False assertion: OutputConfig | Literal[False] = field(default_factory=lambda: OutputConfig(include_reason=True))各参数含义如下(可直接作为配置参考表):
| 参数 | 默认值 | 说明 |
|---|---|---|
rubric | 必填 | 评判标准,用自然语言陈述一个“可真可假的命题” |
model | None(即全局默认裁判模型) | 裁判模型,可用'provider:model-id'字符串或models.Model实例 |
include_input | False | 是否把 Case 的输入一并展示给裁判 |
include_expected_output | False | 是否把期望输出展示给裁判 |
model_settings | None | 透传给 pydantic-ai 的ModelSettings,如 temperature、max_tokens |
score | False | 是否额外产出 0.0–1.0 数值分数,可配evaluation_name、include_reason |
assertion | {include_reason: True} | 默认开启,产出 bool 断言;设为False可关闭 |
Rubric 怎么写:具体、可判定
rubric定义了评判标准,务必具体、清晰。模糊的 rubric 会让裁判无从下手:
差的 rubric(过于模糊):
from pydantic_evals.evaluators import LLMJudge LLMJudge(rubric='Good response') # 太模糊 LLMJudge(rubric='Check quality') # 哪个维度的 quality?好的 rubric(具体):
LLMJudge(rubric='Response directly answers the user question without hallucination') LLMJudge(rubric='Response uses formal, professional language appropriate for business communication') LLMJudge(rubric='All factual claims in the response are supported by the provided context')一个容易被忽视的细节:从源码看(llm_as_a_judge.py),四个内置裁判 Agent 的系统提示词都要求“rubric 中的陈述为真,则输出通过测试”。也就是说 rubric 本质上是一个命题,裁判做的是真假判断,因此写成“Response is polite”这类可判定的陈述句比写成任务式指令更贴合其工作机制。
控制裁判能看到什么:上下文包含策略
通过include_input和include_expected_output两个布尔参数,可以精确控制裁判看到的信息:
from pydantic_evals.evaluators import LLMJudge # 只看输出(默认) LLMJudge(rubric='Response is polite') # 输出 + 输入 LLMJudge( rubric='Response accurately answers the input question', include_input=True, ) # 输出 + 输入 + 期望输出 LLMJudge( rubric='Response is semantically equivalent to the expected output', include_input=True, include_expected_output=True, )完整示例:用裁判判断“回答是否与期望答案语义一致”:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge dataset = Dataset( name='math_check', cases=[ Case( inputs='What is 2+2?', expected_output='4', ), ], evaluators=[ # 这个裁判能看到:output + inputs + expected_output LLMJudge( rubric='Response provides the same answer as expected, possibly with explanation', include_input=True, include_expected_output=True, ), ], )源码视角:参数如何路由到四个裁判函数
LLMJudge.evaluate按这两个开关组合,把任务分发给llm_as_a_judge.py中四个专门化的异步函数(见 common.py):
| include_input | include_expected_output | 调用的裁判函数 |
|---|---|---|
| False | False | judge_output |
| True | False | judge_input_output |
| True | True | judge_input_output_expected |
| False | True | judge_output_expected |
每个函数背后是一个独立的Agent(如_judge_output_agent),各自带有针对自己输入组合微调过的 few-shot 系统提示词。测试用例 test_evaluator_common.py 中的test_llm_judge_evaluator正是通过 mock 这 4 个函数,逐一验证了上述路由关系。
提示词本体由_build_prompt(llm_as_a_judge.py)拼装,按固定顺序输出带 XML 标签的段落:
<Input>…</Input> <Output>…</Output> <ExpectedOutput>…</ExpectedOutput> <Rubric>…</Rubric>源码注释说明了这个顺序的用意:与系统提示词中 few-shot 示例的段落顺序保持一致(rubric 作为指令放在所有上下文之后),使运行时提示词与模型“见过”的格式完全对齐。此外,非字符串内容(dict、dataclass、Pydantic 模型等)会先经_stringify序列化为 JSON(失败则回退到repr),多模态内容(图片等二进制内容类型)则按原样以UserContent形式传入,因此裁判理论上也能评判含图的输出——test_llm_as_a_judge.py 中的test_judge_binary_output_mock、test_judge_input_output_binary_content_list_mock即验证了这一点。
选择裁判模型
默认裁判模型在模块级变量中定义(llm_as_a_judge.py):
_default_model: models.Model | models.KnownModelName = 'openai:gpt-5.2'按成本/质量权衡选择模型:
from pydantic_evals.evaluators import LLMJudge # 默认:openai:gpt-5.2 LLMJudge(rubric='...') # Anthropic Claude(备选默认) LLMJudge( rubric='...', model='anthropic:claude-sonnet-4-6', ) # 轻量模型,用于简单检查 LLMJudge( rubric='Response contains profanity', model='openai:gpt-5.6-luna', ) # 能力最强的模型,用于细微的评判 LLMJudge( rubric='Response demonstrates deep understanding of quantum mechanics', model='openai:gpt-5.6-sol', )定制模型行为:ModelSettings
通过 pydantic-ai 的ModelSettings控制裁判模型行为:
from pydantic_ai import ModelSettings from pydantic_evals.evaluators import LLMJudge LLMJudge( rubric='...', model_settings=ModelSettings( temperature=0.0, # 确定性评估,提升跨次一致性 max_tokens=100, # 限制裁判回复长度 ), )model_settings会被原样透传给底层裁判 Agent 的run调用。对评估场景,temperature=0.0是强烈推荐的基线配置(原因见文末“非确定性”一节)。
输出模式:断言、分数与自定义命名
裁判模型最终返回的是GradingOutput:
class GradingOutput(BaseModel, populate_by_name=True): reason: str = Field(description='A concise 1-2 sentence justification for the verdict.') pass_: bool = Field(validation_alias='pass', serialization_alias='pass') score: float即裁判每次都会给出reason(结论理由)、pass(布尔判定)和score(0.0–1.0 数值分)。你在LLMJudge中配置的是“把这三项中的哪几项、以什么名字放进评估结果”。
仅断言(默认)
默认配置下只输出 pass/fail + reason,结果键名为LLMJudge_pass:
LLMJudge(rubric='Response is accurate') # 返回:{'LLMJudge_pass': EvaluationReason(value=True, reason='...')}在报告中呈现为:
┃ Assertions ┃ ┃ ✔ ┃仅分数
输出 0.0–1.0 的数值分(此时断言关闭,结果键名不再带_pass/_score后缀,直接叫LLMJudge_score或自定义名):
LLMJudge( rubric='Response quality', score={'include_reason': True}, assertion=False, ) # 返回:{'LLMJudge_score': EvaluationReason(value=0.85, reason='...')}报告中呈现为:
┃ Scores ┃ ┃ LLMJudge_score: 0.85 ┃同时输出分数与断言
LLMJudge( rubric='Response quality', score={'include_reason': True}, assertion={'include_reason': True}, ) # 返回:{ # 'LLMJudge_score': EvaluationReason(value=0.85, reason='...'), # 'LLMJudge_pass': EvaluationReason(value=True, reason='...'), # }从源码看(common.py),当score与assertion同时启用时,框架会自动给两者追加_score/_pass后缀以避免重名;只启用其中一种时则直接使用评估器默认名。
自定义名称
通过OutputConfig的evaluation_name重命名结果,便于报告阅读:
LLMJudge( rubric='Response is factually accurate', assertion={ 'evaluation_name': 'accuracy', 'include_reason': True, }, ) # 返回:{'accuracy': EvaluationReason(value=True, reason='...')}报告中即显示为:
┃ Assertions ┃ ┃ accuracy: ✔ ┃OutputConfig(common.py)是一个TypedDict,仅含evaluation_name(结果键名)与include_reason(是否携带reason文本)两个可选字段。include_reason=False时,结果退化为纯标量值而非EvaluationReason——test_evaluator_common.py 中my_assertion与my_score的快照差异恰好印证了这一行为。
实战示例
RAG 接地性评估
评估 RAG 系统是否真正使用了检索到的上下文:
from dataclasses import dataclass from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge @dataclass class RAGInput: question: str context: str dataset = Dataset( name='rag_evaluation', cases=[ Case( inputs=RAGInput( question='What is the capital of France?', context='France is a country in Europe. Its capital is Paris.', ), ), ], evaluators=[ LLMJudge( rubric='Response answers the question using only information from the provided context', include_input=True, assertion={'evaluation_name': 'grounded', 'include_reason': True}, ), LLMJudge( rubric='Response cites specific quotes or facts from the context', include_input=True, assertion={'evaluation_name': 'uses_citations', 'include_reason': True}, ), ], )这里RAGInput作为结构化输入传入后,会被_stringify以 JSON 形式序列化进<Input>段落,裁判能同时看到 question 和 context 两个字段。
配方生成:数据集级 + Case 级 rubric 混合
下面的例子展示数据集级评估器与 Case 级评估器的组合方式(完整可运行版本参见 examples/pydantic_ai_examples 中的示例):
from __future__ import annotations from typing import Any from pydantic import BaseModel from pydantic_ai import Agent, format_as_xml from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import IsInstance, LLMJudge class CustomerOrder(BaseModel): dish_name: str dietary_restriction: str | None = None class Recipe(BaseModel): ingredients: list[str] steps: list[str] recipe_agent = Agent( 'openai:gpt-5-mini', output_type=Recipe, instructions=( 'Generate a recipe to cook the dish that meets the dietary restrictions.' ), ) async def transform_recipe(customer_order: CustomerOrder) -> Recipe: r = await recipe_agent.run(format_as_xml(customer_order)) return r.output recipe_dataset = DatasetCustomerOrder, Recipe, Any, expected_output=None, metadata={'focus': 'vegetarian'}, evaluators=( # (1)! LLMJudge( rubric='Recipe should not contain meat or animal products', ), ), ), Case( name='gluten_free_recipe', inputs=CustomerOrder( dish_name='Chocolate Cake', dietary_restriction='gluten-free' ), expected_output=None, metadata={'focus': 'gluten-free'}, evaluators=( # (2)! LLMJudge( rubric='Recipe should not contain gluten or wheat products', ), ), ), ], evaluators=[ # (3)! IsInstance(type_name='Recipe'), LLMJudge( rubric='Recipe should have clear steps and relevant ingredients', include_input=True, model='anthropic:claude-sonnet-4-6', ), ], ) report = recipe_dataset.evaluate_sync(transform_recipe) print(report) """ Evaluation Summary: transform_recipe ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓ ┃ Case ID ┃ Assertions ┃ Duration ┃ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩ │ vegetarian_recipe │ ✔✔✔ │ 38.1s │ ├────────────────────┼────────────┼──────────┤ │ gluten_free_recipe │ ✔✔✔ │ 22.4s │ ├────────────────────┼────────────┼──────────┤ │ Averages │ 100.0% ✔ │ 30.3s │ └────────────────────┴────────────┴──────────┘ """- Case 级评估器——只在该素食菜 Case 上运行
- Case 级评估器——只在该无麸质 Case 上运行
- 数据集级评估器——对所有 Case 运行
这种分层设计的价值在于:通用维度(类型合法、步骤清晰)放在数据集级一次定义,而“素食菜不能含肉”这类只对特定 Case 成立的约束,则随 Case 携带,避免了给每个 Case 都堆砌无关 rubric。
多维度评估
用多个裁判分别覆盖不同质量维度,而不是用一个 rubric 塞进所有要求:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge dataset = Dataset( name='multi_aspect', cases=[Case(inputs='test')], evaluators=[ # 准确性 LLMJudge( rubric='Response is factually accurate', include_input=True, assertion={'evaluation_name': 'accurate'}, ), # 帮助性(用分数而非断言) LLMJudge( rubric='Response is helpful and actionable', include_input=True, score={'evaluation_name': 'helpfulness'}, assertion=False, ), # 语气 LLMJudge( rubric='Response uses professional, respectful language', assertion={'evaluation_name': 'professional_tone'}, ), # 安全性 LLMJudge( rubric='Response contains no harmful, biased, or inappropriate content', assertion={'evaluation_name': 'safe'}, ), ], )对比式评估
把实际输出与期望输出交给裁判做语义对比:
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge dataset = Dataset( name='comparative_eval', cases=[ Case( name='translation', inputs='Hello world', expected_output='Bonjour le monde', ), ], evaluators=[ LLMJudge( rubric='Response is semantically equivalent to the expected output', include_input=True, include_expected_output=True, score={'evaluation_name': 'semantic_similarity'}, assertion={'evaluation_name': 'correct_meaning'}, ), ], )最佳实践
1. rubric 尽量具体
差:
LLMJudge(rubric='Good answer')好:
LLMJudge(rubric='Response accurately answers the question without hallucinating facts')更好(多要点清单式):
LLMJudge( rubric=''' Response must: 1. Directly answer the question asked 2. Use only information from the provided context 3. Cite specific passages from the context 4. Acknowledge if information is insufficient ''', include_input=True, )2. 使用多个裁判
不要用一条 rubric 试图评估所有维度。把 “good, accurate, helpful, and safe” 拆开成多个独立裁判,每个裁判的判定边界更清晰,失败时也更容易定位是哪个维度出了问题。
3. 与确定性检查组合
from pydantic_evals.evaluators import Contains, IsInstance, LLMJudge evaluators = [ IsInstance(type_name='str'), Contains(value='required_section'), LLMJudge(rubric='Response quality is high'), ]确定性检查成本低、速度快,还可以先于 LLM 检查执行以快速失败(fail fast),省下不必要的裁判调用。
4. 使用 temperature 0 保证一致性
from pydantic_ai import ModelSettings from pydantic_evals.evaluators import LLMJudge LLMJudge( rubric='...', model_settings=ModelSettings(temperature=0.0), )局限性及缓解手段
非确定性
LLM 裁判不是确定性的,同一输出在不同运行中可能得到不同分数。缓解:使用temperature=0.0;多次评估取平均;对不稳定的评估启用重试策略。
成本
LLM 裁判每次判定都是一次真实 API 调用,产生费用与延迟。缓解:简单检查用轻量模型(如gpt-5.6-luna);先跑确定性检查快速失败;尽可能缓存结果;把评估限定在发生变化的 Case 上。
模型偏见
裁判继承训练数据中的偏见(如长度偏好、风格偏好)。缓解:用多个裁判模型对比;审查 reason 而不只看分数;用人工标注的测试集校准裁判;对已知偏见保持警觉。
上下文限制
裁判的输入有 token 上限。缓解:对长输入/输出做智能截断;使用不依赖完整上下文的聚焦 rubric;超长内容考虑分块评估。
调试 LLM 裁判
查看判定理由
from pydantic_evals import Case, Dataset from pydantic_evals.evaluators import LLMJudge def my_task(inputs: str) -> str: return f'Result: {inputs}' dataset = Dataset( name='debug_reasons', cases=[Case(inputs='test')], evaluators=[LLMJudge(rubric='Response is clear')], ) report = dataset.evaluate_sync(my_task) report.print(include_reasons=True) """ Evaluation Summary: my_task ┏━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┓ ┃ Case ID ┃ Assertions ┃ Duration ┃ ┡━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━┩ │ Case 1 │ LLMJudge: ✔ │ 10ms │ │ │ Reason: - │ │ │ │ │ │ │ │ │ │ ├──────────┼─────────────┼──────────┤ │ Averages │ 100.0% ✔ │ 10ms │ └──────────┴─────────────┴──────────┘ """输出形如:
┃ Assertions ┃ ┃ accuracy: ✔ ┃ ┃ Reason: The response │ ┃ correctly states... ┃注意:源码对 reason 有明确约束(llm_as_a_judge.py)——所有裁判 Agent 的系统提示词都要求 reason 为“简洁的 1-2 句结论理由,不得包含推理过程、自我纠正或反复核查”。test_llm_as_a_judge.py 中的test_judge_prompts_constrain_reason用FunctionModel捕获四个裁判实际下发的系统提示词,确保该指令不会在任何一个裁判中被遗漏。因此你看到的 reason 是结论而非推理链,调试 rubric 有效性时应以 reason 为线索反推裁判是否理解了你的标准。
以编程方式访问结果
report = dataset.evaluate_sync(my_task) for case in report.cases: for name, result in case.assertions.items(): print(f'{name}: {result.value}') #> LLMJudge: True if result.reason: print(f' Reason: {result.reason}') #> Reason: -对比不同裁判模型
对同一组 Case 用不同裁判模型跑一遍,交叉验证判定一致性:
judges = [ LLMJudge(rubric='Response is clear', model='openai:gpt-5.2'), LLMJudge(rubric='Response is clear', model='anthropic:claude-sonnet-4-6'), LLMJudge(rubric='Response is clear', model='openai:gpt-5-mini'), ] for judge in judges: dataset = Dataset(name='judge_comparison', cases=[Case(inputs='test')], evaluators=[judge]) report = dataset.evaluate_sync(my_task) # 对比各 report 的结果进阶:自定义默认裁判模型
若项目中所有LLMJudge都应统一使用某个裁判模型,可全局设置:
from pydantic_evals.evaluators import LLMJudge from pydantic_evals.evaluators.llm_as_a_judge import set_default_judge_model # 把默认裁判模型设为 Claude set_default_judge_model('anthropic:claude-sonnet-4-6') # 此后未显式指定 model 的 LLMJudge 实例都会默认使用 Claude LLMJudge(rubric='...')从源码看,set_default_judge_model(llm_as_a_judge.py)只是修改模块级全局变量_default_model(初始值为'openai:gpt-5.2'),四个judge_*函数在未收到显式model参数时都会回退到它。由于该变量是模块全局状态,建议在应用启动阶段设置一次,且所有实例共享这一默认值——单实例的model=参数优先级更高。
相邻能力:G-Eval 风格评估器
同一模块还提供GEval评估器(common.py)与judge_g_eval函数,实现 G-Eval(Liu et al., 2023)思路的简化版:给定criteria、显式的evaluation_steps列表与score_range(默认 1–5),裁判先做链式推理再输出区间内的整数分。其文档注释明确说明:原论文用 log-probs 对分数 token 分布计算期望,这里改为直接索要整数分,以换取 provider 无关的简洁性,代价是与人类判断的一致性略降。当你需要“1–5 整数分级 + 固定评分步骤”而非 0.0–1.0 浮点分时,可以优先考虑它。
相关资源
- 完整内置评估器参考(
IsInstance、Contains、Equals、MaxDuration等):docs/evals/evaluators/built-in.md - 编写自定义评估器:docs/evals/evaluators/custom.md
LLMJudge核心实现:pydantic_evals/pydantic_evals/evaluators/common.py- 裁判 Agent 与提示词构造:pydantic_evals/pydantic_evals/evaluators/llm_as_a_judge.py
- 评估器行为测试:tests/evals/test_llm_as_a_judge.py、tests/evals/test_evaluator_common.py
【免费下载链接】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),仅供参考