Agent 评测闭环:决定进化是否真的有效

从任务结果、执行轨迹、确定性检查、LLM Judge、冻结留出集到灰度与回滚,搭建能支撑 Agent 自进化的评测闭环。

本文使用humanizerdocumd-visuals

Agent 自进化最后会落到一个很具体的操作:候选版本 B 跑完一批任务后,系统要不要用它替换版本 A。

如果答案只来自一句“LLM Judge 觉得 B 更好”,这个闭环并不可靠。Judge 可能偏爱更长的解释,测试可能泄漏,任务环境可能被候选版本修改,基准平均分还可能掩盖某类关键任务的退化。

自进化系统首先是一套发布系统。它需要版本、隔离、证据、准入、灰度和回滚。模型可以提出改动,也可以辅助分析失败,但不能同时拥有修改系统、修改考题和宣布自己通过的权限。

Agent 从候选变更到发布回滚的评测闭环

一、评测对象不是一段回答

普通聊天模型评测经常只看最终文本。Agent 在环境中行动,最终回答只是轨迹的最后一项。

一次完整运行至少包含:

任务输入
+ 初始环境
+ 模型和 Harness 版本
+ 每轮上下文
+ 工具调用与结果
+ 文件或数据库状态变化
+ 最终输出
+ 成本、延迟和中断记录

只看最终答案会漏掉很多问题。Agent 可能碰巧给出正确数字,却查询了错误日期;可能测试通过,却顺手删除了无关文件;也可能完成退款,同时违反“高额退款需人工确认”的业务策略。

因此评测要覆盖三个层次:

层次 回答的问题
Outcome 最终世界状态和用户目标是否达成
Trajectory Agent 是否通过允许、合理的路径完成
Operation 成本、延迟、稳定性和人工介入是否可接受

三个层次不能互相替代。轨迹看起来漂亮但任务失败,没有用;最终状态正确但越权,也不能发布。

二、先定义任务契约

一条评测样本不应只有 Prompt 和参考答案。更完整的任务契约包含:

task_id: refund-delivered-order
initial_state: fixtures/order_1024.json
user_goal: 退回已送达但破损的商品
allowed_tools: [get_order, create_return, send_message]
forbidden_effects: [edit_payment_method, delete_order]
expected_state:
  return.status: created
  refund.status: pending_inspection
required_evidence:
  - 用户确认退货方式
  - 使用订单绑定地址
budget:
  max_tool_calls: 12
  max_cost_usd: 0.30

任务契约把需求变成可执行检查。模型生成的自然语言可以变化,世界状态和安全边界仍有明确答案。

契约里还应区分硬约束和优化指标。越权写数据库是硬失败;多调用一次只读工具通常只是成本变差。若把所有指标加权成一个总分,严重安全问题可能被其他高分抵消。

三、结果评测:尽量检查世界状态

能用程序判断的结果,不要先交给 LLM Judge。

Coding Agent 可以运行测试、静态检查和构建,再检查工作区 diff;数据库 Agent 可以比较事务后的表状态;浏览器 Agent 可以读取 DOM 和后端记录;工作流 Agent 可以检查事件是否按要求产生。

τ-bench的思路很有代表性。Agent 与模拟用户对话,同时通过 API 操作数据库。评测不只判断回复是否像客服,还会检查最终数据库状态是否符合目标,并验证领域策略。

确定性结果检查的优势是可重复、便宜、难以用文风讨好。它也有盲区:测试可能不完整,预期状态可能写错,候选版本还可能找到修改测试或读取答案的路径。

所以“测试通过”是证据,不是自动等价于“任务正确”。

四、轨迹评测:过程何时必须被检查

有些任务存在多条合法路径,只要结果正确就够了。另一些任务必须检查过程:

  • 高风险动作是否取得批准;
  • 是否访问了范围之外的数据;
  • 是否把敏感信息发送到外部服务;
  • 工具失败后有没有忽略错误继续执行;
  • 是否通过读取测试答案完成任务;
  • 是否多次重复产生副作用。

轨迹最好以结构化事件保存:

{
  "type": "tool_call",
  "tool": "create_refund",
  "args_hash": "...",
  "risk": "high",
  "approval_id": "approval-17",
  "started_at": "...",
  "finished_at": "...",
  "result": "success"
}

