
一个 PR 上面挂十几条 AI 评论。逐条点开:三条在纠同一个变量命名,两条指着根本没改过的旧代码,还有一条说这里漏了空指针判断,可那句判断就写在它上面三行。关掉吧,怕真漏了东西;留着吧,得逐条替它擦屁股。更别扭的是,同一个提交隔几个小时再跑一遍,冒出来的意见又换了一批。
这类问题不能简单归成「模型还不够强」。LLM 做代码审查有两个毛病,跟模型聪不聪明关系不大,是结构性的。
一、结果飘
纯 agent 式的审查,靠模型自己决定看哪些文件、搜什么、读多深。这个自由度听着很美,代价是结果不稳。同一份代码、同一个模型,两次运行可能给出两套结论。工具越通用(比如直接甩给它一个 bash),动作空间越大,越飘。附带还有一个 token 雪球:对话式的 agent 循环是有状态的,每一轮都把之前的工具输出和上下文重新塞回去,请求越滚越长,成本不是线性增长,而是越滚越贵。
二、看得窄
另一头的问题是视野太小。很多方案本质上是「把 diff 丢给模型,让它写评论」,模型的注意力被锁死在改动附近那一圈。可真正值钱的问题常常跨文件:某个函数的调用方期待什么样的返回值,一处改动会不会把远处的调用者带崩,新代码有没有违反仓库里早就形成的约定。这些光盯着 diff 看不出来。
先看一组不太好看的数字
一项覆盖 3,109 个 PR 的实证研究显示,只用代码审查 agent 处理的 PR,合并率是 45.2%,纯人工审查是 68.4%。更扎眼的是被拒的那批 agent PR,有 60% 的信噪比低于 30%。翻译过来就是:它说了一大堆,真正有用的没几句。
一条有价值的评论,必须把改动放回整个仓库里看,看它和调用方、被调用方、共享数据结构、项目约定怎么互动,而不是只看被改的那几行。而现有做法,不管是拿数据微调模型、给模型喂检索到的上下文,还是把审查拆成多个 agent 分工,视野最后都落在 diff 及其周边,深度上不去。
核心思路:能算出来的,就别让模型猜
阿里把这套东西开源了,项目叫 open-code-review(简称 OCR),论文标题把立场直接写在脸上:《Determinism over Non-Determinism for Cost-Effective Agent-Based Code Review》,用确定性压过非确定性。它内部已经用了两年,服务过几万名开发者,捞出来的缺陷以百万计,今年 9 月才放出来。
它的核心设计一句话说清:确定性和 agent 各干各擅长的。
哪些活交给工程逻辑?那些绝对不许出错的环节,不交给语言模型,用代码保证正确:
- 文件选择:哪些文件该审、哪些该过滤,用规则定死,保证不遗漏重要改动。
- 文件打包:相关联的文件捆成一个审查单元(比如 message_en.properties 和 message_zh.properties 绑在一起),每个单元是一个独立的 sub-agent,上下文互相隔离。这是分治,大改动集上稳得住,还天然能并行。
- 规则匹配:按每个文件的特征匹配审查规则,把模型注意力收窄,从源头掐掉信息噪声。用模板引擎匹配,比纯靠自然语言描述规则更稳、更可预测。
- 外部定位与反思:评论定位、评论反思各自独立成模块,专门提升位置和内容的准确度。
哪些活留给 agent?动态决策和动态取上下文:一套针对代码审查深度调优的 prompt 模板,一套从大规模生产数据的工具调用轨迹里蒸馏出来的专用工具集。工具集为什么要专门做?通用 agent 工具包的动作空间太大,飘;专用工具集在审查这个场景里更稳、更可预测。
这个分工背后有一条朴素的判断标准:如果一个问题的答案是算得出来的,那算出来比让模型去猜更便宜、也更可信。
确定性被钉在三个点上
1. 规则引导的分派(Rule-Guided Dispatch)
用四层规则链(内置、用户全局、项目级、临时)决定每个改动文件适用什么审查标准。不是让 agent 自己判断哪个文件值得看,而是规则定死看什么(哪些文件)和怎么看(什么标准)。同一个 PR 每次得到的文件和标准分配都一样,后面所有步骤就有了一个可复现的起点。
2. 有据可依的文件审查(Grounded File Review)
每个文件配一个 sub-agent,跑在 ReAct 循环里,但它能用的工具是固定的六个:file_read、file_find、code_search、file_read_diff、code_comment、task_done,每个工具的输出都有上界。上界是刻意加的:任何一次工具调用都不能把上下文窗口撑爆。它不是不给模型探索的自由,而是把自由收窄到审查真正需要的那几种操作上。这六个工具也不是拍脑袋定的,是从专家审查真实 diff 时怎么取上下文的行为数据里提炼出来的。文件级并行让每个 sub-agent 的上下文互相隔离,既不会积成一坨,又能在需要时按需追溯跨文件依赖。
3. 独立反思(Independent Reflection)
这一步最有意思。
agent 跑长任务容易产生幻觉,编出根本没发生的问题。常见的两种修法都不太好使:让同一个模型自己反思自己的输出(Reflexion、Self-Refine 那一类),它会继承导致错误的同一套偏见,越反思越确认;或者让程序分析器去验证,但程序分析器只能核对很窄的一类事实,管不了语义层面的评论。
OCR 的做法是换个角度,不换模型。反思模块还是用同一个 LLM,但给它更少的信息,它只看 diff,看不到 agent 那一堆工具调用探出来的额外上下文。在这条不对称的信息边界上,它做的是证伪优先:只过滤那些被 diff 证据直接推翻的评论,而且只做过滤,不生成。
论文里有一句话点破了它的意义:对反思来说,信息边界可能比模型身份更重要。同一个模型,视野小一点、角度刁一点,反而比「同模型同上下文自查」更能把假评论筛掉。
效果
在 AACR-Bench 上测的,200 个真实 PR、10 种语言、50 个仓库、1,505 条经过专家核验的评论,这些答案由 80 多位资深工程师三轮交叉验证过。跨 6 个 LLM 后端:
- 最好的配置(Claude-4.6-Opus)拿到 25.10% 的 SEM-F1,精确率 33.90%,召回 20.00%,平均耗 38.5 万 token、1 分 23 秒。同一个模型跑在 Claude Code 里只有 11.57% 的 SEM-F1,精确率 7.23%,平均耗 566 万 token、13 分 06 秒。
- 换算下来,SEM-F1 高 2.17 倍,token 少 5 到 15 倍。
- 而且不是靠堆召回换来的。OCR 的精确率落在 25.20% 到 37.80%,Claude Code 是 7.23% 到 15.93%。Claude Code 靠多说换召回,200 个 PR 吐出 4,580 条评论,OCR 只吐 465 到 1,096 条。
- 就算把最弱的 OCR 配置(Deepseek-V4-Pro,17.90%)拎出来,也高过最强的 Claude Code 配置(14.13%)。
两个数字摆在一起看很有味道:同一个 Claude-4.6-Opus,在 OCR 里排第一,在 Claude Code 里排到接近垫底。差的不是模型,是系统设计。
这套模式值得抄到别处
代码审查只是第一个落点。这条思路真正通用的地方在于:凡是有明确对错、边界清晰、又要可审计的重复性任务,先把能算的部分算掉,再让模型处理剩下那点真正需要判断的东西。
- 安全扫描、依赖风险、密钥泄露这类检查,答案是确定的,规则跑一遍就有结论,不需要推理。让模型逐条重新发现同一个 SQL 拼接漏洞,是在为一个没有分歧的答案反复付推理费。有个旁证:混合 LLM 加静态分析的方案,在各种骨干模型上把误报消掉了 94% 到 98%,同时保住召回。
- 合规检查、文档校验、格式与接口一致性,同理。
- 再往大了说,任何重复、任务边界清楚、结论要能追溯的 agent 场景,都值得先问一句:这一步的答案,是算得出来的,还是必须靠判断?
设计上能直接照搬的几条:
- 先把确定性检查当过滤器放在最前面,不要当事后补丁挂在 agent 流程屁股上。
- 主动收窄 agent 的动作空间,给每个工具的输出设上界,防 token 雪球。
- 需要校验时,优先动信息边界而不是换模型,让校验方只看到一部分证据,反而更独立。
- 让模型可替换,让流水线成为产品。模型几个月换一茬,值得长期投入的是一个不随模型变动的审查流程。
别急着上头,代价也要说清
混合架构不是免费的午餐,它有自己的税。
两套系统要养。规则集和 agent 流程是两拨东西,各有各的维护成本,各有各的腐烂方式。规则会过期:你的代码库长出一个新框架,规则集里没人补对应条目,那部分覆盖就悄悄没了。更隐蔽的是温水煮青蛙:规则没更新,LLM 那一半开始偷偷替规则兜底,把本该固化成规则的重复问题,一次次用推理临时挡掉。看着还能用,成本却一直漏。重复报告也是真问题,同一个问题被确定性半边和 LLM 半边各报一次,得去重。
论文作者自己也承认,SEM-F1 这种语义匹配指标测不出可操作性、清晰度、严重性这些东西,而一条真有用但匹配不上标准答案的评论会被算成误报。所以那 25% 的精确率是下限性质的数字,实际体感可能更好,但别把它当成绝对承诺。
小结
AI 代码审查这一年多的路线,正在从「把 diff 丢给一个万能 agent」往「确定性工程加受限 agent」挪。这件事的意义超出了代码审查本身:它说明非确定性不是 LLM agent 的天性,而是可以被工程化消掉的设计缺陷。你不需要一个更聪明的模型,你需要一个更克制的系统。
能算的就别猜。这句话配得上成为接下来一年 agent 设计的默认起点。
参考来源: