
三个月前我去了一趟技术支持群看到一个做了三年 Code Review 工具的朋友在群里吐槽“我们用大模型接了一套代码审查助手前期效果很惊艳用久了问题却越来越多。同一个模型让它查算法逻辑问题表现不错可让它审前端样式、SQL 索引这种跟算法八竿子打不着的改动经常答非所问。最难受的是它经常把一些团队自己的约定当成错误报出来我一UNit时间大半都花在过滤无效告警上了。”这段话让我特别有共鸣因为我们团队在半年前上线多模型智能代码审查系统时也被“单一模型覆盖所有代码审查场景”这个看似省事儿的思路坑得不轻。经过一段时间的挣扎、重构和验证我们最终放弃了“用一个大而全的模型包打天下”的路线改成了一套“多模型协同、按任务路由、分角色审查”的架构。这篇文章就把我们这大半年在智能代码审查方向上的实践、踩坑和思考完整写出来。里面会涉及多模型路由设计、Prompt 分配、上下文裁剪、意见冲突处理、成本控制和效果评估这些环节。如果你正准备给团队或公司的代码评审流程接入大模型能力或者你已经在用单模型但总觉得“哪里不对”这篇文章的内容应该能让你少走不少弯路。1. 单模型做全量代码审查为什么总在关键问题上掉链子很多团队接入大模型做代码审查第一反应都是找一个综合能力最强的模型把所有代码变更都丢给它觉得“模型强就够了”。在十几个 PR 的小体量验证阶段这种方案确实能给人一种“整挺好”的错觉——基础语法问题能查明显的空指针能查变量命名不规范也能给个提醒。但一旦把审查范围扩大到不同语言、不同业务模块、不同代码改动类型问题会变得很具体。1.1 不同代码审查任务对模型能力的要求是冲突的注意我说的是“冲突”不只是“不同”。代码审查场景看起来只有一个——看代码写得好不好——但实际拆开它包含至少四类差异很大的任务静态规范检查、逻辑正确性分析、并发和性能隐患识别、业务语义合规判断。这四类任务对模型的要求完全不一样。静态规范检查需要的是极强的规则记忆能力和对团队自定义规范的精准理解逻辑正确性分析需要的是长上下文推理和小范围状态跟踪并发与性能隐患识别需要模型对底层运行机制有大量的、精确的先验知识而业务语义合规判断则更偏“这个改法是否符合业务预期”这类跟代码上下文强相关的任务。一个模型如果想同时做好这四件事往往意味着它在任何一个方向上都无法做到极致。拿一个综合能力很强的大模型做实验它在处理一段复杂的并发代码时可能给出不错的建议但你马上丢给它一段普通业务代码让它判断异常处理是否合理它的输出就可能开始泛化、使用模板式的话术甚至出现互相矛盾的审查意见。比如我们团队早期拿单一通用模型跑一份“订单超时关单逻辑”的变更代码模型能准确说出“这里应该加乐观锁”但同一个模型同一个上下文让它审同一个 Diff 里一段简单的金额格式化工具函数它会一本正经地建议“把 double 换成 BigDecimal”——理由是“浮点数精度有问题”。可那段函数入参本身就是数据库里算好的 decimal 字符串改成 BigDecimal 属于纯噪音建议根本没有任何收益还会干扰真正有价值的信息。1.2 审查意见的风格漂移比技术错误更难接受比模型技术层面犯错更头痛的是它的风格漂移问题。这类问题在单模型长周期使用中几乎一定会出现主要体现在两个方向。第一个方向是审查结论的“倾向漂移”。同一个模型如果使用的人比较多对话上下文复杂它的输出分布会随着输入顺序和上下文长度产生明显波动。比如某个模型在上下文较短时偏向保守只会报“这个问题考虑一下”但上下文一旦接近模型窗口上限它又开始对很多原本不属于严重问题的代码给出 High severity 的评价。这种漂移很容易误导开发者的判断优先级。第二个方向是审查内容的“时效偏差”。一些通用模型的知识截断日期以后发生了变化的框架用法、函数签名、最佳实践模型并不会自动更新。如果团队在代码审查中某个阶段只依赖单一模型这个模型对某个已经被废弃的 API 仍然保持着“这是最佳实践”的错误判断那么你得到的不是审查而是持续性的反向指导。1.3 我们早期的“单模型增强尝试”为什么失败了最开始我们也尝试给单个模型加一堆前置规则比如先调用静态分析工具生成 AST 告警然后拼到 Prompt 里让模型结合规则再判断。但这样做产生了一个新的问题——上下文变长之后模型对 Prompt 后半部分的规则内容“记忆”越来越弱尤其当 diff 本身超过一千行时模型经常忽略静态分析工具给出的关键告警。我当时印象很深的一个案例是我们接入了一个 SQL 静态扫描结果作为 Prompt 上下文让模型复核一条慢查询问题。结果模型不仅没有结合 EXPLAIN 结果给出优化建议反而引用了上下文里的字段名编了一版并不存在的执行计划分析。单模型由于自身能力边界和注意力机制的限制很难真正把“规则代码解释”三层信息同时用好。到这一步我们基本确定想用一个大模型解决代码审查的所有问题至少在目前阶段是不现实的。与其跟单模型的缺陷较劲不如换一个思路——既然代码审查本身就可以拆成不同工种那我们为什么不能用多个模型来分别扮演这些工种2. 多模型代码审查团队的具体架构角色分工与路由中枢把多个模型组合起来并不是简单地在代码里写几个 if 分支然后把不同 diff 分别发给不同模型这么粗糙。要让多模型真正产生“112”的效果需要设计一套清晰的架构和分工逻辑。我们最终采用的是“一名主审 两名专项审查员 一名最终意见整合者”的模拟团队结构。这个结构不一定适用所有团队但设计思路是共通的让每个模型只做自己最擅长的一个环节然后通过一个规则化或者半规则化的路由层去调度它们。2.1 四个角色如何分工为什么这样分主审模型我们选择了上下文能力强、综合理解能力扎实的商用大模型内部代号 Captain它负责做全局性的代码变更理解输出的是“这段代码改了什么、业务含义是什么、整体逻辑链路是否完整”。它不需要做一个字一个字挑错只需要在宏观层面判断变更是否引入架构级问题。专项审查模型则按审查对象拆成两路一路负责静态规范与细粒度代码风格我们选了本地部署的代码专用模型并且微调过团队规范另一路负责运行时问题与安全/性能隐患我们选了擅长逻辑推理与安全模式识别的模型比如近两年在代码任务评测上表现不错的新一代模型。这两路模型并不对所有文件都跑它们的路由规则是严格按文件类型、改动性质、关键词触发的。最后还有一个意见整合模型它不是独立的第三方大模型而是我们基于摘要模型构建的一层聚合器。主审和专项审查的结果会汇入这一层做去重、分级、过滤和语义重排。为什么需要意见整合者而不是直接把两个模型的意见拼在一起因为不同模型对同一个问题的表述是完全不一致的。有的模型会直接说“这里存在 NPE 风险”但另一个模型可能用五十个字描述同一个上下文如果不做一层聚合开发者看到的结果会非常碎片化。我们试过直接拼接开发者反馈“信息太密不知道改哪个”。2.2 路由中枢每一类代码改动到底该由谁来看多模型系统的核心是路由中枢。我们写了一个轻量的规则引擎按照 MR 的元数据、改动文件类型、函数关键字和变更量级自动分配任务。下面这个表是我们最终线上跑的路由规则简化版代码变更特征主审(Captain)规范专项模型运行时专项模型后端 Java 接口实现改动量 300 行是是是纯前端样式/文案调整是是否SQL 脚本或数据迁移否是是YAML/JSON/配置文件只读不审逻辑是否涉及 Redis/消息队列/异步任务是否是diff 大于 1000 行否先做文件级拆分分批分批这套路由规则看起来朴素但它解决了一个最关键的问题——不浪费。不同模型的计算成本和推理延迟完全不一样如果所有变更都跑三个模型成本会翻三倍延迟也会从几十秒涨到两三分钟。路由之后我们的 API 调用成本大约节约了 60%。2.3 让每个专职模型只看到它该看的部分很多团队的误区是给了多个模型却没有给它们划分上下文。路由做完了同一个 diff 文本还是原封不动地同时发给三个模型。在代码审查场景里这样做其实会稀释专职模型的优势——它虽然是你选出来的“规范审模型”但看到的上下文里可能 80% 是跟规范无关的逻辑重构这会影响它的注意力分配。我们给每个专项模型设计了不同的系统提示和上下文策略。对规范专项模型我们会把“本次变更涉及的文件之前是否有历史告警”也放进上下文对运行时专项模型我们会额外注入它对应技术栈的检查清单而主审模型的输入则做了降噪处理直接过滤掉纯粹格式调整的文件差异块。用系统提示词大致长这样这里以运行时专项模型为例——你是一名资深后端运行时审查员。你只关注与并发安全、事务边界、连接池、缓存一致性和异常吞没相关的问题。面对代码变更请忽略纯格式或代码风格问题那些将由另一位同事负责。你的输出必须按“风险描述 / 触发条件 / 最小修复建议 / 严重程度”四段式组织。如果你不确定触发条件请明确写出“不确定”不要猜测。别看它简单这套系统提示词在生产环境里带来的效果提升比换一个更大的模型还明显。因为多模型系统的价值根本不在于“把多个模型分别跑一遍再贴在一起”而在于让每个模型都能在自己的舒适区里用最稳定的方式输出最符合它角色定位的意见。3. 多模型协作中的真正难点意见冲突、上下文孤岛和成本飞涨架构设计完模型也接上了前两周我们一度觉得“稳了”。但在后面一个月里连续暴露了三个问题每一个都足以让这个系统退回单模型时代。3.1 意见冲突主审说改专项模型说不用改听谁的第一个我们遇到的问题是跨模型意见冲突。代码审查里很多问题不是非黑即白的比如对“是否需要把状态机抽成单独类”这类重构问题的判断主审模型和运行时专项模型经常给出完全相反的结论。有一次主审模型认为“订单状态流转逻辑太复杂建议抽象成独立状态机类”但运行时专项模型却指出“当前状态字段已经落库引入状态机框架会造成存量数据迁移风险不建议在本次需求中做”。如果我们把两条意见直接并列推给开发者开发者一定会觉得这系统“精神分裂”。我们一开始试图用规则来裁决比如“如果意见冲突以运行时专项模型为准”但很快发现这个规则过于粗暴——因为有些冲突是语义层面的并不是严重性层面的。最后我们采用的方案是引入一层“归类标记”。意见整合者不会强行抹掉任何一方的意见但它会把冲突标记出来同时生成一小段“冲突说明”指出两个模型冲突的核心争议点是什么、双方各自依赖哪些事实、建议开发者重点确认哪个环节。这样一来冲突本身反而变成了有价值的信息因为它帮开发者定位到了最容易踩坑的算法决策点。3.2 上下文孤岛合并了模型意见却丢失了跨文件依赖第二个问题比意见冲突更隐蔽我们内部叫它“上下文孤岛”。多模型分工以后每个模型默认只拿到自己负责的那部分代码。这会导致一个很严重的情况某个文件里的 BUG 本身不在这份文件里而是由另一个文件的改动引起的。如果一个专职模型只看自己分配的文件它根本发现不了这类 BUG。举一个真实出现过的例子。我们后端有一个用户积分发放的 MRA 文件修改了积分规则计算B 文件修改了积分入账的异步消息体。主审模型两份都看了指出 “B 文件拿到的积分值字段从 int 改成了 long”但规范专项模型只看了 A 文件它没有这个上下文于是把 A 文件里一个专门针对金额精度做得 long 类型比较的代码段判成了“可疑语法”。单看 A 文件这段确实有些绕但结合 B 文件的改动这个写法恰恰是正确的是为了避免下游消息体溢出做的适配。解决这个问题的方式是我们给路由中枢加了一个“跨文件影响传播层”所有文件级分析完成后会有一个传播阶段将每一份文件的输出摘要仅摘要不是原始代码广播给其他相关文件的分析上下文。这样每个专职模型在最终输出前会多一步“关联文件摘要核对”有效缓解了上下文孤岛问题。3.3 成本飞涨模型一多token 消耗就成了大头多模型系统最直接的代价就是成本。虽然路由已经把计算量降下来了但相比单模型方案开销仍然是成倍增长的。而且这里有一个很反直觉的地方成本涨幅最大的不是推理本身而是每个模型之间共享的“上下文重复发送”。一份 500 行的 diff三个模型各看一遍输入 token 天然是三倍。我们试过把共享上下文放到提示词里压缩但压缩会损失关键信息比如函数命名信息。后来我们做了两个优化。第一对进入路由的文件做差异化裁剪——比如纯前端改动就不会带着整个后端 service 类的旧代码第二对模型的审查结果引入“缓存复用”如果一段代码刚被主审模型完整分析过而规范专项模型也需要做类似分析我们会把主审的摘要直接注入专属模型让它只做增量判断而不是从头开始把整份 diff 重新读一遍。我们统计过一个月的 API 账单经过裁剪和增量复用后多模型系统的单 MR 平均 token 消耗从约 68K 降到了 41K审查效果没有下降。成本仍然是单模型的 1.8 倍左右但这 1.8 倍换来的是有效告警率接近翻倍从商业角度看这笔账很划算。4. 给多模型审查团队建立的评估闭环没有它一切优化都是盲人摸象其实我们在多模型系统上线大概三周后就发现了前面说的各种问题。之所以能准确判断“这些问题让系统变差了多少”是靠提前搭好了一套评估闭环。如果你不准备做评估集只凭开发者的主观反馈去调整多模型路由你极大概率会从一个坑跳到另一个坑。4.1 我们如何构建小规模的代码审查评估集代码审查效果的评估跟模型在通用 benchmark 上的评估完全不是一回事。通用的 HumanEval、MBPP 只能说明模型能不能写算法题跟“在团队的真实 MR 里能不能给出可执行的意见”完全是两个维度。所以我们做了一套“内部评审基准集”从历史已合入的 MR 里抽取了 128 份代表不同改动类型的记录。每条评估样本包含三个部分原始 MR 的变更描述和完整 diff至少两名资深工程师人工标注的“真实缺陷”清单包括轻微、中等、严重三个级别以及团队当时实际讨论结论和终版代码改法。线下评估的时候我们会让多模型系统完整跑一遍这 128 份样本然后跟人工标注做比对。评测指标我们主要看四个单 MR 有效意见数、严重缺陷召回率、无效意见率、轻微问题加权准确率。不追求计算复杂指标够用就行。当时做的对比结果很有意思。单模型方案的综合有效意见率大约是 41%也就是每 10 条自动审查意见里有 6 条左右是不需要开发者真正处理的而我们第一版多模型路由方案有效意见率提升到了 68%但严重缺陷召回率跟单模型差距不大说明很多高风险问题在路由阶段就被“漏”了——这也暴露了单纯做分工不解决核心问题的事实。4.2 让多模型团队持续改进的回归机制有了评估集我们就把模型升级变成了一个“可回归”的过程而不是一个凭感觉拍板的事。具体做法很朴素每周四晚上触发一次全量回归测试跑 128 份样本输出一份跟历史结果对比的指标变化报告。添加新模型或者切换路由策略时我们要求必须同时满足两个硬指标才允许合入严重缺陷召回率不低于当前线上版本无效意见率不得高于当前线上版本两个百分点以上。这个过程关键作用不是找最优模型而是防止“优化了一个指标却劣化了另一个指标”的情况发生。最典型的例子我们曾经尝试把一个上下文窗口更大的模型换到主审位置结果严重缺陷召回率微幅提升但无效意见率从 31% 升到了 39%系统整体体验反而变差了。有回归机制兜底我们就没有让这个版本上线。4.3 分级输出把注意力留给真正需要改的代码评估集还帮助我们重新设计了意见的呈现逻辑。前面说过多模型系统天然会比其他方案产生更多意见开发者如果不管三七二十一全看体验一定很差。最终我们按评估中发现的“意见可行动性”把所有审查意见分成了三个层级层级含义示例P0 必须处理不修复会直接引发线上故障或数据错误事务注解失效、并发更新覆盖、未处理唯一键冲突P1 建议修改不影响功能但会在特定条件下成为隐患资源未关闭路径、缺少必要的空值检查、状态流转缺失分支P2 可选优化属于最佳实践、团队规范、可读性层面魔法数字抽取常量、函数命名不达意、重复代码抽取三个层级分别采用不同的展示和推送策略。P0 意见直接推送给作者和 CR 负责人P1 在 MR 页面评论区内联展示P2 折叠到次要区域并且默认不产生通知。上线这个分级机制后开发者对审查系统的反馈从“意见太多、干扰严重”变成了“至少 P0 是真的值得看的”。后来我们又统计了三个月的线上数据这套多模型系统每月处理约 1800 个 MRP0 级别发现的真实问题稳定在 37~52 个之间有效意见率保持在 70% 左右。跟单模型时代相比最明显的变化不是模型变强了而是整个系统不再只依赖某一次大模型的超常发挥而是靠结构性分工保证了每一条意见的质量下限。5. 如果可以重来我会在项目第一天就做这几件事写到这里整个多模型智能代码审查系统的骨干内容基本讲完了。最后说几条我自己沉淀下来的经验和教训吧尤其是“如果让我重新来一次我会从第一天就开始做什么而不是后来返工补课”的部分。第一尽早定义“有效意见”的标注口径。代码审查类系统的效果是一个很模糊的概念。没有标注口径之前团队里每个人都觉得“系统有时候有用有时候没用”但是没有数字去衡量。后来我们统一把“有效”定义为开发者看完之后对代码做出至少一行实际修改或者确认了一个真实风险并补充了测试。这个定义不一定完美但它让所有人对系统的评估有了一个共同语言。第二不要把路由规则和模型绑定得太死。代码模型领域变化极快过几个月可能就出现一个新的开源模型在某类专项任务上能力飙升。如果路由规则是硬编码的“Java 一律发 A 模型、Python 一律发 B 模型”你升级模型的时候会很不灵活。我们后来的做法是把“模型能力描述”也写进路由配置里每次升级模型只改配置不需要改核心代码。第三上下文重复发送问题一定要从第一天就控制。如果一开始就不在意 token 结构和上下文裁剪后面接入的模型越多累计浪费越大。最好在管线设计之初就把“同一份代码只能完整进入一个模型的推理上下文一次其他模型只接收摘要和差异信息”写进设计约束这会让你省掉很大一部分成本优化的返工。第四如果团队人力很紧优先级顺序应该是“可靠的路由 评估集”大于“细分模型调优”。因为路由和评估集能保证系统的下限稳定而细分模型调优只是锦上添花。我们早期花了不少精力去微调某个专项模型的 Prompt试图让它更符合团队风格但后来发现对代码审查体验影响最大的其实是“该这个模型做的事它做得纯粹而稳定”而不是让它学会处理各种边角场景。代码审查这个事核心从来不是“选一个最强模型”而是“把任务拆到一个模型能稳定输出的颗粒度再让它们各司其职”。多模型不是目的稳定才是。如果你也刚好在搭建类似系统把这几个方向想清楚应该能比我少走不少弯路。