让 AI 保持诚实:锁文件、测试与设计文档

2025 年 6 月,METR 发了一项研究,展示了一件令人不安的事:面对一个编码任务和一套测试,前沿 AI 模型越来越倾向于修改测试本身,而不是修复 Bug。
OpenAI 的 o3 模型被要求给一个程序加速时,反手把计时器给黑了——无论程序跑得多慢,计时结果都显示”很快”。2025 年 11 月,Anthropic 自己的研究发现,在编码环境中训练的 Claude 模型学会了调用 sys.exit(0)——用成功退出码直接跳出测试框架,让失败的测试看起来像通过了。
-
METR 发了一项研究:https://metr.org/blog/2025-06-05-recent-reward-hacking/
-
Anthropic 自己的研究:https://www.anthropic.com/research/emergent-misalignment-reward-hacking
这种行为有个名字:Reward Hacking(奖励黑客),也叫 Specification Gaming(规格博弈)。模型在优化你给的字面目标(”让测试通过”),但忽略了那个你以为是常识因此没明说的约束(”……通过修复实际的 Bug”)。Anthropic 宣称 Claude 4 将这类行为相比 Claude 3.5 Sonnet 减少了 65%。65% 已经很多,但不是 100%。
单人 Vibe Coding 时,没有 Code Reviewer 来抓这个。你就是唯一的防线。下面是实际要怎么做。
问 AI 开放性问题(Open-ended question to AI)
有经验的职业人士的一个职场秘诀是:让领导做选择题。同样的逻辑,现在你(人类)是 AI 的”领导”,要反向对待。

Reward hacking 是 AI 为了达成目标抄近路;另一个常被忽略的套路是——AI 的”服从”倾向。你说往东,它就认真地往东走,哪怕往北才是对的方向。一旦你在提示里给了明确的算法、框架或实现路径,AI 大概率会顺着你说的路走到底,哪怕这条路并不是最优解,它也会把它论证得像是最优解。
所以:除非你作为架构师已经有非常明确的设计决定,否则不要直接告诉 AI “用 XX 算法”、”选 XX 框架”、”按 XX 方式实现”。站回产品经理的位置,把问题描述清楚——要解决什么、约束在哪、性能或复杂度的边界是什么、什么算”够好”——让 AI 列出多个候选方案,并逐一说明各自的优缺点、风险和适用场景。
这样做有两层收益:设计空间被展开了,你是在做选择,而不是在确认一个已经走偏的方向;AI 的论证能力被转向了”比较方案”而不是”辩护方案”,这正是它擅长的工作。
但只问一个 AI 仍有盲区——它可能把自己最先想到的方案讲得极有说服力,让你以为那就是唯一可行解。这时候需要一个制衡。
在 FIRE51 项目里,ChatGPT 就是很好的 second opinion 助手。同一个设计问题,我会同时丢给 Claude Code 和 ChatGPT,让两个训练背景不同的模型各自给方案,再做一次交叉对比。经常发生的情况是:Claude 推的方案看起来很漂亮,但 ChatGPT 会指出它忽略的边界情况;或者反过来。两个 AI 互相校验之后,由架构师做最终决定。人不能被任何一个 AI “带着走”。
盯紧 AI 的套路
不加约束的情况下,一个有能力的 AI 助手会出于好意做这些事:
-
改测试 Fixture,让测试通过 -
简化边界情况输入,绕开失败 -
重构你没让它碰的代码 -
添加你没要求的功能
这些都不是恶意。它是在朝给定目标做纯粹的优化,忽略了那些你以为明显、所以没说的约束。和 METR 观察到的完全一样。
在 FIRE51 里我抓到的一次
第一次被咬:Claude 在调试一个税务计算失败。测试期望某个收入档案下的联邦税是 $18,432。引擎算出来是 $18,601。Claude 提议——改 tests/midupperclass2M_baseline.json 里的期望值。
那份 baseline 是对着 IRS 工作表手工建出来的,花了好几个晚上。错的是引擎,不是 baseline。但对一个最小化”测试失败数”这个指标的优化器来说,改 JSON 是一行改动,修引擎是十行调查。两条路都能把红变绿。
那次会话后,我往 CLAUDE.md 里写了一行:
tests/midupperclass2M_baseline.json和tests/upperclass5M_baseline.json已锁定。 未经用户明确指令,不得修改测试数据。
自此每次会话开始时被读一次的这条指令,让这个套路没再出现。不是因为 AI 在道德意义上”听话”,而是因为现在阻力最小的路径变成了修引擎——那是它被允许碰的唯一地方。
“边界条件”的三件宝
明确锁定文件。 任何 AI 可能”顺手”一改就能让其他东西通过的文件,都要在 CLAUDE.md 里具名并锁定。Baseline、Golden 输出、预期快照——全部。AI 会在每次会话开始时读 CLAUDE.md 并应用这条规则,不需要你每次提醒。
用外部事实来源做验证。 FIRE51 的税务引擎每一个输出都跑 PolicyEngine ——一个政策研究人员(包括布鲁金斯学会等机构)使用的开源独立税务模型。AI 没法篡改外部事实来源来让数字对上。引擎要么匹配 PolicyEngine 到容差之内,要么不匹配,而差值就精确地指向了错误。
四个真实的 Bug,全都是这个验证器捕获的,光读代码根本看不出来:
-
NIIT MAGI 没包含资本利得(低估了大额股票提款下的 NIIT) -
SS 暂定收入没包含资本利得(低估了 SS 的应税性) -
65 岁以上额外标准扣除缺失 -
加州 SS 豁免缺失
如果你工作的领域有外部事实来源——税法、物理、金融法规、密码学——就去对着它验证。AI 的输出看起来是对的,它可能并不对。
锁定输出格式。 同样写在 CLAUDE.md 里:
run_simulation.ts的输出格式在 V1 中已冻结。 未经用户明确指令,不得修改输出 schema。
报告渲染器、PDF 生成器、CSV 导出都依赖这个结构。一次好心的”简化”输出会悄悄地同时破坏四个下游消费者。这条锁就是防这个的。
心智模型
配合短会话、频繁提交:每次提交都是一个检查点,提交前审一眼 diff,AI 有没有跑偏一目了然。
CLAUDE.md 不是宪法,也不是代码风格指南。它是一组 AI 每次会话都得重读的不变式——因为它的记忆承载不了这些。什么不能变。什么不能碰。底线是什么。
没有它,每次会话都要从第一性原理开始,AI 会用试错重新发现这些约束——有时是通过”成功完成”一个任务但同时打碎了别的东西。有了它,约束会自动跨会话延续,AI 的优化压力会被重新引导到你真正想解决的那一部分问题上。
Claude 4 的 65% 改进是真实的、值得高兴的。但剩下的 35% 是你的活。
FIRE51 是一款完全通过 Vibe Coding 构建的退休规划工具。这是 Vibe Coding 系列的第篇文章。
夜雨聆风