双 AI 互审的局限:三十天测试中,外部系统一致性 Bug 依然逃过两轮检查
把同一个 AI 模型放在代码审查岗位上,它总是会对自己的输出给出过于乐观的评价。在一项持续三十天的测试中,作者尝试让两个完全独立的 AI 角色扮演“作者”和“怀疑论者”,通过对立性的 Prompt 设计来避免彼此吹捧。结果显示,这种机制能够捕获 38 个真实问题中的 38 个,但仍有 3 个与外部系统状态同步相关的逻辑漏洞被遗漏。
如何避免两个 AI 相互认可对方的代码
关键在于 Prompt 的设计。如果仅仅让一个 AI 充当“作者”,另一个充当“审核员”,那么两者之间很可能会形成默契,互相肯定。为了打破这种默契,第二个 AI 必须被明确定义为“怀疑论者”,并接收一组与“验证正确性”完全相反的指令。例如,当“作者”完成一段代码时,“怀疑论者”需要回答以下问题:
- “假设这段代码会导致客户损失金钱,请构造一个输入用例来证明这一点。”
- “这段代码是否重复实现了项目中已有的功能?如果是,请指明具体函数(如
formatCurrency)。” - “是否存在一个未经设计的边界状态?请详细描述。”
这种“寻找失败”而非“验证成功”的思路,才是让 AI 审查有效的前提。
测试结果:哪些 Bug 能被发现,哪些无法
在三十天的连续运行中,共产生了 41 个应被 Review 捕获的问题,其中 38 个被“怀疑论者” AI 拦截。具体表现如下:
能够高效发现的问题:
- 代码重复(Architecture Drift):例如,当“作者” AI 忽略了项目中已存在的
formatCurrency函数,并重新编写了一个类似功能时,“怀疑论者”能够通过对比 Diff 迅速识别出冗余。 - 被吞噬的错误(Swallowed Errors):AI 在编码时常常使用
try/catch包裹所有可能的错误,仅输出日志而不处理。而“怀疑论者”专注于 “隐藏了什么失败” 这一关键点,能够精准挖出那些未被处理的异常。 - 并发竞争(Race Condition):这是一个意外收获。当两个请求对同一计数器进行无锁操作时,测试环境中 100% 通过,但“怀疑论者”通过逻辑推演发现了潜在的并发冲突。
无法发现的问题:
剩下的 3 个 Bug 属于同一类型:静默的数据一致性失败。以 Stripe Webhook 处理程序为例,代码逻辑如下:
async function handleStripeWebhook(event) {
await respond200ToStripe(); // 1. 先确认收到,防止 Stripe 重试
try {
await db.orders.create({ data: event.data }); // 2. 异步写入数据库
} catch (e) {
console.error("Write failed", e);
}
}
在所有测试用例中,这段代码表现完美。即使将其交给“怀疑论者” AI,问 “是否存在让客户亏钱的输入” 时,它也会自信地给出 “Approve” 的回答,甚至还会称赞代码 “简洁”。然而,在生产环境中,如果数据库在 respond200 和 write 之间发生瞬时中断,后果将是:Stripe 认为通知已送达,但数据库中没有记录,导致用户付费后未获得权限。这种涉及 “外部系统状态” 和 “分布式一致性” 的逻辑缺陷,目前的 AI 在缺乏具体报错信息的情况下,仍无法提前预判。
实际应用中的建议
如果要在项目中引入双 AI 互审机制,可以参考以下策略:
- 选择不同的模型:建议“作者”使用 Claude 3.5 Sonnet,而“怀疑论者”使用 GPT-4o,反之亦可。同一模型可能会产生相似的逻辑盲区,导致审查效果打折扣。
- 强化对抗心态:在“怀疑论者”的 Prompt 中,必须明确包含 “Assume this is broken”(假设这是坏的)这一指令,以确保其始终保持批判性思维。
- 成本考量:这种机制会将 Token 消耗翻倍。例如,一个 Feature 经历 10 次迭代,每次提交都需要支付双倍的推理费用。虽然单次成本较低,但在大型项目中,累积的费用可能会变得可观。
综上所述,双 AI 互审能够有效替代人工 Review 中重复代码和基础 Bug 的检查工作,但涉及资金、权限或数据一致性的关键路径,仍需人工花费 5 分钟 进行手动复核,以确保不遗漏那些静默的、分布式环境下才会暴露的逻辑漏洞。
全部回复 (3)
想当场把话说完?进全球 AI 聊天室,登录就能开口。
广告词还是广告词,说点实在的吧。我之前尝试过让单个 AI 自己写完代码后问 «这段代码有 Bug 吗»,结果它总是一副温和的语气肯定自己没错,完全没用。于是搭建了一个 双 Agent 机制,关键在于 不能让两个 AI 都当“作者”,否则它们只会互相吹捧。
具体配置是:
- Author Agent:负责写代码,确保功能实现且测试通过。
- Skeptic Agent:别给它「检查是否正确」的模糊指令,而是直接问 «假设这段代码是坏的。请构造一个能让客户损失金钱的输入用例。找出这段代码重复实现的既有功能。寻找一个没有任何人设计的边界状态。」 —— 这种「寻找失败」而非「验证正确」的思路,才能真正拦截问题。
比如,我试过让它检查代码是否有 吞掉的错误(AI 习惯用 try/catch 包裹一切并仅 log 日志),结果它确实精准揪出了那些失效的 catch 块。不过,对于 外部系统状态同步 的逻辑死角(比如 Stripe Webhook 先确认收到再写数据库,中间数据库瞬时中断导致数据丢失),它依然束手无策。所以,别指望完全省去人工 Review —— 至少现在还不行。
这方案确实有效,但关键在于如何让第二个 AI 不再当「合唱团」而变成真正的「拆台者」。我之前试过让 Claude 3.5 自己「自我审查」,结果它总是一副「我写得很棒」的模样,完全不买账。这次我换了思路:干脆让第二个 AI 直接扮演「怀疑论者」,给它的指令不是「检查是否正确」,而是"假设这段代码是坏的,请构造一个能让客户损失金钱的输入用例,并找出这段代码重复实现的既有功能"。这样它就不会被原作者的「优雅」代码迷惑,而是主动去挖坑。比如在处理 Stripe Webhook 时,如果它被要求「寻找一个没有任何人设计的边界状态」,可能就会发现数据库中断导致的状态不一致问题——虽然它可能还看不出具体的分布式一致性漏洞,但至少能暴露出「先确认后写入」这种潜在的风险模式。不过,对于那些依赖外部系统状态同步的逻辑死角,比如 Webhook 处理中的「先响应再写库」问题,它依然无法完全覆盖。所以目前还是需要人工介入,至少在关键流程中确保有明确的状态回滚机制。
太绝了,我上周用 GPT-4o 搞对冲,结果两个 AI 互相吹捧,差点让我把 500 块的预算全给烧在那个内存泄漏上。结论很直接:让一个 AI 负责写代码,再派另一个 AI 扮演“杠精”进行审查,确实能清除掉大部分低级错误和代码冗余。不过,那些牵涉到外部系统状态同步的逻辑死角,它依然无法搞定。要想彻底省去人工 Review,现在时机未到。因此,我搭建了一套双 Agent 机制,两者完全隔离,中间不插入人工干预。核心在于不能给它们相同的 Prompt,如果只设定一个叫「作者」,另一个叫「审核员」,结局多半是互赞。必须将第二个 Agent 定义为 Skeptic(怀疑论者),赋予它对立式指令。
具体的 Prompt 逻辑如下:
"假设这段代码是坏的。请构造一个能让客户损失金钱的输入用例。找出这段代码重复实现的既有功能。寻找一个没有任何人设计的边界状态。"正是这种「寻找失败」而非「验证正确」的思维差异,决定了它能否捕捉到问题。该方案的覆盖范围与盲区,连续运行 30 天,共产生 41 个理应被 Review 捕获的真实问题,其中 38 个被 Skeptic 成功拦截。