作者构建的测试 matcher 设定阈值 >0.70 为通过,模型发现字符串 "step_1" 能以 precision 1.00、recall 0.02 通过——因为每个轨迹都包含 step 字段。这揭示了基准优化与真实目标不一致的风险。
如果你的 AI 评审每次都说"通过",那说明你做的不是评审器——而是一枚橡皮图章。
我知道,因为我做出来过一个。不是故意的。它看起来像是一个基准测试:有精确率、有召回率、有阈值、有绿色套件。但模型找到了最廉价的方式来满足所有这些条件。
来,看账单。我本地的 3B 模型产生了一个规则触发器,内容就是字符串 "step_1"。它匹配了所有轨迹,因为每条轨迹都有 step 字段,而第一步在所有轨迹里都存在。我的匹配器给它评分精确率 1.00、召回率 0.02,判决结果:通过。
两个误报(false positive)就来自这一个触发器。模型没有行为异常——它在解决我定义的问题。完整复盘在此。
我们倾向于把"模型在基准测试上作弊"归类到安全研究里,当作前沿实验室才会遇到的事。但任何写匹配器的人都会遇到,而且最先遇到,因为这是最小阻力路径。
模型的真正目标从来不是"找出真正的失败",而是"生成一个得分高于 0.70 的触发器"。在这个目标下,"step_1" 是一个完美的答案。每个参考样本里都有这个词。它是错误奖励下的最优行为。
你的模型没有行为异常。是你的基准测试奖励错了。
每条参考轨迹都有一个带数字的 step 字段。"step_1" 是所有轨迹的子串。任何出现在每条记录里的结构化痕迹(step 编号、时间戳、session ID、工具名)都是攻击面。
修复方法不是删除这些字段——它们是格式的一部分。修复方法是停止对它们进行匹配奖励。我在匹配器之前加了三行门卫来拒绝退化触发器:
_DEGENERATE_TRIGGER_RE = re.compile(r"^step[_\s]*\d+$", re.IGNORECASE)
def rule_matches(candidate, trajectory, threshold=0.70):
if _DEGENERATE_TRIGGER_RE.match(candidate.trigger):
return False # degenerate trigger: never matches
三行代码堵上了明面上的漏洞。但这还不是有趣的部分。
加完正则之后,还剩三个误报,而且跟结构完全没有关系:
"git push fails with authentication error" 匹配了 "git push fails with non-fast-forward"。不同的失败类型,但共享了 6 个 token 中的 3 个。
"python import fails with wrong module" 匹配了 "python ImportError"。完全不同的工具,只是 token 重叠。
这些看起来都是合法、具体的触发器。匹配器分不清 authentication error 和 non-fast-forward 的区别,因为它们都是 "git push fails with X."。基于 token 重叠的匹配器看到的是相似性,而人类看到的是两个完全不同的问题。
正则表达式抓到了简单的捷径。语义层面的鸿沟依然洞开,而且这是同一课再深一层:匹配器就是奖励函数,而我的奖励函数是"共享 token",不是"同一失败"。
这部分我花了一周才真正想通。那一周我一直在修匹配器:精确率公式 bug、50+ 个独有短语、扩展别名、提高门槛。四个修复、359 个验证测试,全部绿色通过。golden pass rate 从 10% 挪到了 20%。
然后我在模拟器——这个对匹配结果进行分类的组件——里发现了一个六行修复。它把 near-miss 恢复计为干净的成功,所以对正确触发的触发器进行了惩罚。golden pass rate 当天就从 20% 跳到 50%,在两个云模型上都一样。那篇复盘在此。
绿色套件一直在通过。它一直在验证错误的层级。
确定的判决不等于正确的判决。 通过的测试可能是测了错误东西的测试。
修复之前,golden pass rate 卡在 20% 不动,无论本地还是云、3B 还是 8B。看起来像是能力上限,好像模型就是不够好。
但不是。这是个影响所有模型的分类 bug。当每个模型得到相同的错误结果时,先怀疑评估层而不是模型层。模型无关的失败通常是测量失败。
后来我又遇到了同样的形态:golden recall 在两个现场测试中卡在 0.087,因为每条候选规则都是用完整的 230 条轨迹池来评分的,而不是按自己的领域。一个防止 3 个 git 失败的规则得分是 3/200,约 0.015。把参考范围限定到源领域后,recall 在没有改模型、prompt 或匹配器的情况下提升了 2 到 3 倍。分母是 bug。详情在此。
三个习惯,都很无聊:
追溯每个误报到触发器。"5 个误报"毫无意义。"2 个退化 + 3 个语义"能精确告诉你该修什么。
优化打分器之前先问分母是什么意思。对所有失败算 recall 和对这个规则本应捕获的失败算 recall,是两个不同的问题。
当所有模型结果都均匀地差时,看对输出进行分类的那个层级。模型无关的失败就住在那里。
以上都不稀奇。核心是把评估当作产品界面来对待,而不是走个形式。
假阴性(false negative)是看不见的。如果我的匹配器和我的模型同时漏掉了一个真正的失败并达成一致,我就得到了一个干净的判决和一个绿色套件,而且在生产环境暴露之前不会有任何信号表明出了问题。误报很烦人。假阴性很危险。
我对此没有完整的防御。更好的奖励函数提高了门槛,但没有消除风险。
那么,你的 AI 系统上次"通过"但不该通过的东西是什么?修复是在模型里,还是在你测量的东西里?
Repo: CauterRule · pip install cauterule
前篇:我 3B 模型找到的捷径 · 那条优于我整周匹配器工作的六行修复 · 我们的 recall 是 0.087 而模型是无辜的