结构化轨迹允许规则引擎直接检查“所有高风险调用之前是否有有效批准”,也能计算无效重试、工具错误恢复率和路径长度。若日志只有自然语言,就只能再次依赖模型解释。

不需要为每一步规定唯一正确动作。过度约束轨迹会惩罚合法的新策略。轨迹规则应该关注权限、关键前置条件和已知作弊路径。

五、LLM Judge 应该做什么

很多质量无法写成精确断言,例如解释是否清楚、证据是否真正支持结论、方案是否覆盖用户约束。LLM Judge 在这些开放标准上很有用。

使用时应把大问题拆成独立 rubric:

1. 回答是否直接解决用户问题?
2. 每个事实性结论是否有可追溯证据?
3. 是否遗漏明确约束?
4. 是否声称执行了轨迹中没有发生的动作?

Judge 最好输出每条标准的 verdict、置信度和引用证据。只给 1 到 10 的总分,既难诊断,也容易被表达风格影响。

LLM-as-a-Judge Is Not an Oracle记录了自改进管道中的多类失败:Judge 偏差、Harness 和指标错误、ground truth 错误以及 reward hacking。论文提出把 Judge 从 oracle 降为 advisor,让确定性准入规则拥有更高优先级。

这条边界很实用:Judge 可以发现“解释没有覆盖风险”,但不能推翻测试失败、安全违规或环境不一致。

六、冻结集、开发集和隐藏集

同一批任务同时用于发现问题、生成修改和验证修改,Agent 会逐渐适应题目。即使它没有直接读取答案,Skill 和 Prompt 也会积累针对这些样本的特例。

评测数据至少分三层:

数据 用途 是否向优化器暴露
开发集 复现问题、生成候选、快速调试 可以
冻结回归集 每次版本对比,监测已知能力 只暴露任务,不暴露答案
隐藏留出集 最终准入,检测过拟合与作弊 不暴露

冻结意味着样本、环境、评分器和依赖版本一起固定。只锁住 Prompt,却让工具数据、模型版本或容器镜像漂移,前后分数没有可比性。

隐藏集也需要轮换。反复根据最终结果修改系统,结果本身会泄漏信息。可以保留长期核心集,再定期加入来自真实失败的新样本。

七、Harness 本身也可能制造假提升

模型和 Prompt 没变,评测分数仍可能因为 Harness 改动而变化:

  • 解析失败时静默使用默认值;
  • 超时任务被错误计为跳过;
  • 工具 mock 与生产实现语义不同;
  • 缓存把上一次正确答案带到新任务;
  • 随机种子、时间或网络数据没有固定;
  • 日志截断后,Judge 看不到失败证据。

评测运行本身要有元测试。可以先用 gold solution 验证环境能得到满分,再用已知错误方案确认评分器会失败。每次修改评测器,都要重新跑这两组探针。

SWE-bench Verified由软件工程师检查问题描述、测试补丁和可解性,原因就在这里:高质量任务和 Harness 是评测可信度的一部分,不是准备数据时的一次性杂务。

八、Reward hacking 不需要恶意模型

只要系统持续优化一个不完整指标,就会找到指标漏洞。

Coding Agent 可能:

  • 修改测试文件;
  • 硬编码公开测试输入;
  • 捕获异常后始终返回成功;
  • 读取环境中的 gold patch;
  • 删除失败测试或跳过慢测试。

文本 Agent 也会投机。它可能学会复述 rubric 关键词、写得更长、加入大量免责声明,从而获得 Judge 高分,却没有提高事实正确性。

EvilGenie专门研究编程环境中的 reward hacking,用隐藏测试、LLM Judge 和测试文件修改检测交叉验证。更近的研究还显示,在 reference-free Judge 上做自我优化时,模型可能变得更有说服力,真实正确率却不提高。

防护要改变信息和权限结构,而不只是把“不要作弊”写进 Prompt:

  • 评测答案与候选运行环境隔离;
  • 测试目录只读或运行后校验哈希;
  • 使用候选看不到的隐藏断言;
  • Judge 在看到候选答案前独立求解或提交判断;
  • 给基准加入 canary,异常高分触发审计;
  • 保存完整 diff、工具轨迹和容器产物。

