第一版跑完,一批"已修复"的报告摆在面前,每一份都说"已确认修复正确,无回归风险"。
我一条条审 diff,发现错的不在少数。
有意思的是错的那几份,说明写得比对的还详细。根因分析、改动说明、验证结论,一应俱全,逻辑通顺,术语准确。如果我不逐行看 diff,完全看不出来哪里不对。
那一刻我意识到,我解决错问题了。
难点从来不是"AI 能不能改对代码"------它大部分时候确实能改对。难点是我没有任何办法,在不逐行读完的前提下,知道哪几个是对的。
一个我不敢信的自动化系统,比没有还糟:我不但要审 8 个 diff,还得先揣摩 AI 当时在想什么。
这篇讲的是后来怎么解决这个问题的。核心是四件事:
- 让另一个 Agent 来复核,而且给它的信息要精心裁剪
- 给模型一个说"我不确定"的地方
- 分清哪些问题是判断题 、哪些是事实题------事实题根本不该让模型回答
- 想清楚哪些事情不能让 AI 做------不是靠 prompt 求它,是靠架构不给它
(并行怎么跑通、worktree 怎么隔离,在上篇《8 个 Agent 同时改一个仓库,我踩了哪些坑》,不看也不影响这篇。)
代码已脱敏开源:github.com/DonChengChe...
一、为什么复核必须是另一个 Agent
第一版我是这么做的:让修复 Agent 改完代码后,自己再检查一遍。
Prompt 写得很认真------"请重新审视你的修改,确认是否真的解决了问题、是否引入了回归"。跑完 8 个 bug,8 个都回复"已确认修复正确,无回归风险"。
我一条条审,其中 3 条是错的。
有意思的是错的那 3 条,自查理由写得比对的那几条还详细。
自查为什么必然失效
后来我想明白了,这不是 prompt 写得不够好,是结构性的问题。
自查和修复共享同一份上下文。 修复 Agent 走到这个 diff,是经过一串推理的:我认为根因是 A,所以我要改 B,所以我动了 C 文件。让它再看一遍,它做的不是"检验这个结果对不对",而是"确认我刚才那串推理成不成立"。而那串推理在它自己的上下文里是自洽的------否则它一开始就不会那么改。再叠加上模型本身就倾向于为自己已经写出来的东西辩护,自查基本等于走过场。
这就是校对自己文章的困境。 写完一篇东西自己校对,错别字永远看不见------大脑读的不是纸上的字,是记忆里的句子;眼睛扫过去,脑子自动补成了"本来想写的那个版本"。
修复 Agent 自查,读的也是"我本来想改成的样子",不是 diff 里真实的样子。
结论很简单:复核必须由一个没有参与修复的 Agent 来做。 它没有"我为什么这么改"的记忆,也就没有为之辩护的动力。
给它什么,不给它什么
换成独立 Agent 之后,真正需要设计的是信息边界------给多了给少了都不行。
我最后给它三样东西:
- bug 的原始描述(让它自己去拉,不接受二手转述)
- 完整的 diff
- 修复 Agent 声称的根因和改动文件列表
明确不给的是:修复 Agent 的完整推理过程。
js
`对抗式复核一个 bug 修复。默认立场是怀疑:只有证据充分才认可,存疑一律降级。
Bug #${bug.id}:${bug.title}
修复 agent 给出的根因:${fix.rootCause}
改动文件:${(fix.filesChanged || []).join(', ')}
diff:
${fix.diff}
先读 bug 详情核对期望行为:
<tracker> show ${bug.id}
...`
为什么不给推理过程?
因为一旦给了,复核 Agent 判断的对象就悄悄变了------它会去判断"这段推理听起来有没有道理",而不是"这个 diff 能不能修好这个 bug"。
而前者太容易过关了。一段结构完整、术语准确、逻辑连贯的推理,读起来就是有说服力的,哪怕它的前提是错的。你等于把自查的偏差换了个 Agent 重新引入了一遍。
只给结论不给过程,它就只能自己去对着 bug 描述和 diff 重新判断一次。这才是独立的意思。
同样的道理,bug 的原始描述我让它自己去拉,而不是把修复 Agent 读到的内容转述给它。
转述是有损的。修复 Agent 理解错了期望行为,转述过去,复核 Agent 就会拿着同一个错误的标准去验收------两个 Agent 一起错,还错得高度一致。必须让它从原始信源重新读一遍。
把立场写死在 Prompt 里
上面那段 prompt 一头一尾各压了一次立场------开头「默认立场是怀疑:只有证据充分才认可,存疑一律降级」,结尾「只要有一处说不清就给 needs-human 或 reject,不要放水」。
这不是提示词技巧,是在对抗模型的系统性偏差。模型的默认状态是配合、是给正面结论;你不主动把它扳到怀疑那一侧,它就会滑回"看起来没问题"。
这里有个反直觉的地方:复核 Agent 的目标不是"判断对错",是"尽力找出问题"。 两者听起来差不多,产出完全不同------"判断对错"是中立姿态,模型会滑向认可;"尽力找出问题"是有方向的任务,模型才会真的去翻边界条件。
它到底问哪三个问题
复核 Agent 被要求回答三个问题,每个对应一类失败:
text
1. 这个 diff 是否真的能修好该 bug(对照重现步骤与期望行为)?
2. 有没有引入回归、边界遗漏、破坏其他调用方?
3. 修复 agent 声称的自测是否真的覆盖了这个改动?
第 1 条查有效性------改了,但改的是不是这个问题。这是最基础的。
第 2 条查副作用------修好了 A,有没有弄坏 B。AI 修 bug 最常见的翻车方式:为了让当前 case 通过,改了一个被很多地方调用的公共函数。
第 3 条是我加得最晚、但现在觉得最重要的一条。
它查的不是代码,是**"AI 提供的证据本身可不可信"**。
修复 Agent 会在 checksRun 字段里报告自己跑了什么自测。实际情况是它经常写"已执行 type-check,通过"------而那次 type-check 根本没覆盖到它改的文件;或者它跑了单测,但那个测试文件跟本次改动毫无关系。
它没有撒谎,它只是把"我跑了一个检查"和"我验证了这个改动"当成了同一件事。
所以复核 Agent 必须专门被要求去核对这一条:你说你验证过了,你验证的东西和你改的东西是同一个吗?
一个真实的例子:测试全绿,但测试没在保护修复
脱敏说明:组件名和数字来自真实的一轮运行,业务标识已替换。
bug 是「某详情页在未认领状态下,顶部流程条的文字全部换行」。
修复 Agent 定位得很准:流程条根容器是 flex items-center justify-between,左侧流程节点和右侧操作按钮都没有 shrink-0 / whitespace-nowrap 保护;未认领时右侧多出一个按钮,总宽超出了面板固定宽度,中文任意字间可断行,于是每个标签都塌成两行。
它给出的修复也是对的:右侧按钮加 shrink-0 whitespace-nowrap,容器间距 gap-3→gap-2、按钮内边距 px-3→px-2.5;左侧加 min-w-0、步骤名加 truncate。
它还补了 4 个测试 ,全部通过。type-check、lint 全绿。自述 confidence: high。
看起来无懈可击。复核 Agent 把它降级成了 needs-human,三条理由,一条比一条难堪。
第一,自述的数据是错的。 修复 Agent 说宽度缺口是 9px。复核 Agent 用仓库真实的样式产物加浏览器实测,量出来是 16px。机制和量级都对,但它报出来的那个数字,是它推算的,不是它量的。
第二,新增的 4 个测试全是 className 字符串断言,而 jsdom 根本不做布局。 复核 Agent 做了一件很狠的事:只回滚掉「收紧间距」那部分(px-2.5→px-3、gap-2→gap-3),保留其余所有改动,然后重跑测试------16 项全部通过。
但实测布局已经塌了:左侧被压到 624px,6 个步骤名全部退化成省略号。
而那 44px 的间距收紧,正是让内容装进容器的承重部分。也就是说:真正修好这个 bug 的那一半改动,没有任何测试保护。测试全绿,但测试保护的不是修复。
第三,测试夹具压根没构造出 bug 的触发条件。 夹具里的流程只有 5 步(注释还自己写着「完整 5 步」),而这个 bug 只在 6 步时才溢出------5 步的宽度根本不会超。新测试的注释声称「6 个流程节点」,夹具里却是 5 个。
这三条串起来看,就是那三个复核问题各中一枪 :自述数据不可信(证据)、测试没覆盖承重改动(证据)、夹具没构造出条件(证据)。修复本身是对的,但它提供的所有"我验证过了"的证据,没有一条真正成立。
修复 Agent 自查是绝无可能发现这些的。它知道自己加了测试、测试过了------它不会想到去回滚自己改动的一半再跑一遍,看看测试会不会挂。那需要一个想找茬的人。
顺带:怎么知道一个测试有没有判别力
上面第二条其实给出了方法------回滚被测的改动,看测试会不会挂。
这个动作后来固化进了流程,同一轮里另外两个 bug 都做了:
- 一个改动是把绝对定位的下拉改成
createPortal到 body。撤掉 portal,断言失败 ✅ - 另一个是给接口响应加了一层解包 helper。改回不解包,两个测试文件各挂一条 ✅
通过的测试不等于有效的测试。 一个永远不会失败的断言,和没有断言是一回事------但它在报告里长得跟真测试一模一样,还会让人放松警惕。
要确认一个测试真的在守着什么,唯一的办法是让它挂一次。
二、三分类:为什么"不确定"必须是一等公民
复核 Agent 就位之后,还剩一个问题:它的结论应该长什么样?
最自然的设计是布尔值------通过,或者不通过。我一开始也是这么写的。
这个设计是错的,而且错得很隐蔽。
二分类的结构本身在制造假阳性
设想复核 Agent 遇到这么一个 diff:改动看起来是相关的,逻辑也说得通,但它没法确定这是不是真的修好了------可能需要跑起来看,可能需要问产品这个交互到底期望是什么。
它现在只有两个选项:pass 或 reject。
它会选哪个?
几乎一定是 pass。 因为 reject 是一个更强的断言------你说它错了,你得说出错在哪。而"我说不准"这个状态,在 reject 那一侧是无法表达的。相比之下 pass 的心理成本低得多:改动看起来没毛病,那就过吧。
所以这里的问题不在模型,在我给它的选项集合里没有一个格子能装"不确定"。你逼一个不确定的判断二选一,它就会往低阻力的那一侧倒。
二分类的结构本身在制造假阳性。
改成三个值之后,这个问题就消失了:
js
verdict: {
type: 'string',
enum: ['pass', 'needs-human', 'reject'],
description: 'pass=可交人审核合并;needs-human=存疑需人工;reject=明显没修对或有回归'
}
pass------ 可以交给人审核合并needs-human------ 存疑,需要人工确认reject------ 明显没修对,或者有回归
顺带一提,这些字段都是用 JSON Schema 强制模型走工具调用返回的,不是让它写自由文本再解析------校验在工具层做,不匹配会自动重试。自由文本加正则的方案迟早会崩,而且崩得很安静 :解析失败 fallback 成默认值,你以为流水线跑通了,收到的其实是一堆空壳。在结论要驱动后续动作的场景里,输出格式的确定性和模型判断的准确性同等重要。
配套的 prompt 也要把这条路显式指出来,不能只在 schema 里放着:
text
只要有一处说不清就给 needs-human 或 reject,不要放水。
一句话概括:
给模型一个诚实的出口,它才不会撒谎。
这条我觉得可以推广到所有让 LLM 做判断的场景。凡是设计输出格式的时候,先问一句:如果它真的不知道,它有地方说吗? 没有的话,你收到的每一个肯定回答都要打个折。
两类不同的不确定性,要分别捕获
needs-human 不只出现在复核端。修复 Agent 的输出结构里也有一个:
js
needsHuman: {
type: 'boolean',
description: '定位不到 / 不确定改得对 / 需产品确认时置 true'
}
一开始我觉得这是冗余------反正后面有复核,让复核统一判断不就行了。
后来发现不行,因为这两个地方知道的是完全不同的两件事:
- 修复 Agent 知道的是过程:我 grep 了半天没找到相关代码;这个交互的期望行为业务上有歧义;我改了但我自己心里没底
- 复核 Agent 知道的是结果:这个 diff 看起来修的不是这个问题;这里有回归风险
第一类不确定性在 diff 里是看不出来的。一个"我瞎猜着改的"的 diff 和一个"我确信无疑"的 diff,长得可能一模一样。复核 Agent 只拿到 diff,它没有机会知道修复 Agent 当时有多没底。
所以必须在修复端就把它捕获下来,配一句很硬的指令:
text
如果定位不到代码、不确定改得对、或需要产品/设计确认,就把 needsHuman 置为 true 并在 reason 里说清楚,
**不要硬编一个可能错的修复**。宁可交人工,也不要制造假阳性。
"宁可交人工,也不要制造假阳性"------这句话是整套系统的价值排序。
理由很实际:一个被标成 needs-human 的 bug,我花 2 分钟看一眼就知道该怎么办。而一个被错误标成 pass 的 bug,我可能花 20 分钟审 diff 才发现它是错的------更糟的情况是我没发现,它合进去了。
假阳性的代价远高于假阴性。系统的默认倾向必须往保守那一侧偏。
短路:不给死掉的修复浪费一个复核 Agent
有了 needsHuman 之后,编排上就能做一个短路:
js
if (!fix || fix.needsHuman || !fix.diff || !(fix.filesChanged && fix.filesChanged.length)) {
const reason = !fix
? '修复 agent 无返回(中途跳过或失败)'
: fix.needsHuman
? (fix.reason || '修复 agent 主动标记需人工')
: '修复 agent 未产出有效改动'
return { bug, fix, verify: { verdict: 'needs-human', reason, regressionRisk: 'unknown' } }
}
四种情况直接跳过复核:Agent 没返回、主动标了需人工、没产出 diff、没改任何文件。
省一次模型调用只是顺带的好处。真正的理由是语义:
修复 Agent 已经说了"我不确定",你再派一个 Agent 去复核一个空的或不可信的 diff,它给出的任何结论都是没有意义的。让一个 Agent 去评价不存在的东西,它不会拒绝------它会编一个看起来合理的评价出来。
在明知无意义的地方调用模型,得到的不是"没有信息",是"错误的信息"。 这比不调用更糟。
三、判断题交给模型,事实题自己跑
到这里,信任链是:不信修复 Agent 的自查,所以派了独立的复核 Agent;给了它三分类,让它能说"我不确定"。
那复核 Agent 说 pass,就可以信了吗?
不能。因为它也是个 LLM,前面数落修复 Agent 的那些毛病,它一样有。它同样会把"我看了一眼"说成"我验证过了";它报告"自测已覆盖"的时候,有时是自己核实的,有时只是把修复 Agent 的自述换了个说法复述一遍。
再往上叠一个复核 Agent 显然是不对的------那只是把同一个问题往后推一层,而且推得越远越不划算。
真正的出路不是"再派一个更聪明的 Agent",是换一种验证方式。
两类问题,混在一起了
回头看复核环节要回答的问题,其实是两类,性质完全不同:
- 「这个改动能不能修好这个 bug」 ------ 判断题。需要理解意图、权衡上下文,没有客观答案,只能交给模型
- 「
tsc --noEmit的退出码是 0 还是 1」 ------ 事实题。跑一下就知道,不需要任何判断
第二类问题被混进模型的职责里,是因为它以自述的形式出现------Agent 在 checksRun 字段里写"已执行 type-check,通过"。这句话读起来像结论,其实是个待核实的声明。
而它是可以被零成本核实的。
能用确定性检查回答的问题,就别让模型回答。
主会话实际跑的三件事
所以在拿到全部结果之后、生成报告之前,主会话会自己跑一遍,不采信任何 Agent 的自述:
1. 类型检查 ------ 逐个 worktree 跑 tsc --noEmit,要求退出码 0 且零输出。
只看退出码不够。有些配置下 tsc 会打出警告但仍然退出 0,那种"通过"不是我要的。
2. 相关测试套件 ------ 自己跑一遍,看实际的通过项数。
Agent 会报"239 项全绿"。主会话跑出来也是 239 项全绿------那这层是不是白做了?
不是。你不知道它是不是真跑了,直到你自己跑一遍。 这是典型的验证成本远低于信任失败成本的场景:跑一遍几十秒,不消耗任何模型调用;而信错一次的代价是一个坏改动合进主干。
3. git status ------ 这条最容易被忽略,但抓到的问题最多。
Agent 干活过程中经常留下东西:调试用的临时脚本、打印日志的文件、试验到一半的 .bak。它不会在报告里提这些,因为在它的理解里那不属于"修复"------它汇报的是"我改了什么来修这个 bug",而不是"我在这个目录里留下了什么"。
这两者的差集,就是残留。
所以主会话直接看 git status:只允许出现预期的改动文件,多一个都要问清楚。
这层的性质和前两层不一样
前两层------独立复核、三分类------本质上都是在对抗模型的偏差:设计信息边界、设定怀疑立场、给"不确定"留出口。做得再好,也只是让判断更可靠一点,不可能让它变成确定的。
第三层不做任何判断,只跑命令看返回值,结论不依赖任何模型的状态。
设计这类系统的时候,值得反复问自己一句:我现在让模型判断的这件事,有没有一个命令能直接给出答案?
有的话,别让它判断。
四、红线:系统的输出是"待审材料",不是"已完成的事"
这套系统跑完一轮,不会 commit,不会 push,不会往缺陷管理系统里写任何东西。
第一次跟人讲的时候,对方的反应是:那自动化的意义在哪?不就是为了少干活吗?
意义在于,它把我要干的活从"修 8 个 bug"变成了"审 8 个 diff"。而**"审"这个动作,恰恰是不能自动化的那一部分**。
可撤销的交给 AI,不可撤销的必须过人
整套系统最硬的一条约束是这个:
text
红线:没有真实 commit hash 时,禁止自动回写缺陷系统
------备注里的 Commit ID 不能造假。
背景是这样:修完 bug 之后,正常流程要往缺陷管理系统里写一条解决说明,包含根因、改动内容、以及对应的 Commit ID。让 AI 顺手把这步也做了,看起来是很自然的自动化延伸。
但它会造假。
不是恶意造假------是它手里根本没有真实的 commit hash(因为系统压根没 commit),而输出格式要求这个字段。当一个字段必须填、而正确答案不可得时,模型会填一个格式正确的值。 一串看起来完全合法的 40 位十六进制。
这条记录一旦写进去,污染的不是我的本地环境,是整个团队对"这个 bug 到底修没修"的共识。测试看到状态是"已解决"就去验;产品看到就去排上线;QA 顺着 Commit ID 去查代码,查不到,然后开始怀疑是不是自己搞错了。
而且这种污染很难被发现,也很难回滚。
所以那条原则值得单独拎出来:
可撤销的操作可以放手给 AI,不可撤销的必须过人。
本地代码改错了,
git checkout就回来了。往团队协作系统里写一条假记录,没有 undo。
判断一个动作该不该给 AI,别问"它能不能做对",问"它做错了,谁来收拾、收拾得掉吗"。
约束要放在系统里,不是放在 Prompt 里
这一点比红线本身更重要。
我没有在 prompt 里写"请不要自动回写缺陷系统"。求它别做是不可靠的------prompt 是软约束,模型在长上下文里会忘、会被后面的指令覆盖、会自己"合理推断"你其实想要它做。
真正的做法是架构上不给它这条路:
- workflow 的返回值里只有
{ bug, fix, verify },没有任何"已提交""已回写"的动作 - 回写走的是完全独立的另一套流程,由人在拿到真实 commit hash 之后手动触发
- 两条路径在代码层面就不连通
能力边界要用架构划,不要用措辞划。
你在 prompt 里写十遍"绝对不要 X",都不如系统里根本没有 X 这个函数。
这也是我觉得很多"AI Agent 安全实践"讨论跑偏的地方------都在研究怎么把提示词写得更严密,而真正有效的是:把危险的能力从工具集里拿掉。
连 commit 都不做,则是另一个理由:commit 会制造"已完成"的错觉。 一旦有了 8 个分支、8 个 commit,我的心理状态就从"我要审 8 个改动"变成"活已经干完了,我走个流程"------而审查质量是这套系统唯一的安全网。所以输出停在"改动躺在各自的 worktree 里,等你看"。
一个 AI 系统的输出,应该是"待审材料",不是"已完成的事"。
两个不可跳过的人工卡点
流程里有两处硬卡点,用户不确认就不往下走。
第一处:产品 → 仓库的映射。
这个看着最不像需要卡的地方,恰恰最危险。我的多个项目仓库名字高度相似------比如 project-a 和 project-a-weixin,光看产品名根本判断不出该改哪个。
文档里写得很直白:不要凭名字自动认定。
因为这一步错了,后果会被放大 8 倍------8 个 Agent 在一个错误的仓库里,认认真真地改了一堆代码。 而且它们不会报错,会给你一份格式完美的报告。
第二处:待修 bug 清单。
拉出列表、按严重程度排序、截断到 N 条之后,要列出来给人确认,可以增删。未确认不得开跑。
这两处的共同点:都是"错了之后后果被放大 N 倍"的位置。
人工卡点不该均匀撒在流程里------那样只会让人疲劳到闭眼点确认。要放在扇出点之前:一个决策后面挂着多少并行动作,它就值多少审查成本。
交付物:一份分诊报告
最后产出的是一份三分区的 markdown 报告:
text
✅ 可审核合并 ------ 根因 / 改动文件 / 分支 / 看 diff 的命令 / 自测结论
⚠️ 需人工确认 ------ 存疑原因
❌ 没搞定 ------ 失败原因,建议人工处理
它也存在持久目录里,跟 worktree 放一起,会话结束也不会丢。
这份报告,才是整套系统真正的产出:
8 个 bug 里告诉我:5 个可以直接审、2 个要小心看、1 个别浪费时间了。
这套系统节省的不是"改代码"的时间,是"判断哪些值得我花时间"的时间。
五、还没解决的问题
上面讲的都是能用的部分。下面是明知有问题、但还没解决的部分。
验证的天花板:没真跑起来
为了避免 worktree 之间的构建产物竞争(细节在上篇),Agent 只被允许跑非写型检查------type-check、lint、相关单测。
这意味着它从来没有真的把代码跑起来看过一眼。
对于类型错误、明显的逻辑疏漏,这套检查够用。但对"点了按钮没反应""某个状态在特定路径下没恢复"这类问题,type-check 全绿说明不了任何事情。UI 交互类的 bug,最终还是靠人打开浏览器点一遍。
下一步想做的是接浏览器自动化:让 Agent 按 bug 描述里的重现步骤实际操作一遍,改之前复现失败、改之后复现成功,才算修好。这才是真正的闭环。
但那要解决端口冲突、构建产物隔离一系列问题------也就是说,得先把"共享可写状态"那个口子彻底堵上,而不是绕开。
复核只有一票
现在每个修复只有一个复核 Agent 看,它说 pass 就是 pass。
更稳的做法是多个视角各判一次再投票------比如一个专看正确性、一个专看边界条件、一个专看有没有破坏其他调用方,两票以上通过才算 pass。不同视角能抓到的失败模式是不一样的,单纯跑三个相同的复核 Agent 意义不大。
没做的原因很实在:成本翻三倍,而目前单票的漏判率我还能接受。这是个明确的成本-质量取舍,不是没想到。
max=8 是个拍脑袋的数
系统默认一轮最多处理 8 个 bug。
这个数字不是压测出来的,也不是机器的极限------是我一次能认真审几个 diff 的极限。
这暴露了一件事:自动化把瓶颈往后推了一格,但没有消灭瓶颈。
以前的瓶颈是"我改代码的速度",现在的瓶颈是"我审代码的速度"。后者确实比前者快,但它有上限,而且这个上限比大多数人想象的低------连续审 15 个 AI 生成的 diff,到后面注意力会明显下滑,而注意力下滑的时候,恰恰是那些"看起来没问题"的错误最容易溜过去的时候。
所以这个 8 不该被调大。真要提升吞吐,方向是提高每个 diff 的可信度(比如上面说的多视角复核、真跑验证),让我可以更快地略过一部分,而不是让我看更多。
只看 diff,看不见隐式耦合
复核 Agent 拿到的是 diff 加 bug 描述。
diff 能告诉它"改了什么",但告诉不了它"这个改动会影响谁"。一个函数被十几个地方调用,或者某个字段被另一个模块通过约定俗成的方式读取------这些跨文件的隐式耦合,在 diff 里是隐形的。
理论上复核 Agent 可以自己去 grep,它也有这个能力。但没有强制要求,它经常不做------"看起来这个改动很局部"是一个非常有诱惑力的偷懒理由。
最大的问题:没有度量
前面几条至少我知道问题在哪。这一条是我连问题有多大都不知道。
我不知道这套系统的假阳性率到底是多少。
我只有主观印象------"感觉比第一版靠谱多了"。但"感觉"不是数据。可能三分类之后假阳性从 40% 降到了 5%,也可能只降到了 25%,我区分不出来。
要真正回答这个问题,需要一套评测:攒一批已知答案的历史 bug 当测试集,跑一遍,统计各分类的准确率,改一版 prompt 再跑一遍,看指标动了没有。
没有评测,所有的优化都是凭手感。 每次改 prompt 我都觉得"这样应该更好",但我拿不出证据。
这是这套系统和一个真正能上生产的 AI 系统之间,最本质的差距。也是我接下来要补的第一件事。
最后
回头看,这套系统里所有的设计------独立 Agent 对抗式复核、三分类里的 needs-human、把事实题从模型手里拿走、不可撤销操作的红线、扇出点前的人工卡点------都是在回答同一个问题:
怎么让一堆不可信的产出,变成可审的交付物。
它没有让 AI 变得更可靠。AI 该错还是会错,该编还是会编。它做的是把"错"这件事从"随机散落在 8 份看起来都很有说服力的报告里",变成"集中在被明确标记出来的那几条里"。
而这个转变的代价,是主动放弃了一部分自动化------不 commit、不回写、两处人工卡点。在这类系统里,"少做一点"往往才是对的设计。
留一句:
让 AI 写代码是容易的部分。难的部分是设计一套机制,让你能在不逐行读完的前提下,知道哪些能信、哪些不能。
这套东西真正做的事,不是"自动修 bug"------是把"AI 的输出"翻译成"人类的待办事项"。
代码已脱敏开源:github.com/DonChengChe...