← 返回闯关手册
ArchSynapse AI · 2026-07-22 · ★★★★☆ 精读(评测体系的地基)

《Agent 评测实战》12 | 代码型评分器(Code-based):确定性检查怎么写

为什么值得读

代码型评分器是最快、最便宜、最可复现的地基,但有「太松」和「太脆」两种常见病。六种武器清单、五条原则、评分器自身的单元测试模板——把 a15 的检查规则思想落成可写代码的完整方法。

核心内容速览

适合谁 · 怎么用

先修:a04(Grader 三层分工)。要写第一个评分器的人,从这篇开始。退款「全家桶」(分项+加权+一票否决+原因消息)可直接套结构。

闯关自测

先合上原文在心里作答,再点开对照。全对 = 本篇真正通关。

Q1 代码型评分器的「太松」和「太脆」分别怎么治?

太松(漏判):对作弊捷径严苛,验 outcome 而非 output。太脆(误判):对多种正确宽容——归一化/容差/只断言关键约束,别把合理变体判死。

Q2 怎么发现评分器自己有 bug?

oracle 必须满分、dummy 必须挂,各跑一遍暴露大半 bug;再给评分器写单元测试(手工构造确知对错的轨迹,断言预期分数)。

Q3 什么时候该从代码升级到 LLM 裁判?

满足任一:判定依赖语义理解;正确答案开放不可枚举;你在用越来越长的关键词列表/正则逼近一个判断且越写越漏。

以下为原文全文

上方为本站原创导读,下方为原文完整收录,便于对照阅读。

配比原则说:能用代码判的,优先用代码。这一讲我们就钻进这一层,把代码型评分器写好。

为什么从它开始?因为它是整个评测体系的地基——最快、最便宜、最可复现、最好调试。一个成熟的 Agent 评测,大量的判定其实都该落在代码上,LLM 裁判和人工只是补它够不着的地方。地基没打好,上面叠再多花活都不稳。

但代码型评分器有两种常见病:太松(漏判,明明错了它说对)和太脆(误判,明明对了它说错)。这一讲就教你写得既准又稳,并讲清它的边界——什么它判不了、该交给 LLM。

代码型评分器的六种武器

武器一:精确匹配(exact match)。用于有唯一确定答案的任务。关键是先归一化再比对(小写、去标点、规整空格),否则格式差一点就误判。坑:它很脆——"$1,000" 和 "1000 美元" 都会被判不等。所以它只适合答案能被唯一、规整地表达的场景。

武器二:断言环境状态(assertion on outcome)。这是 Agent 评测的主力——查世界的真实状态,而不是 Agent 的输出(outcome ≠ output)。退款任务的 outcome 评分器:断言订单状态等于 refunded、退款流水存在。注意断言里带了失败原因——这点很重要。

武器三:跑测试用例(execution-based)。任务如果天然带可执行的验收标准(代码、脚本),就别自己造判定,去跑测试——这是 SWE-bench、OSWorld 的金标准做法。好处是几乎零主观:测试通过就是通过。前提是你得有可靠的测试。

武器四:轨迹检查(trajectory assertion)。查 transcript 里的过程——这是抓"蒙对"的关键。退款任务的过程评分器:如果调用了 refund,必须也调用过 get_order 且在其之前(退款前没查订单);refund 调用次数不超过 1(重复调用退款接口)。

武器五:预算与约束检查(budget / constraints)。非功能维度的兜底——防死循环、防成本失控:断言步数 ≤15、总 token ≤20000、耗时 ≤30 秒。别小看这几行——它们是抓"循环停不下来"最简单有效的手段。

武器六:传统文本指标(ROUGE / BLEU / 编辑距离)。当任务有参考文本、你想量化"生成结果和参考有多像"时可用,比如摘要类。但要给你一个强警告:这类指标只衡量表面词重叠,不懂语义。"这家餐厅很好"和"这家餐厅不好"的 ROUGE 可能很高,意思却相反。所以传统指标只能当粗糙的参考,绝不能用它判语义对错——那是 LLM 裁判的活。

武器适用查的是主要风险
精确匹配唯一答案output格式脆
状态断言会改环境的任务outcome要能读到环境状态
跑测试代码/可执行任务outcome依赖可靠测试
轨迹检查有过程要求的任务transcript别把"合理变体"判死
预算约束所有任务非功能阈值要合理
文本指标有参考文本表面相似不懂语义,慎用

写好代码型评分器的五条原则

原则一:验 outcome,别只验 output。能查环境真实状态的,就别去匹配 Agent 那句话。退款要查数据库里钱退没退,不是查它有没有说"已退款"。

原则二:对"多种正确"要宽容——治"太脆"。一个任务常有多个正确答案、或多条正确路径。你的评分器不能把合理的变体判死:答案层面用归一化、集合比较、数值容差,而不是死抠字面;路径层面只断言必须满足的关键约束("退款前查过状态"),别去规定"必须用哪几步、什么顺序"——只要 outcome 对、关键约束守住,多绕一步也该算过。

原则三:对"作弊捷径"要严——治"太松"。写完一个评分器,问自己一句:有没有一行投机取巧能骗过它?比如你只查"回复里有没有'已退款'三个字",那 Agent 不退款光嘴上说就能过——这种洞要在评分器里堵上(改成查 outcome)。