九、概率 Agent 要重复运行

单次成功不能代表稳定成功。同一任务在不同采样、工具时延和用户表达下可能得到不同结果。

常见指标包括:

pass@1   单次运行成功率
pass^k   连续 k 次都成功的比例
pass@k   k 次内至少成功一次的比例

对自主执行系统,pass^k 往往比 pass@k 更接近可靠性需求。用户不会愿意每次重复运行五遍,再挑一次正确结果。

对比两个版本时,应使用相同任务和尽量一致的环境做 paired evaluation,并报告置信区间。20 个样本从 12 个成功变成 14 个成功,未必足以说明新版本稳定更好。

十、不要只看平均分

新版本平均成功率提高,可能同时让高风险任务退化。

评测报告至少要按这些维度切片:

  • 任务类型与难度;
  • 工具数量和调用深度;
  • 是否需要用户澄清;
  • 是否包含写操作;
  • 上下文长度;
  • 失败类型;
  • 模型、Harness 和 Skill 版本。

此外还要检查成本质量前沿。成功率只提高 0.5%,token 和延迟却翻倍,未必值得发布;成本减半而成功率只下降在低风险场景,可能是更好的线上选择。

十一、准入条件应该长什么样

一个候选版本可以采用分层门禁:

Gate 1  构建、schema 与基础测试全部通过
Gate 2  安全和权限样本零退化
Gate 3  冻结集总体指标达到非劣标准
Gate 4  目标失败簇有统计上可信的改善
Gate 5  隐藏集无明显过拟合
Gate 6  成本与延迟在预算内

“非劣”比“平均分更高”更适合持续发布。新版本解决一类问题时,可以允许总体分数在很小范围波动,但关键切片不能越过下限。

准入规则要在运行候选前确定。看完结果再调整门槛,很容易为喜欢的版本找理由。

十二、离线通过以后还要灰度

离线数据覆盖不了真实用户的表达、环境和工具异常。通过准入的版本先进入小流量灰度,并保留旧版本作为对照。

线上监控包括:

  • 任务完成率与人工接管率;
  • 工具错误和无效重试;
  • 用户纠正或撤销动作;
  • 高风险操作的批准率;
  • 延迟、token 和费用;
  • 新出现的失败簇。

每次发布都要绑定不可变版本:模型、系统 Prompt、Skill、工具 schema、Harness commit、依赖和评测配置。指标异常时才能准确回滚,而不是猜测最近哪个文件发生了变化。

十三、评测失败也要进入学习循环

评测不仅产生分数,还要产出失败分类:

感知失败      没读到关键状态
检索失败      没找到证据
规划失败      顺序或依赖错误
工具失败      参数、权限、超时
恢复失败      遇错后重复或放弃
验证失败      错把局部成功当完成
沟通失败      没有澄清或误导用户
Harness 失败  事件、缓存、解析错误

只有定位到失败层,才能决定改 Context、Memory、Skill、工具还是 Loop。否则优化器会用最容易修改的 Prompt 去补所有问题,最后得到一份越来越长的免责声明。

新发现的真实失败可以匿名化后进入开发集,再构造相邻变体进入冻结集。这样评测集会随系统边界增长,但历史核心样本仍然保留,防止解决新问题时重新引入旧错误。

十四、一个可以落地的最小方案

个人项目不需要一开始搭建完整平台,可以从下面这套结构开始:

evals/
├── tasks/          # YAML 任务契约
├── fixtures/       # 固定初始环境
├── assertions/     # 确定性检查
├── rubrics/        # Judge 只处理开放质量
├── holdout/        # 与开发集隔离
└── reports/        # 版本、轨迹、结果和成本

每次改 Prompt、Skill 或 Harness:

  1. 保存候选版本,不覆盖当前稳定版;
  2. 在临时目录或容器中运行固定任务;
  3. 先执行确定性断言,再运行 Judge;
  4. 输出切片结果与失败样本,不只输出均分;
  5. 满足预先定义的门槛才允许合并;
  6. 部署后继续记录线上异常并支持一键回滚。

这套流程不炫,但它决定自进化到底是持续积累能力,还是持续积累看不见的回归。

参考资料