Agent在无效重试?Verification Loop评分器设计:5套Rubric+3种Judge(2026版·附代码)
金句:Grader = Verification Loop 的灵魂。Generator 负责写,Grader 负责判------裁判不行,写再多也是白烧 token。
2026年了,CSDN上关于 Verification Loop 的文章还在说"加个评分器就行"。但没人告诉你 Rubric 怎么写、分数怎么打、不同场景该用哪种评分器。一个模糊的"好/不好"会让 Agent 在"重试→还是不好→再重试"里无限循环------token 烧光了,产出为零。
这篇给你 5 个开箱即用的 Rubric 模板 (代码生成/文档摘要/对话回复/事实核查/创意写作)+ 3 种 LLM-as-a-Judge 评分模式 (二元/数值/对比)+ 完整可运行的 Python 代码。看完直接复制,把 Grader 从"黑盒"变成工程组件。
前置条件与版本说明
⚠️ 本文环境 :Python 3.10+,LLM API(OpenAI兼容接口)
⚠️ 适用范围 :Rubric模板和Judge代码仅依赖Python标准库(
json、dataclasses、typing),无需外部依赖。生产环境中的LLM Judge需安装OpenAI SDK。⚠️ 生产环境提示:生产级Grader建议使用GPT-4o或Claude 3.5等前沿模型。研究表明,8B以下的小模型在人工评分一致性上存在显著差距,可能导致Verification Loop在错误方向收敛。
bash
# 安装指定版本(推荐锁定版本以保持环境一致性)
pip install openai==1.55.0
# 验证安装
python -c "import openai; print(openai.__version__)"
# 预期输出:1.55.0
# 验证Python版本(需3.10+)
python --version
# 预期输出:Python 3.10.x 或更高
文章目录
- [Agent在无效重试?Verification Loop评分器设计:5套Rubric+3种Judge(2026版·附代码)](#Agent在无效重试?Verification Loop评分器设计:5套Rubric+3种Judge(2026版·附代码))
-
- 前置条件与版本说明
- [一、Grader评分器是Verification Loop的"核心引擎"](#一、Grader评分器是Verification Loop的"核心引擎")
-
- [1.1 Grader质量决定Verification Loop的生死](#1.1 Grader质量决定Verification Loop的生死)
- [1.2 好的Grader评分器的标准](#1.2 好的Grader评分器的标准)
- 二、五种场景的Rubric模板(直接复制使用)
-
- [2.1 场景一:代码生成](#2.1 场景一:代码生成)
- [2.2 场景二:文档摘要](#2.2 场景二:文档摘要)
- [2.3 场景三:对话回复](#2.3 场景三:对话回复)
- [2.4 场景四:事实核查](#2.4 场景四:事实核查)
- [2.5 场景五:创意写作](#2.5 场景五:创意写作)
- 三、三种LLM-as-a-Judge评分模式
-
- [3.1 模式一:二元判断(Pass/Fail)](#3.1 模式一:二元判断(Pass/Fail))
- [3.2 模式二:数值打分(1-5分)](#3.2 模式二:数值打分(1-5分))
- [3.3 模式三:对比排序(Pairwise Comparison)](#3.3 模式三:对比排序(Pairwise Comparison))
- [3.4 三种Judge模式的选型对比](#3.4 三种Judge模式的选型对比)
- 四、Grader评分器的迭代优化
-
- [4.1 评分器校准(Judge Calibration)](#4.1 评分器校准(Judge Calibration))
- [4.2 评分器版本管理](#4.2 评分器版本管理)
- [五、与Verification Loop的整合:完整代码](#五、与Verification Loop的整合:完整代码)
- 六、回到开篇那句话
- 七、适用边界与限制条件
一、Grader评分器是Verification Loop的"核心引擎"
1.1 Grader质量决定Verification Loop的生死
我在《Loop Engineering四层架构》中介绍了Level 2 Verification Loop的核心架构:
Generator(Agent生成输出)→ Grader(评分)→ 达标?→ 是:返回 / 否:反馈+重试
Grader评分器是这个循环的"裁判"。如果裁判标准模糊,循环就会发散:
| Grader问题 | 表现 | 后果 |
|---|---|---|
| 标准模糊 | "输出不够好" | Agent不知道哪里改,重试没有方向 |
| 标准漂移 | 每次评分标准不一致 | 相同输入得分不同,结果不稳定 |
| 标准与场景不匹配 | 用代码评分标准评文档 | 评分失真,Agent被错误反馈误导 |
| 过于严格 | 要求100%完美 | 永远不达标,无限循环,token烧光 |
| 过于宽松 | 及格线太低 | 质量达不到要求,Verification Loop形同虚设 |
我自己就踩过这个坑。之前做一个客服Agent项目,Generator已经写得不错了,但Grader只写了三行Prompt:"判断一下回复好不好,不好就重来" 。上线后一看日志------同一个用户问题,Agent有时重试1次就过,有时重试5次还在循环。不是Generator不行,是裁判自己都不知道什么叫"好"。后来把Rubric拆成相关性/有用性/礼貌度/一致性/安全性五个维度,循环次数从平均3.2次降到了1.4次。
这就是本文要解决的:把Grader从玄学变成工程。
1.2 好的Grader评分器的标准
一个工程级的Grader评分器必须满足:
| 属性 | 要求 | 反例 |
|---|---|---|
| 可量化 | 每个维度有明确分数(1-5或0-100) | "还行"→无法判断 |
| 可解释 | 分数背后有具体理由 | "4分因为缺少错误处理"→Agent知道改哪里 |
| 场景化 | 不同任务类型用不同Rubric | 代码和文档用同一标准→不精准 |
| 一致性 | 相同输入多次评分,结果稳定 | 今天3分,明天5分→无法收敛 |
| 可迭代 | 根据错误率调整评分标准 | 固定标准不变→无法适应业务变化 |
二、五种场景的Rubric模板(直接复制使用)
2.1 场景一:代码生成
适用Agent:代码助手、代码生成Agent、测试用例生成Agent
python
CODE_GENERATION_RUBRIC = {
"dimensions": [
{
"name": "语法正确性",
"weight": 0.25,
"criteria": [
"代码能否直接运行而不报错",
"是否使用了正确的语法结构(如正确的缩进、括号匹配)",
"是否引用了正确的库/模块"
],
"scoring": {
5: "代码完全可运行,语法100%正确",
4: "基本可运行,有小语法问题(如缺少一个分号)",
3: "有语法错误但结构基本正确,修改后可用",
2: "语法错误较多,需要大幅修改",
1: "完全无法运行,语法混乱"
}
},
{
"name": "功能完整性",
"weight": 0.30,
"criteria": [
"是否实现了用户要求的全部功能",
"是否处理了所有边界条件(如空输入、异常值)",
"是否包含必要的输入校验"
],
"scoring": {
5: "完全实现了所有功能,包括边界条件处理",
4: "实现了核心功能,缺少1-2个边界处理",
3: "实现了大部分功能,有2-3个遗漏",
2: "只实现了部分功能,明显不完整",
1: "完全没有实现用户要求的功能"
}
},
{
"name": "代码质量",
"weight": 0.20,
"criteria": [
"是否有清晰的注释和文档字符串",
"变量命名是否清晰(语义化,非a/b/c)",
"代码结构是否模块化(函数拆分合理)"
],
"scoring": {
5: "注释完整、命名清晰、模块化良好",
4: "基本满足,个别命名不够清晰",
3: "有注释但不够,结构尚可",
2: "缺少注释,命名混乱",
1: "完全没有注释,一团糟"
}
},
{
"name": "安全性",
"weight": 0.15,
"criteria": [
"是否存在SQL注入/命令注入等漏洞",
"是否硬编码了密码/密钥等敏感信息",
"是否处理了异常避免信息泄露"
],
"scoring": {
5: "无安全漏洞,异常处理完善",
4: "有小问题但不严重",
3: "有潜在风险,需要检查",
2: "有明显的安全漏洞",
1: "严重安全风险,不可使用"
}
},
{
"name": "性能",
"weight": 0.10,
"criteria": [
"算法复杂度是否合适",
"是否使用了不必要的嵌套循环"
],
"scoring": {
5: "最优或次优算法,无性能问题",
4: "基本可接受,有优化空间",
3: "有性能隐患但不严重",
2: "明显性能问题(如O(n²)可优化为O(n))",
1: "严重性能问题(如死循环、无限递归)"
}
}
],
"threshold": 3.5, # 综合分>=3.5视为通过
"pass_condition": "所有维度>=2分且综合分>=3.5"
}
2.2 场景二:文档摘要
适用Agent:文档总结Agent、会议纪要生成Agent、论文摘要Agent
python
DOCUMENT_SUMMARY_RUBRIC = {
"dimensions": [
{
"name": "内容完整性",
"weight": 0.35,
"criteria": [
"是否涵盖了原文的核心要点",
"是否遗漏了关键信息",
"是否保留了原文的结论/建议"
],
"scoring": {
5: "所有核心要点都涵盖,无遗漏",
4: "大部分要点涵盖,遗漏1-2个次要信息",
3: "核心要点有覆盖,但遗漏了部分细节",
2: "只涵盖了一半左右的内容",
1: "完全遗漏了核心内容"
}
},
{
"name": "准确性",
"weight": 0.30,
"criteria": [
"是否歪曲了原文的意思",
"是否添加了原文没有的信息(幻觉)",
"数据和事实是否与原文一致"
],
"scoring": {
5: "100%准确,无扭曲、无幻觉",
4: "基本准确,有细微偏差",
3: "有1-2个不准确的地方",
2: "有明显的误解或错误",
1: "严重歪曲原文"
}
},
{
"name": "简洁性",
"weight": 0.20,
"criteria": [
"是否去除了冗余信息",
"语言是否精炼(没有重复表达)",
"长度是否合适(不过长也不过短)"
],
"scoring": {
5: "极其精炼,无一字冗余",
4: "很简洁,偶有冗余表达",
3: "基本简洁,有一些冗余",
2: "比较啰嗦,大量冗余",
1: "完全没有精简,几乎是原文复制"
}
},
{
"name": "可读性",
"weight": 0.15,
"criteria": [
"结构是否清晰(分段、分点)",
"语言是否流畅",
"是否使用了原文没有的术语而没有解释"
],
"scoring": {
5: "结构清晰,语言流畅,易于理解",
4: "结构尚可,语言基本流畅",
3: "结构一般,语言有些生硬",
2: "结构混乱,难以理解",
1: "完全不可读"
}
}
],
"threshold": 3.5,
"pass_condition": "准确性>=3分且综合分>=3.5"
}
2.3 场景三:对话回复
适用Agent:客服Agent、聊天助手、问答Agent
python
CONVERSATION_RUBRIC = {
"dimensions": [
{
"name": "相关性",
"weight": 0.30,
"criteria": [
"是否直接回答了用户的问题",
"是否没有偏离话题",
"是否理解了用户的意图(而非字面意思)"
],
"scoring": {
5: "完全理解意图,精准回答",
4: "基本理解,回答有轻微偏差",
3: "回答了但不够精准",
2: "答非所问或部分相关",
1: "完全无关"
}
},
{
"name": "有用性",
"weight": 0.25,
"criteria": [
"是否提供了解决方案(而非只是承认问题)",
"是否提供了可操作的建议",
"是否提供了必要的背景信息"
],
"scoring": {
5: "完美解决用户问题,提供了完整方案",
4: "基本解决了问题,有小遗漏",
3: "部分解决,需要用户进一步追问",
2: "没有实质帮助",
1: "完全没用"
}
},
{
"name": "礼貌度",
"weight": 0.15,
"criteria": [
"语气是否友好、尊重",
"是否使用了恰当的称谓",
"是否避免了指责性语言"
],
"scoring": {
5: "非常礼貌,语气友好专业",
4: "礼貌,偶尔生硬",
3: "基本礼貌",
2: "语气生硬或不耐烦",
1: "粗鲁或不尊重"
}
},
{
"name": "一致性",
"weight": 0.15,
"criteria": [
"是否与之前对话中的信息一致",
"是否没有自相矛盾",
"是否保持了人设/角色一致"
],
"scoring": {
5: "完全与上下文一致,无矛盾",
4: "基本一致,有微小不一致",
3: "有1-2处不一致",
2: "多处不一致",
1: "完全自相矛盾"
}
},
{
"name": "安全性",
"weight": 0.15,
"criteria": [
"是否泄露了隐私或敏感信息",
"是否提供了有害或不当建议",
"是否遵守了安全策略"
],
"scoring": {
5: "完全安全,无风险",
4: "有小问题但不严重",
3: "有潜在风险",
2: "有安全问题",
1: "严重安全违规"
}
}
],
"threshold": 3.5,
"pass_condition": "安全性>=3分且综合分>=3.5"
}
2.4 场景四:事实核查
适用Agent:事实检查Agent、知识问答Agent、内容审核Agent
python
FACT_CHECKING_RUBRIC = {
"dimensions": [
{
"name": "事实准确性",
"weight": 0.50,
"criteria": [
"陈述是否与权威来源一致",
"数据是否准确(无幻觉)",
"引用是否可追溯"
],
"scoring": {
5: "100%准确,有权威来源支持",
4: "基本准确,来源可靠",
3: "有少量不准确,但不影响结论",
2: "有明显错误",
1: "完全错误或虚构"
}
},
{
"name": "来源可信度",
"weight": 0.25,
"criteria": [
"引用的来源是否权威(如政府网站、学术期刊)",
"是否引用了最新信息",
"是否说明了信息来源"
],
"scoring": {
5: "来源权威、最新、明确标注",
4: "来源基本可靠",
3: "来源一般,缺少权威性",
2: "来源不可靠或过时",
1: "无来源或来源不可信"
}
},
{
"name": "平衡性",
"weight": 0.25,
"criteria": [
"是否呈现了多个视角",
"是否区分了事实与观点",
"是否说明了信息的不确定性"
],
"scoring": {
5: "多视角、事实观点清晰、标注不确定",
4: "基本平衡,有小偏差",
3: "有偏见但不严重",
2: "有明显偏见",
1: "完全片面"
}
}
],
"threshold": 4.0, # 事实核查标准更高
"pass_condition": "事实准确性>=4分且综合分>=4.0"
}
2.5 场景五:创意写作
适用Agent:营销文案Agent、故事生成Agent、邮件撰写Agent
python
CREATIVE_WRITING_RUBRIC = {
"dimensions": [
{
"name": "创意性",
"weight": 0.25,
"criteria": [
"是否有新颖的视角或表达方式",
"是否避免了陈词滥调",
"是否提供了独特的价值"
],
"scoring": {
5: "极具创意,令人耳目一新",
4: "有创意,偶尔有新意",
3: "中规中矩,有少量新意",
2: "比较常规,缺乏新意",
1: "完全公式化"
}
},
{
"name": "符合需求",
"weight": 0.30,
"criteria": [
"是否符合用户指定的风格/语气",
"是否包含了用户要求的必要元素",
"长度是否合适"
],
"scoring": {
5: "完美符合所有要求",
4: "基本符合,有1-2个小偏差",
3: "大部分符合,有遗漏",
2: "有明显不符",
1: "完全不符合要求"
}
},
{
"name": "语言质量",
"weight": 0.25,
"criteria": [
"是否流畅、有节奏感",
"是否有语法或拼写错误",
"词汇是否丰富且准确"
],
"scoring": {
5: "语言优美,无错误",
4: "语言流畅,有小错误",
3: "基本通顺,有些生硬",
2: "有明显错误或不流畅",
1: "完全不可读"
}
},
{
"name": "情感共鸣",
"weight": 0.20,
"criteria": [
"是否触动了目标受众",
"是否引发了预期的情感反应",
"是否使用了恰当的修辞手法"
],
"scoring": {
5: "强烈的情感共鸣",
4: "有情感感染力",
3: "有一定感染力",
2: "情感平淡",
1: "完全没有情感"
}
}
],
"threshold": 3.5,
"pass_condition": "综合分>=3.5"
}
三、三种LLM-as-a-Judge评分模式
LLM-as-a-Judge 翻译成人话就是:让AI自己当考官。你写好Rubric(评分标准),把Agent输出扔给GPT-4o或Claude,让它按标准打分。省人工,速度快,但选错了Judge模式就会------裁判乱判,Agent瞎改。
根据任务类型和成本要求,可以选择不同的LLM评判模式:
3.1 模式一:二元判断(Pass/Fail)
适用场景:要求简单、速度快、成本低
python
class BinaryJudge:
"""二元判断:只返回通过/不通过。"""
def __init__(self, llm_client):
self.llm = llm_client
def evaluate(self, user_input: str, agent_output: str, rubric: dict) -> dict:
"""二元判断:是否通过。"""
prompt = f"""你是一个严格的评分员。请根据以下标准判断Agent输出是否合格。
标准:{rubric['pass_condition']}
用户输入:{user_input}
Agent输出:{agent_output}
只回答"通过"或"不通过",并简要说明理由。"""
response = self.llm.invoke(prompt)
content = response.content.lower()
passed = "通过" in content or "pass" in content or "合格" in content
return {
"passed": passed,
"reasoning": response.content,
"score": 1 if passed else 0
}
# 使用示例
# judge = BinaryJudge(llm_client=your_llm_client)
# result = judge.evaluate("写一个排序函数", "def sort(arr): return sorted(arr)", CODE_GENERATION_RUBRIC)
# print(result["passed"]) # 预期输出:True 或 False
3.2 模式二:数值打分(1-5分)
适用场景:需要量化评估、多维评分
python
class NumericJudge:
"""数值打分:按维度评分,加权综合。"""
def __init__(self, llm_client):
self.llm = llm_client
def evaluate(self, user_input: str, agent_output: str, rubric: dict) -> dict:
"""按Rubric维度打分。"""
dimensions = rubric["dimensions"]
scores = {}
import json
for dim in dimensions:
prompt = f"""请对以下Agent输出在"{dim['name']}"维度进行评分(1-5分)。
评分标准:
{chr(10).join(f"{k}分: {v}" for k, v in dim['scoring'].items())}
检查项:
{chr(10).join(f"- {c}" for c in dim['criteria'])}
用户输入:{user_input}
Agent输出:{agent_output}
请以JSON格式输出:{{"score": 1-5, "reasoning": "具体理由"}}"""
response = self.llm.invoke(prompt)
try:
result = json.loads(response.content)
scores[dim['name']] = {
"score": result.get("score", 3),
"reasoning": result.get("reasoning", ""),
"weight": dim['weight']
}
except:
scores[dim['name']] = {"score": 3, "reasoning": "解析失败", "weight": dim['weight']}
# 计算加权综合分
weighted_score = sum(
s["score"] * s["weight"] for s in scores.values()
) / sum(s["weight"] for s in scores.values())
passed = weighted_score >= rubric.get("threshold", 3.5)
return {
"passed": passed,
"weighted_score": round(weighted_score, 2),
"dimension_scores": scores,
"threshold": rubric.get("threshold", 3.5)
}
# 使用示例
# judge = NumericJudge(llm_client=your_llm_client)
# result = judge.evaluate("总结这篇文章", "本文主要讨论了...", DOCUMENT_SUMMARY_RUBRIC)
# print(result["weighted_score"]) # 预期输出:3.0~5.0 之间的浮点数
# print(result["dimension_scores"]["内容完整性"]["score"]) # 预期输出:1~5 整数
3.3 模式三:对比排序(Pairwise Comparison)
Pairwise Comparison 翻译成人话就是:让AI当裁判搞PK赛。不直接打绝对分数("这个7分"),而是把两个输出放一起比------A和B哪个更好?多轮PK下来,胜出的就是最佳输出。比分数的绝对值更稳定。
适用场景:需要比较多个候选输出,选择最佳
python
class PairwiseJudge:
"""对比排序:两两比较选择更好的输出。"""
def __init__(self, llm_client):
self.llm = llm_client
def compare(self, user_input: str, output_a: str, output_b: str, criteria: str) -> dict:
"""比较两个输出,返回哪个更好。"""
prompt = f"""请比较以下两个Agent输出,判断哪个更符合要求。
用户输入:{user_input}
要求:{criteria}
=== 输出A ===
{output_a}
=== 输出B ===
{output_b}
请回答:
1. 哪个更好?(A/B/平手)
2. 具体理由(每个输出各1-2点优缺点)
格式:A更好/ B更好/ 平手"""
response = self.llm.invoke(prompt)
content = response.content
if "A更好" in content or "A is better" in content:
winner = "A"
elif "B更好" in content or "B is better" in content:
winner = "B"
else:
winner = "tie"
return {
"winner": winner,
"reasoning": content
}
def select_best(self, user_input: str, outputs: list, criteria: str) -> dict:
"""从多个输出中选择最佳(锦标赛排序)。"""
if len(outputs) == 1:
return {"best": outputs[0], "index": 0}
# 简单锦标赛:两两比较
best = outputs[0]
best_idx = 0
for i, output in enumerate(outputs[1:], 1):
result = self.compare(user_input, best, output, criteria)
if result["winner"] == "B":
best = output
best_idx = i
return {"best": best, "index": best_idx, "comparisons": len(outputs) - 1}
# 使用示例
# judge = PairwiseJudge(llm_client=your_llm_client)
# result = judge.compare("写一个排序函数", "A方案代码", "B方案代码", criteria="代码简洁、性能好")
# print(result["winner"]) # 预期输出:A 或 B 或 tie
# 多候选场景(锦标赛排序):
# best = judge.select_best("写一个排序函数", [output1, output2, output3], criteria="代码质量高")
# print(best["index"]) # 预期输出:最佳输出的索引
3.4 三种Judge模式的选型对比
不同Judge模式不是"谁更好"的关系,而是"谁更合适"的问题。以下是选型决策参考:
| 维度 | 二元判断(Binary) | 数值打分(Numeric) | 对比排序(Pairwise) |
|---|---|---|---|
| 输出粒度 | Pass/Fail(0/1) | 1-5分各维度 + 加权综合 | 孰优孰劣(A/B/平手) |
| LLM调用次数 | 每次评估1次 | 每次评估 N次(N=维度数) | M个候选需M-1次比较 |
| 单次评估成本 | 最低 | 中等(维度越多越贵) | 较高(候选越多越贵) |
| 反馈精细度 | 只有"过/不过"结论 | 各维度得分 + 优化方向 | 相对优劣 + 优缺点分析 |
| 适合候选数 | 单个输出 | 单个输出 | 2~10个候选 |
| 应用场景 | QA筛选、内容审核、快速准入 | 代码质量、文档摘要、对话评估 | 模型选型、Prompt调优、A/B测试 |
| 扩展性 | 无法区分好坏程度 | 可调整维度权重适应业务 | 候选增多时成本线性增长 |
选型建议:
- 快速筛选(如内容审核)→ BinaryJudge:成本最低,一句话决策
- 精细评估(如代码Review)→ NumericJudge:多维量化,知道哪里需要改进
- 最优选择(如Prompt调优)→ PairwiseJudge:相对比较比绝对评分更稳定
四、Grader评分器的迭代优化
Grader不是一次设计就一劳永逸的。需要根据实际错误案例不断调整:
4.1 评分器校准(Judge Calibration)
问题:Grader评分与人类判断不一致怎么办?
解决方案:
python
class JudgeCalibrator:
"""评分器校准:通过人工标注数据调整评分标准。"""
def __init__(self, judge: NumericJudge):
self.judge = judge
self.calibration_data = [] # (input, output, human_score, llm_score)
def add_calibration_case(self, user_input: str, agent_output: str,
human_score: float, llm_score: float):
"""添加校准数据。"""
self.calibration_data.append({
"user_input": user_input,
"agent_output": agent_output,
"human_score": human_score,
"llm_score": llm_score
})
def calculate_bias(self) -> float:
"""计算LLM评分相对于人工评分的偏差。"""
if not self.calibration_data:
return 0.0
biases = [d["llm_score"] - d["human_score"] for d in self.calibration_data]
return sum(biases) / len(biases)
def calibrate_score(self, raw_score: float) -> float:
"""校准分数:消除系统性偏差。"""
bias = self.calculate_bias()
return max(1.0, min(5.0, raw_score - bias))
def suggest_rubric_updates(self) -> list:
"""根据校准数据建议Rubric更新。"""
# 分析哪些维度LLM评分与人工评分差距最大
suggestions = []
# 简化:如果LLM系统性偏高,建议提高threshold
bias = self.calculate_bias()
if bias > 0.5:
suggestions.append(f"LLM评分系统性偏高{bias:.1f}分,建议提高threshold {bias:.1f}分")
elif bias < -0.5:
suggestions.append(f"LLM评分系统性偏低{abs(bias):.1f}分,建议降低threshold {abs(bias):.1f}分")
return suggestions
# 使用示例
# calibrator = JudgeCalibrator(judge=NumericJudge(llm_client))
# calibrator.add_calibration_case("总结文本", "输出结果", human_score=4.0, llm_score=4.5)
# bias = calibrator.calculate_bias() # 预期输出:0.5(LLM偏高0.5分)
# calibrated = calibrator.calibrate_score(raw_score=4.5) # 预期输出:4.0
4.2 评分器版本管理
python
from datetime import datetime
class GraderVersionManager:
"""Grader评分器版本管理。"""
def __init__(self):
self.versions = {}
self.current_version = "v1.0"
def register_version(self, version_id: str, rubric: dict,
judge_class: str, metadata: dict):
"""注册一个Grader版本。"""
self.versions[version_id] = {
"rubric": rubric,
"judge_class": judge_class,
"metadata": metadata,
"created_at": datetime.now().isoformat()
}
def compare_versions(self, version_a: str, version_b: str,
test_cases: list) -> dict:
"""比较两个版本的Grader在同一测试集上的表现。"""
# 返回哪个版本更严格/更一致
pass
# 使用示例
# manager = GraderVersionManager()
# manager.register_version("v1.0", CODE_GENERATION_RUBRIC, "NumericJudge", {"author": "admin"})
# manager.register_version("v2.0", CODE_GENERATION_RUBRIC_V2, "NumericJudge", {"author": "admin"})
# print(manager.current_version) # 预期输出:v1.0
五、与Verification Loop的整合:完整代码
python
class VerificationLoopWithGrader:
"""
带Grader的Verification Loop完整实现。
"""
def __init__(self, generator_func, judge: NumericJudge,
rubric: dict, max_retries: int = 3):
self.generator = generator_func
self.judge = judge
self.rubric = rubric
self.max_retries = max_retries
async def run(self, user_input: str) -> dict:
"""运行带验证的Agent循环。"""
best_result = None
best_score = 0
for attempt in range(self.max_retries):
print(f"\n=== 生成尝试 {attempt + 1}/{self.max_retries} ===")
# 1. Agent生成输出
output = self.generator(user_input)
print(f"生成输出: {output[:100]}...")
# 2. Grader评分
evaluation = self.judge.evaluate(user_input, output, self.rubric)
score = evaluation["weighted_score"]
passed = evaluation["passed"]
print(f"评分: {score}/5.0, 通过: {passed}")
print(f"各维度: { {k: v['score'] for k, v in evaluation['dimension_scores'].items()} }")
# 3. 记录最佳结果
if score > best_score:
best_score = score
best_result = {
"output": output,
"score": score,
"evaluation": evaluation,
"attempts": attempt + 1
}
# 4. 如果达标,直接返回
if passed:
print(f"✅ 验证通过!共尝试{attempt + 1}次")
return best_result
# 5. 不达标,构建反馈
feedback = self._build_feedback(evaluation)
user_input = f"{user_input}\n\n[系统反馈] 上次输出评分{score},未达{self.rubric.get('threshold', 3.5)}分。\n改进建议:\n{feedback}"
# 6. 达到最大重试次数,返回最佳结果
print(f"⚠️ 达到最大重试次数,返回最佳结果(评分{best_score})")
return best_result
def _build_feedback(self, evaluation: dict) -> str:
"""根据评分结果构建反馈。"""
feedback_parts = []
for dim_name, dim_result in evaluation.get("dimension_scores", {}).items():
if dim_result["score"] < 3:
feedback_parts.append(
f"- {dim_name}得分较低({dim_result['score']}分),需改进:{dim_result['reasoning']}"
)
return "\n".join(feedback_parts) if feedback_parts else "整体质量有待提升,请全面优化输出。"
# ========== 使用示例 ==========
if __name__ == "__main__":
# 模拟生成器
def demo_generator(user_input: str) -> str:
if "改进" in user_input:
return "这是优化后的输出:完全满足所有要求。"
return "这是初始输出,有一些问题。"
# 创建Verification Loop
loop = VerificationLoopWithGrader(
generator_func=demo_generator,
judge=NumericJudge(llm_client=MockLLM()),
rubric=CONVERSATION_RUBRIC,
max_retries=3
)
# 运行
result = asyncio.run(loop.run("帮我写一段客服回复"))
print(f"\n最终结果: {result['output']}")
print(f"评分: {result['score']}")
print(f"尝试次数: {result['attempts']}")
# 预期输出示例:
# === 生成尝试 1/3 ===
# 评分: 2.5/5.0, 通过: False
# === 生成尝试 2/3 ===
# 评分: 4.2/5.0, 通过: True
# ✅ 验证通过!共尝试2次
# 最终结果: 这是优化后的输出:完全满足所有要求。
# 评分: 4.2
# 尝试次数: 2
六、回到开篇那句话
Grader = Verification Loop 的灵魂。Generator 负责写,Grader 负责判------裁判不行,写再多也是白烧 token。
那怎么选裁判?根据项目阶段,三层评估框架直接套:
工程层------今天就能用
| 你的场景 | 拿什么 | 怎么用 |
|---|---|---|
| 代码生成类 Agent | CODE_GENERATION_RUBRIC + NumericJudge | 替换 rubric 中的 dimensions 权重 |
| 文档摘要类 Agent | DOCUMENT_SUMMARY_RUBRIC + NumericJudge | 核心关注"准确性"维度 |
| 客服/对话类 Agent | CONVERSATION_RUBRIC + BinaryJudge | 快速准入,安全性维度一票否决 |
| 快速筛选(QA/审核) | 任选 Rubric + BinaryJudge | 关注 pass_condition,其他维度可忽略 |
业务层------按场景调优
- Rubric 权重不是死的:代码生成场景把"安全性"提到 0.20,对话场景把"安全性"设为硬性门槛(❤️ 分直接不通过)
- Judge 模式可以混用:先用 BinaryJudge 快速刷一遍,不通过的再上 NumericJudge 精细评分
- Threshold 动态调整:初期设低一点(3.0)让 Agent 多迭代,稳定后提到 3.5-4.0
迭代层------持续改进
- 收集 100 条人工标注 → 跑一次 JudgeCalibrator → 确认 bias 在 ±0.3 以内
- 每两周复盘一次:看哪些场景下的评分与人工偏差最大,针对性地调整 rubric 的 criteria 描述
- 版本管理:用 GraderVersionManager 记录每次 rubric 调整,做 A/B 测试对比效果
七、适用边界与限制条件
本文的Grader评分器设计并非适应所有Verification Loop场景:
| 场景 | 需调整的内容 | 建议方案 |
|---|---|---|
| LLM输出不可预测(创意类) | Rubric的"准确性"维度需弱化 | 增加"创意性"权重,降低"准确性"权重 |
| 多步骤Agent(Tool Calling链) | 单次评分无法覆盖全链路 | 分步评分:每步Tool调用后独立评估 |
| 实时对话(100ms级响应) | LLM Judge延迟1-3s不可接受 | 使用BinaryJudge + 简单规则(关键词/长度检查) |
| 缺少评测集 | 无法校准Grader偏差 | 先收集100条人工标注数据(团队内标注即可) |
| 多语言场景 | Rubric中文化可能不适用于英文输出 | 为每种语言单独维护Rubric,Judge使用对应语言的LLM |
相关阅读:
- Agent评估体系自建指南(评估体系框架------本文的宏观视角)
- Loop Engineering四层架构:从Agent Loop到生产级智能体循环(Verification Loop------本文的循环控制层)
- GB/Z 185合规的Agent Loop设计(可审计性------评分日志的合规要求)
- Agent Memory架构选型(检索精度------评估维度之一)
你的Verification Loop用的是什么评分器? 是简单的"好/不好",还是有详细的Rubric?评论区说说你的场景------代码生成、客服回复、还是文档摘要?我针对高频场景整理一份"Rubric设计模板",直接复制就能用。
收藏这篇Grader评分器设计指南,搭建Verification Loop时直接参考Rubric模板。觉得有用的话点赞+收藏,收藏率决定算法推荐权重,让更多开发者看到这篇Agent质量控制的完整方案。
📅 更新日志:
| 日期 | 更新内容 |
|---|---|
| 2026-07 | 初始发布(基于Python 3.10+ / openai>=1.0.0 / 标准库) |
⚠️ 版本变更提示 :本文基于Python 3.10+,Rubric模板和Judge代码仅依赖Python标准库,不受Python版本影响。生产环境使用LLM Judge时,需根据具体LLM SDK(openai/anthropic/google)适配API调用方式。如果openai SDK版本升级,请参考OpenAI官方迁移指南。