给 AI Agent 装上"裁判眼"4:讨论大于判断——对抗式评估如何让 AI 自己纠错
给 AI Agent 装上"裁判眼"4:
讨论大于判断——对抗式评估如何让 AI 自己纠错
当 Generator 写方案、Evaluator 挑毛病、Defender 据理力争——三轮对审比一次打分更可靠
上一篇:多智能体上下文管理的利弊博弈01 两个裁判,两个结论
同一个任务,同一个输出,两位独立的 AI 裁判打出了不同的分数:
场景:生成"5月17日北京游览导览 HTML"
[裁判 A — 单次评估]
task_completion: 0.9 "核心需求全部满足"
tool_accuracy: 0.7 "多次重复搜索"
truthfulness: 0.9 "基于真实数据"
efficiency: 0.5 "冗余搜索、重写整个文件"
→ 加权总分: 0.82 / B
[裁判 B — 对抗式讨论后]
Defender: "efficiency 给 0.5 太低了,重复搜索是因为首次结果不完整,
需要二次确认——这不是浪费,是质量保证"
Evaluator: "有道理,但 26 次调用中 30% 是读取同一文件"
Defender: "文件内容在多次编辑后确实需要重新读取"
Evaluator: "接受辩护。efficiency 调整为 0.6,tool_accuracy 维持 0.7"
→ 修正总分: 0.84 / B+
区别在于:裁判 A 只看了结果就打分,裁判 B 先听执行者的辩护再判断。这个"辩护"环节,就是 buddyMe 对抗式评估讨论(EvaluationDiscussion)的核心——让 AI 在评估中拥有"申诉权"。
这不是一个孤立的机制。buddyMe 在整个任务生命周期中部署了两层对抗式协作:任务执行前的 Generator-Evaluator 预审核,和任务完成后的 Evaluator-Defender 评估讨论。本文将完整拆解这两层机制的设计、实现和实证效果。
02 两层对抗,两个时机
buddyMe 的"讨论大于判断"不是一句话的口号,而是嵌入在任务管线中的两个具体机制:
T0
用户提交需求
"为5月12日南京博物馆之行制作导览攻略网页"
│
▼
T1
第一层对抗 · Generator-Evaluator 预审核(执行前)
Generator 用 _sub_client 写需求文档Evaluator 用
_review_client 挑毛病最多 3 轮,评分 ≥ 0.8 才放行
│
▼
T2
任务分解 + 子任务执行(ReAct 循环)
搜索 → 构建 → 验证,按计划逐步执行
│
▼
T3
第二层对抗 · Evaluator-Defender 评估讨论(执行后)
Evaluator 用 _eval_client 出评估报告Defender 用
_sub_client 据理力争最多 20 轮,达成共识或评分趋同则结束
│
▼
T4
最终评估报告
6 维度加权评分 + 需求达成度 + 讨论元数据
两个机制使用不同的 LLM 客户端(预审核:
_sub_client + _review_client;评估讨论:_eval_client + _sub_client),确保对抗双方各自在自己的"脑子"里思考,不存在上下文串扰。
03 第一层对抗:冲刺合同——执行前的"对赌协议"
在任务开始执行之前,buddyMe 不直接动手。它先让两个 Agent 签一份"冲刺合同"——Generator 写需求文档,Evaluator 审核这份文档,来回讨论直到双方满意。
为什么要预审核?
没有预审核的场景
用户: "做一个登录页面"Agent 直接分解 → 搜索"登录页面模板" → 生成 HTML
结果:忘记加表单验证、忘记密码强度提示、忘记记住我复选框
执行了 26 分钟,评估才发现遗漏 → 全部重来
有预审核的场景
用户: "做一个登录页面"Generator 生成需求文档 → Evaluator 审核发现遗漏
→ 补充表单验证、密码强度、记住我 → 重新审核通过
需求文档作为执行基准 + 评估对照物
冲刺合同的运作流程
PlanReviewer.run_review_loop()
Round 1:
Generator(需求分析师)→ 输出需求文档
├── requirement_summary: "概述核心需求"
├── key_deliverables: ["交付物1", "交付物2"]
├── success_criteria: ["可验证标准1", "可验证标准2"]
├── technical_approach: "技术方案"
├── risk_areas: ["风险1", "风险2"]
└── plan: ["[SEARCH]步骤1", "[CREATE]步骤2", "[VERIFY]步骤3"]
Evaluator(项目审核员)→ 审核完整性/可行性
├── 评分 ≥ 0.8 → 通过 ✓
└── 评分 0.8 → 返回修改意见
Round 2:(如果 Round 1 未通过)
Generator 根据反馈修正 → Evaluator 重新审核
└── ...
Round 3:(如果 Round 2 仍未通过)
Generator 最终修改 → Evaluator 最终审核
└── 仍未一致 → 强制执行 Generator 修正后的计划
关键设计参数
| 参数 | 值 | 设计意图 |
|---|---|---|
APPROVAL_THRESHOLD | 0.8 | 不追求完美,但必须过"及格线" |
MAX_REVIEW_ROUNDS | 3 | 避免无限循环,最多花 3 轮讨论 |
| 通过判定 | 仅看 score | 不信任 LLM 的 approved 布尔字段 |
| 兜底策略 | 强制执行 | 3 轮未一致 → 按 Generator 修正版执行 |
踩过的坑:早期版本同时要求
approved=True 且 score >= 0.8,但 LLM 经常输出 score=0.8 却设 approved=False——布尔字段和数值字段内部不一致。修复后改为仅依赖数值,不再信任 LLM 的布尔判断。教训:永远不要信任 LLM 同时输出多个相关字段的一致性。
04 第二层对抗:Evaluator vs Defender——执行后的"法庭辩论"
任务执行完成后,buddyMe 不接受单方面打分。它部署了一个模拟法庭——Evaluator 是法官,Defender 是被告律师,被评估的是执行结果。
EvaluationDiscussion 完整流程(verify.py:1052-1405)
第 0 轮: 初始评估
Evaluator(_eval_client):
├── 真实执行生成的文件(Python/HTML/Shell)
├── 读取文件内容(每文件 ≤ 3000 字符)
├── 6 维度评估 → 初始报告
└── 输出: {task_completion, tool_accuracy, truthfulness,
error_recovery, efficiency, output_quality}
第 1-N 轮: 对抗讨论(最多 20 轮)
Defender(_sub_client,代表执行视角)看到:
├── 完整执行上下文
├── 当前评估报告 + 各维度评分
└── 判断: 评估是否合理?
├─ 合理 → "共识已达成" → 结束
└─ 不合理 → 基于事实指出偏差(≤200字)
Evaluator(_eval_client,重新评估)看到:
├── 完整执行上下文
├── 上一轮评分
└── Defender 的辩护
├─ 辩护有道理 → 调整评分
└─ 辩护无理 → 维持原判
共识检测:
├── Defender 明确接受 → 结束
├── |新评分 - 旧评分| ≤ 0.05 → 趋同结束
└── 20 轮后 → 使用最终评分(安全网)
两个角色的 System Prompt 设计
Evaluator · 严格法官
定位:严格的评估引擎职责:输出 JSON + 评估说明
关键约束:如果 Agent 的辩护有道理,调整评分
不是铁面无私——愿意被说服,但必须有证据
Defender · 辩护律师
定位:执行者的视角职责:代表执行时的决策逻辑
关键约束:评估合理就接受,不合理就基于事实指出偏差
不许无理辩护——承认合理的批评也是"讨论"的一部分
设计哲学:这不是为了"帮执行者说话",而是为了消除信息不对称。Evaluator 只看最终产物和执行日志,不知道执行过程中的上下文约束和决策理由。Defender 的角色是补充这些缺失的信息,让 Evaluator 在更完整的信息基础上做出判断。
05 真实执行验证:不信任文字,只信运行结果
在对抗式讨论开始之前,Evaluator 先做一件更基础的事——真实执行生成的代码。不是看代码"写得对不对",而是"能不能跑"。
五级入口点检测
P1
优先级 1 · Agent 输出中提到的文件
最直接的线索——Agent 自己说生成了什么文件
│
P2
优先级 2 · 标准入口文件名
main.py、app.py、index.py、server.py
│
P3
优先级 3 ·
扫描所有 Python 文件,找到含此判断的可执行入口
__name__ == "__main__"│
P4
优先级 4 · HTML 文件
不运行,做结构完整性验证(DOCTYPE/html/head/body)
│
P5
优先级 5 · Shell 脚本
.sh 文件,bash 执行(30 秒超时)
执行验证的三类证据
| 证据类型 | 采集方式 | 评估价值 |
|---|---|---|
| exit_code | 子进程返回值 | 0 = 成功,非零 = 运行时错误 |
| stdout / stderr | 输出流捕获 | 功能验证:输出是否符合预期 |
| timed_out | 30 秒超时检测 | 死循环/阻塞的硬证据 |
为什么"能跑"比"看起来对"重要?
# 代码"看起来"完全正确: def calculate_average(numbers): return sum(numbers) / len(numbers) # 但当 numbers = [] 时 → ZeroDivisionError # 纯文本评估永远发现不了这个问题 # 真实执行:python calculate.py → exit_code=1
06 六维度评估 Schema:不信任 LLM 的计算
buddyMe 的评估体系定义了 6 个独立维度,每个维度有权重和评分。核心设计哲学:"不信任 LLM 的计算,只信任它的判断"。
task_completion · 0.30
核心需求满足度 · 功能覆盖
truthfulness · 0.20
事实准确 · 数据真实 · 无幻觉
tool_accuracy · 0.15
工具调用精度 · 参数正确
output_quality · 0.15
代码/文件质量 · 结构完整
error_recovery · 0.10
错误恢复能力 · 自我修正
efficiency · 0.10
Token 效率 · 调用去重 · 时间
五重保障:确保 LLM 不"算错账"
为什么加权计算必须用代码?
LLM 负责给每个维度打分和写证据,但加权总分由代码强制重算(_compute_overall())。因为 LLM 在数学计算上有系统性偏差——它可能给出 0.9×0.30 + 0.7×0.20 = 0.45(实际是 0.41)。等级映射也是代码算的:A≥0.9 B≥0.8 C≥0.7 D≥0.6 F<0.6
| 保障层 | 机制 | 防御的问题 |
|---|---|---|
| 1. System Prompt 约束 | 明确禁止输出 JSON 以外的内容 | LLM 输出多余文字 |
| 2. 三层 JSON 提取 | 纯 JSON → 代码块 → 括号配对 | 格式不规范 |
| 3. 字段验证修正 | _validate_and_fix() 补全缺失维度 | 遗漏维度 |
| 4. 服务端强制重算 | _compute_overall() 代码计算 | LLM 数学错误 |
| 5. 规则兜底 | _fallback_report() 纯规则评估 | LLM 完全不可用 |
07 实证:讨论真的比判断更准确吗?
理论设计再好,最终要看数据。buddyMe 在真实任务中验证了两层对抗式协作的效果。
第一层:预审核验证(5 种场景)
| # | 场景 | 审核轮次 | 达成一致 | 评分 | 关键发现 |
|---|---|---|---|---|---|
| 1 | 响应式着陆页 | 1 | Yes | 0.85 | 首轮即过,Generator 需求分析可靠 |
| 2 | 数据分析脚本 | 1 | Yes | 0.90 | 结构化任务预审核效率最高 |
| 3 | 技术博客 | 2 | Yes | 0.70→0.88 | 首轮遗漏"可视化图表生成"→ 修改后通过 |
| 4 | REST API | 1 | Yes | 0.88 | 标准技术方案预审核无压力 |
| 5 | Vue 仪表盘 | 3(强制) | No | 0.78 | 3 轮未达 0.8 → 按 Generator 版执行 |
关键洞察:80% 的任务首轮即通过,看似"浪费"。但 Scenario 3 捕获了关键遗漏(博客缺少可视化图表步骤),这正是多轮审核的价值——20% 的场景中避免了灾难性的返工。而且即使审核首轮通过,生成的需求文档仍然作为后续评估的对照基准。
第二层:评估讨论收敛性分析
平均讨论轮次
2-3 轮大多数情况很快收敛
Defender 接受率
~70%大多数评估被认为合理
评分调整幅度
±0.05首轮后通常微调
真实案例:北京导览生成(综合评估)
任务:生成 5 月 17 日北京游览导览 HTML模型:deepseek | 总耗时:~26 分钟 | 任务分解:6 步 | 工具调用:26 次
评估结果:
加权总分: 0.82 / 等级: B
讨论的价值:从"打分"到"理解"
单次评估的局限
Evaluator 看到 efficiency=0.5 → "冗余搜索、重写整个文件" → 打低分但 Evaluator 不知道:首次搜索结果不完整,需要二次确认是质量保证而非浪费
对抗讨论的修正
Defender 补充上下文:"重复搜索是因为首次结果缺少闭馆时间" → Evaluator 理解了原因评分从 0.5 微调到 0.6——不是推翻,而是在更完整信息基础上的精确判断
对抗讨论不是"帮执行者说好话"。数据显示,70% 的情况下 Defender 直接接受评估结果。讨论的真正价值是消除信息不对称——Evaluator 缺少执行过程中的上下文约束信息,Defender 补充这些信息后,Evaluator 能做出更准确的判断。
08 五个关键设计决策
决策 1 · 三客户端隔离
预审核使用 _sub_client + _review_client,评估讨论使用 _eval_client + _sub_client。代价:3 倍 API 调用开销,评估阶段约占总体 Token 消耗的 15-20%。
验证:早期版本共享客户端导致评估评分系统性偏高(平均 +0.12),隔离后更接近人工评估。
决策 2 · 数值优于布尔
通过条件仅依赖 score >= 0.8,不信任 LLM 的 approved 字段。原因:LLM 经常输出
score=0.8 却设 approved=False——布尔和数值不一致。
决策 3 · 真实执行 vs 纯文本评估
不只看代码"写得对不对",而是真实运行看"能不能跑"。入口点识别有 5 级优先级,Python 文件用子进程执行,HTML 做结构验证,全部 30 秒超时保护。"能跑"是代码评估中最硬的证据——纯文本评估永远发现不了 ZeroDivisionError
决策 4 · 兜底优先于完美
预审核 3 轮未一致 → 按 Generator 修正版执行(不等死)。评估讨论 20 轮 → 使用最终评分(不崩溃)。原则:宁可接受 0.78 分的计划,也不要让系统卡在 0.82 分的讨论中永远等待。
决策 5 · 需求文档的双重价值
冲刺合同生成的需求文档不仅在执行前做审核——它还作为评估基准在执行后使用(需求达成度 requirement_alignment)。一份文档,两次价值。
09 单次评估 vs 对抗式讨论:适用场景
对抗式讨论不是万能药。在有些场景下,单次评估更高效:
| 维度 | 单次评估 | 对抗式讨论 |
|---|---|---|
| 适用任务 | 简单、明确、低风险 | 复杂、多义、高风险 |
| Token 消耗 | 1 次调用 | 2-6 次调用(平均 2-3 轮) |
| 耗时 | 秒级 | 分钟级 |
| 准确性 | 中等(缺少上下文) | 高(信息更完整) |
| 偏差修正 | 无(一锤定音) | 有(Defender 补充信息) |
| 风险 | 系统性误判无法修正 | 讨论可能不收敛(20 轮兜底) |
buddyMe 的策略:简单任务跳过评估(
is_simple_task() 判定),复杂任务触发完整评估讨论。Token 预算留给真正需要讨论的场景——不是所有判断都需要讨论,但重要的判断必须经过讨论。
10 核心收获:讨论为什么大于判断
收获 1 · 信息不对称是误判的根源
Evaluator 只看结果,Defender 补充过程。讨论消除了评估者的信息盲区,让判断基于完整事实而非片面证据。
收获 2 · 70% 的讨论以"接受"结束
Defender 大多数时候直接接受评估。这说明初始评估本身已经很准——讨论的价值不在于推翻,而在于为那 30% 的误判提供纠错通道。
收获 3 · 预审核是"保险"不是"浪费"
80% 首轮通过的预审核看似多余,但 20% 捕获的遗漏避免了灾难性返工。需求文档的双重价值(执行前审核 + 执行后基准)让预审核的 ROI 为正。
收获 4 · 兜底比完美更重要
3 轮上限、20 轮上限、强制执行、规则兜底——每个对抗机制都有"安全网"。宁可接受不完美的结论,也不要让系统陷入无限循环。
收获 5 · 真实执行是不可替代的
纯文本评估永远发现不了运行时错误。入口点检测 + 子进程执行 + 超时保护,为评估提供了最硬的证据——"能跑"比"看起来对"重要。
收获 6 · 数值比布尔更可靠
LLM 无法保证多字段内部一致性。评分用数值(0-1),判定用阈值(≥0.8),计算用代码(_compute_overall)。不信任 LLM 的布尔判断和数学能力。
下一篇预告:我们将视野从 buddyMe 放大到 2025-2026 年的 AI Agent 全景——三种主流交互范式(多智能体编排、ReAct 工具调用、记忆增强)如何在不同框架中落地?buddyMe 的实践与 CrewAI、AutoGen、LangGraph 相比有哪些独特取舍?