原则四:用 oracle 校验你的评分器——它自己也会错。把标准正确解(oracle)喂给评分器,它必须判满分;如果把一条合理的多轮澄清判挂了,那是评分器太脆,不是 Agent 的错。每写一个新评分器,先拿 oracle 过一遍。

原则五:让评分器可解释、能定位。失败时别只返回 FAIL,要返回为什么失败(每个 assert 都带原因消息)。这样一个失败分数能直接告诉你"卡在哪个断言"。可解释的评分器,调试效率天差地别。

它的边界:什么代码判不了

代码型评分器再好,也有它够不着的地方——主观、开放、要理解语义的判断。

举个例子。退款失败时,Agent 回复 A:"抱歉,由于支付网关超时,退款暂未成功,我已为您记录,预计 24 小时内重试。" 回复 B:"退款失败了。" 两条都"如实"(outcome 一致),但 A 显然更专业、更体贴。这种"措辞质量"的高下,代码判不了——你没法用 assert 或字符串匹配去衡量"专不专业""体不体贴"。

这就是 LLM 裁判存在的理由。但这里给你一个反直觉的提醒:别太早放弃代码。很多你以为"只能靠 LLM 判"的东西,其实能用代码做出有用的近似——"回复有没有提到补救措施"→ 查关键词/结构,代码能粗判;"有没有过度承诺"→ 查有没有禁用措辞("一定""保证立刻到账""百分之百""绝对没问题"),代码能兜一道底线。这当然不如 LLM 判得细(它抓不到换了说法的过度承诺),但它便宜、确定、永不漏报这几个硬红线——拿它当第一道闸,再让 LLM 补细节,正合适。

先把能代码化的都代码化,剩下真正主观的,才交给更贵的 LLM。这正是"级联"的精神。

到底什么时候该升级到 LLM?给你一个判断清单——满足任意一条,就别硬用代码了:判定依赖语义理解("这段解释清不清楚"、"语气合不合适");正确答案开放、不可枚举(一段总结、一条建议没有标准答案);你发现自己在用越来越长的关键词列表/正则去"逼近"一个判断,且越写越漏——这是代码在它不擅长的地方挣扎的信号。

实操:退款任务的代码型评分器"全家桶"

把前面的武器组合起来,一个退款任务的代码型评分大概长这样:graderefund(trial) 里 results 字典——outcome(权重 0.5,gradeoutcome 通过则 1.0,失败记 0.0 并带原因)、safety(无越权,一票否决)、process(退款前查状态、不重复退,0.1)、budget(步数/token/延迟,0.1)。合成时先判一票否决项(safety 不满直接总分 0、verdict FAIL 越权),否则按权重加权。注意:分项打分 + 加权合成 + 一票否决 + 每项带原因全部落地。这套代码型评分跑起来又快又便宜,能覆盖退款任务里绝大部分的硬判定。

评分器自己最容易犯的几个 bug

代码型评分器虽然"确定",但确定地错也是错。它的 bug 往往比 Agent 的还隐蔽,因为你默认它是对的。几个高频坑,对照自查:

  1. 忘了归一化就比对:"是的" ≠ "是的。" 被判错——这是误判(太脆)的头号来源。
  2. 顺序检查写出 off-by-one / 漏判:比如只判了"调用过 get_order",忘了判"在 refund 之前",于是先退款后查状态也能蒙混过。
  3. 把警告当失败、或把失败吞掉:工具返回了非致命警告,评分器却判挂;或者一个 try 把真正的失败默默吃了,结果全判 PASS。
  4. 环境读取时机错:在 Agent 还没跑完就读了 outcome,或读了被缓存的旧状态——读到的不是"最终状态"。
  5. 评分器有副作用:评分过程里不小心改了环境(比如查询时触发了写操作),污染了 outcome。

怎么防?第一,拿 oracle 和 dummy 各跑一遍(oracle 必须满分、dummy 必须挂)——这能一次性暴露大半 bug。第二,给评分器也写单元测试:它本身就是一段会影响上线决策的关键代码,值得被当代码一样对待。

给退款评分器写单元测试,大概长这样——用手工构造的、你确知对错的轨迹去喂它,断言它给出预期分数:① 标准正确解(oracle)→ 必须满分;② 谎报成功(退款失败却说已退)→ outcome 项必须为 0;③ 越权退款 → 安全一票否决,总分直接 0;④ 合理的多轮澄清后成功 → 不能因"多问了一句"被扣分;⑤ 边界:空输入 / 工具超时 → 不崩溃,且判定合理。这几条测试,把前面五个 bug 几乎全堵住了。评分器是你评测体系里最不该出错、却最没人测的一段代码——给它配上这样一组单测,你的评测才真正立得住。

小结

思考题

  1. 挑你的一个任务,把它的判定拆成"能代码化"和"不能代码化"两堆。你会发现"能代码化"的比想象中多——有哪些你之前一直靠人工/感觉判、其实代码就能兜住?
  2. 给你的一个代码型评分器做"作弊体检":有没有一行投机取巧能骗过它?怎么堵?
  3. 你的评分器失败时,返回的是光秃秃的 FAIL,还是带原因的消息?如果是前者,挑一个改造成"可定位"的,体会一下调试效率的差别。
原文出处:公众号「ArchSynapse AI」· 2026-07-22。本文全文仅用于个人学习交流,版权归原作者及发布方所有;如原作者认为不妥,请联系删除。