
AI 代码审查喊了很多年“即将取代人工”但真落到自己团队里大家发现它更像一个“很会聊天但不太靠谱的新同事”——说得头头是道细看全是正确的废话偶尔还编出几个不存在的函数名来吓唬你。直到我拆解 open-code-review 这套方案才真正想明白一件事AI 代码审查能不能工程化关键不在模型多强而在用什么架构把它包起来。open-code-review 的核心思路挺朴素把“确定性流水线 LLM Agent”拼成一条混合链路。前半段用传统工具把代码里“一定有问题”的部分筛干净后半段才让大模型去处理那些需要语义理解、上下文推断和业务判断的问题。这篇文章我会完整拆解这个混合架构怎么设计、怎么落地、踩了哪些坑以及最关键的——怎么让 AI 输出的审查结论真正被人接受。适合正在选型、准备自研或想优化现有 AI Review 流程的研发效能负责人和一线工程师。1. 为什么 AI 代码审查需要“确定性流水线 LLM Agent”的混合架构1.1 纯 LLM 做代码审查听起来很美用起来很痛先说说我最早踩的坑。当时兴致勃勃把 PR 链接直接丢给一个通用大模型让它“帮我审查代码”期望它像资深 Reviewer 一样给出精准意见。结果它确实给了——给了 12 条意见其中 9 条是“请确保异常被正确处理”“建议增加日志记录”这类放之四海而皆准的废话还有 2 条提到一个根本不存在的函数依赖图1 条建议重构一个刚在隔壁分支被删掉的模块。这就是纯 LLM 做 Review 的原生问题它没有感知变更上下文的能力没有“这个仓库本来长什么样”的准确记忆更没有稳定判断“这段代码为什么这么写”的业务锚点。你给它看一个 300 行的 diff它只能基于这 300 行做泛化推理一旦 diff 里引用了外部函数、共享类型或历史遗留逻辑它就开始“合理脑补”一脑补就出错。我后来想明白一个类比让纯 LLM 直接做代码审查就像让一个只读过三天项目文档的新人去做审计他能说出“流程需要规范化”这种话但你说不出“第 37 行这个事务边界有问题因为 finally 里吞掉了异常”。前者是态度后者才是审查。1.2 纯规则做代码审查稳定但天花板太低那反过来用传统确定性工具行不行SonarQube、ESLint、Go Vet、PyLint这些都是确定性的给同一份代码永远是同一个输出不会幻觉不会漏判性能还高。但它们的能力边界非常清晰只能做有明确规则定义的检查。复杂语法、类型错误、未使用变量、安全正则遗漏、明显的坏味道这些它们确实干得漂亮。然而代码审查真正有价值的部分恰恰是那些“规则没写但人一眼就能看出问题”的语义级判断。举几个例子这个状态机里少了一条状态迁移路径、这个缓存失效策略可能导致数据不一致、这个接口设计在半年后必然导致调用方误用、这段逻辑放在事务里会锁表这些都不是静态规则能表达的。它们需要理解业务模型、理解调用链、理解代码的隐含契约。我之前维护的静态扫描集群一度跑了 200 多条规则命中率很高但团队 Review 时还是会忽略它因为真正卡住线上事故的问题它一个都没报出来。确定性工具的命门就是它只能回答“有没有违反规则”回答不了“这么写合不合理”。1.3 混合架构的分工哲学让规则的归规则让模型的归模型open-code-review 给了一个很干脆的答案两者都别单干让它们各管一段。确定性流水线在第一层负责三件事先做编译/静态检查/Lint把代码中确定性的错误和坏味道全部拦下来然后对 diff 做精细化处理区分新增行、修改行、删除行、上下文行生成干净的“审查单元”最后把已有问题打成基线让第二层只盯增量。这一层的产出不是“审查结论”而是一份结构化的、经过过滤的“最小审查包”。LLM Agent 在第二层只负责语义级审查。它拿到的是已经清洗过的 diff 数据、关联文件片段、团队规则用它的上下文理解能力去判断“这段逻辑合理吗”“这个异常处理得当吗”“这个命名有没有歧义”。它不需要做编译检查不需要去猜被删掉的行不需要重复报告存量问题所有体力活都被流水线干完了。这套分工的核心原则我总结成一句话凡是确定性工具能查的绝不浪费 LLM 的上下文凡是规则表达不了的判断力才交给模型。这条原则直接决定了后面所有设计也解释了为什么 open-code-review 会比裸调大模型稳定得多。2. 确定性流水线先把“一定错”的问题筛干净2.1 前缀检查编译、静态分析、Lint 层的设计混合架构的第一道关卡是传统的确定性检查。很多人以为这就是简单跑一下 CI其实这里有一个关键设计检查必须被分成“阻断型”和“非阻断型”。编译错误就是典型的阻断型问题代码根本合不进去这种问题交由流水线直接拦截并标记为 failed压根不需要任何 LLM 参与。我之前见过一个团队硬要省事让 LLM 先看一遍再决定要不要跑构建结果模型把编译错误当成“建议修改”整个流程就失去了意义。非阻断型问题比如代码风格偏好、轻微可读性建议也应该由 Lint 规则处理不要扔给模型。ESLint、RuboCop、go vet 这类工具在规则明确时输出是 100% 可复现的成本几乎为零对这类问题的处理能力远强于 LLM。这里最容易犯的错误是什么检查都往提示词里塞最后模型把“分号没加”和“事务边界错误”混在一起报严重程度完全不可信。我建议的检查分层逻辑如下P0 阻断项编译失败、类型错误、测试失败、安全漏洞模式匹配P1 规则项Lint 错误、复杂度超阈值、明显反模式由规则引擎处理P2 语义项架构合理性、逻辑边界、异步处理、业务语义这部分才进入 LLM Agent这套前缀检查的收益是实打实的它把 LLM 从“什么都看”变成“只看语义问题”把关的对象从几千行代码浓缩成几百行增量上下文利用率高了一个数量级。2.2 变更范围隔离为什么必须让 AI 只看增量这是我从 open-code-review 里学到的最有价值的设计之一变更范围隔离。理论很简单——AI 审查的输入不应该是整个 PR 的全量代码而必须是精确的增量 diff 加上必要的上下文。实际执行时可以这样做先跑git diff --unified3拿到带三行上下文的变更在此基础上用脚本解析出本次 commit 新增、修改、删除的代码块。然后做一个“文件影响面分析”找出被修改函数的所有调用点把调用点的代码片段也一并提取出来。这就是所谓的“审查证据包”。举个具体例子团队几乎每周都会遇到这种情况开发者在工具函数里改了一个布尔判断逻辑调用的地方有七个其中四个调用点都会受影响但 diff 里只能看到这个函数的两行变化。人来审查时都容易漏掉更别提站在一个孤立 diff 上推理的 LLM。通过影响面分析把四个调用点一并塞进上下文LLM 才能给出“这个改法会导致 XX 模块的权限校验失效”这种级别的判断。这个阶段输出的审查包建议用结构化的 JSON 或 YAML 保存包含文件路径、改动行号、旧代码/新代码对、关联函数的调用点、依赖关系、变更类型。这是整个确定性流水线的核心产出LLM 不直接读 git diff它读的是这份已经整理好的“案件卷宗”。2.3 已有问题过滤怎么把噪音压下去工作中一个真实的挫败场景是模型第一次跑就报出 200 多个问题开发一脸懵——这代码又不是今天写的为什么突然全是问题看细节才发现绝大多数是仓库里沉淀了半年甚至一年的存量告警并不是这个 PR 引入的。要解决这个问题就必须引入“基线化”思路。在把 LLM 接进来之前先对主干分支做一次全量扫描把当前所有问题打成一个基线清单保存起来。之后每次 PR 审查确定性流水线只输出“新增问题”和“与基线相关的变化”凡是基线里已经有记录的问题直接过滤掉不让它出现在 LLM 的输入里。实际操作中可以用一个简单的对比表来维护来源处理方式是否进入 LLM存量问题基线已有直接忽略不展示否本次新增的确定性错误阻断管线或标记 P0否本次新增的规则坏味道进入 LLM 作为参考可选语义逻辑存疑规则无法表达进入 LLM 重点审查是这套过滤机制可以把初始噪音直接砍掉 70% 以上。我第一次跑通这套流程时审查报告瞬间从“一份没人愿意读的问题清单”变成了“几条有分量的异议”Reviewer 的重视程度完全不一样。2.4 确定性流水线的输出格式喂给 LLM 的“结构化压缩包”确定性流水线最后一步是把审查包格式化。这一步很多人忽略但其实直接影响 LLM 的推理质量。不要直接把原始 diff 丢给模型一定要做结构化压缩——保留关键行号、代码片段、变量符号、依赖引用同时去掉无关的空行、缩进噪声、被重命名但逻辑不变的纯噪音改动。我常用的审查包结构类似这样{ pull_request: { id: PR-1243, title: fix: 调整订单超时状态的迁移逻辑, base_branch: main, changed_files: 3 }, files: [ { path: src/order/state_machine.go, change_type: modified, changed_lines: [ { line_number: 87, old_code: if oldState TimedOut { return false }, new_code: if oldState TimedOut { return true } } ], related_symbols: [OrderStateMachine, TransitionTo], call_sites: [ { file: src/api/order_handler.go, line_number: 142, code: if !sm.TransitionTo(target) { reject() } } ] } ], baseline_filtered: true, linkage: file_imports_diagram }这个 JSON 再配合一段简短的仓库背景描述语言栈、框架版本、目录结构组成 LLM 的完整上下文。核心原则是给模型的信息必须是最小充分的——再多一点就会分散注意力再少一点它就会开始编。这也是为什么 open-code-review 的设计里LLM 不是纯提示词工程而是数据工程的一部分。3. LLM Agent 层把代码当上下文把规则当约束3.1 Agent 的完整工作流召回、组装、推理、结构化输出第二层的 LLM Agent设计上不应该是一个“聊天式问答机器人”而应该是一个“按流程执行任务的专业审计员”。open-code-review 里的 Agent 工作流我拆成五步。第一步是召回基于确定性流水线产出的审查包从代码库中精准召回关联文件、函数定义、调用链、历史审查记录和团队规则。第二步是组装把召回的上下文按照规定模板拼装成结构化提示词减少原始 diff 里无效 token 的占比。第三步是推理让模型按关注面逐项审查每项审查只针对一个明确的语义问题不做开放式总结。第四步是结构化输出所有审查结论必须按固定 schema 生成 JSON不允许自由发挥。第五步是回写通过 API 把审查结果提交到代码托管平台的 PR 评论线程或审查记录中。这个流程里最容易被忽略的是“召回”这一环。直接给模型一整包文件它也能出结果但质量不稳定。真正的做法是做针对性召回要审查的文件引用了哪些模块这些模块的公共接口长什么样这个文件在历史上有过哪些相关审查记录这些才是模型需要的上下文。召回质量决定了模型是“基于理解做判断”还是“基于猜测试探性输出”。Agent 工作流五步设计召回提取关联文件、符号定义、调用链、规则库条目组装将上下文压缩为“审查包 规则 任务指令”三件套推理按关注面逐项审查每个关注面单独推理结构化输出按 schema 输出审查项禁止自由文本回写通过平台 API 写入 review comment3.2 上下文组装的艺术文件内容、Diff、关联文件的取舍上下文怎么组装是这套架构里最考验功力的一环。窗口有限代码无限必须做取舍。我总结的优先级排序是PR 描述和任务上下文 规则库 变更 diff带上下文 关联文件片段 历史审查示例 完整文件源代码。PR 描述为什么排在第一位因为它给了模型业务意图——知道这次改动的目的是什么模型才能判断“这么做是否合理”。否则它只能做文法级审查看不出逻辑问题。规则库放在第二位是因为团队规范是语义审查的标尺。模型没有量尺就没法测量。diff 排在中间但这里有一个重要细节大 diff 必须拆。单文件超过 400 行变更、单 PR 超过 1000 行变更都必须按函数边界拆成多个子任务。宁可发多次请求也不要把 5000 行代码硬塞进一个窗口模型一旦超出 token 预算表现是断崖式下跌的——前面的记住了后面的全瞎了。关联文件的取舍标准是“是否被本次变更直接影响”。间接影响的文件先不放。比如改了一个公共方法的签名就把所有调用方放进去如果只是加了一个注释那就什么都不用放。不要为了“以防万一”把所有相关文件都塞进上下文“以防万一”的本质是没想清楚审查关注面。3.3 规则注入把团队规范写进 Prompt团队规范是 LLM 审查的标尺但直接把规范文档丢给模型是无效的。我实测下来的做法是先从团队手册、代码规范、历史 review 记录里提炼出“机器可执行的审查规则”每条规则必须满足三个条件描述清晰、可验证、冲突时能给出改法。一个典型的规则条目长这样- id: T-1024 category: concurrency severity: warning description: 在共享可变状态上进行并发写入时必须显式加锁或使用原子操作 verification: 检查本次变更中是否有对多个 goroutine 可见的 map / slice / struct 字段的直接写入 且写入语句没有包裹在 sync.Mutex / atomic 等同步原语中。 suggestion: 为共享状态引入 sync.Mutex或在写入路径使用 atomic 操作 如果该状态在单个 goroutine 内生命周期内建议在代码注释中说明线程封闭。规则注入的量要克制。我实测超过 20 条关键规则之后模型的“准确性”反而下降因为它开始权衡“哪条规则更值得遵守”权衡过程就会产生幻觉。建议规则库按优先级分批注入每次审查只注入与该变更最相关的 5~10 条规则其余放远在低优先级上下文中。从技术实现上规则的存储格式用 Markdown 或 YAML 都可以但一定要在运行时转成结构化对象再插入 Prompt而不是直接把整个规则文件塞进去。LLM 是文本模型你给它的文本越结构化它的输出越稳定。3.4 从 Chat 模式到 Review 模式结构化的输出设计绝大多数 AI 代码审查工具翻车不是模型不够强而是输出的形态让人没法用。聊天模式下模型会输出“我觉得这里可能有问题建议考虑一下并发场景”这种话在人看来就是正确的废话没有任何行动价值。open-code-review 给了一个非常好的示范强制模型按 Review schema 输出。每条审查结论必须包含严重级别、精确位置、引用代码片段、问题描述、修改建议、相关规则 ID。没有引用具体代码片段的问题直接视为无效输出。我定义了一套分级体系供参考分四档Blocker必须阻止合入。例如内存泄漏、并发竞态、安全漏洞、核心逻辑逻辑反转Warning建议修改。例如错误处理缺失、潜在的边界条件、不合理的资源释放Nitpick风格偏好。不影响功能的微调建议但如果和规则库冲突则升级为 warningQuestion不是结论是提问。例如“这里状态迁移路径是不是少了一条”留给开发者回答这种结构化输出的好处显而易见Reviewer 可以按级别筛选处理也可以快速定位到具体行。更关键的是它能被后续的“人工确认/驳回”流程自动处理——人工点一下“确认”这条建议就进入案例库成为以后模型审查的参考资料。这就是人机闭环的地基。4. 工程化落地把架构接到真实研发流程里4.1 CI/CD 接入从 PR 触发到结果回写架构设计得再漂亮接不进 CI 就是白搭。open-code-review 在工程化落地上最突出的地方就是它没有做一个“命令行工具”而是做了一套可以嵌入现有研发工作流的服务化组件。以 GitLab CI 为例可以在 merge request 事件上触发一个 review jobjob 内部跑四个阶段checkout 拉取代码、run deterministic pipeline 生成审查包、call LLM Agent 执行审查、writeback 调用 GitLab API 创建 review comment。GitHub Action 类似只是用 pull_request 事件和 issues API。这里有一个关键细节LLM 调用不稳定超时和失败是常态。所以要选择异步模式不要阻塞 PR。我的做法是触发 job 后机器人先在 PR 里回复“AI 审查已开始预计 2~3 分钟完成”审查完成后通过更新评论的方式覆盖这条消息。跑挂了也无所谓留一句“审查服务异常请人工 review”总不能因为 AI 服务抖动把整个 CI 卡死。异常兜底机制必须有定时器、重试逻辑、熔断开关。LLM 连续失败三次就自动熔断后续审查请求直接走“跳过 AI 审查、只跑确定性流水线”的降级路径。我见过太多团队死在“AI 服务不稳定导致 CI 天天红”这个坎上把稳定性问题列为第一优先级而不是先堆功能。4.2 人机协同节奏AI 建议、人工决策、事后对话工程化的第二层含义是AI 的输出要能够被人的反馈持续改善。理想的工作流是这样的AI 在 PR 评论区发布审查结果每条结果右侧带两个按钮“确认”“驳回”。Reviewer 对每条建议做出反馈。确认的建议进入“优秀例句库”驳回的建议进入“屏蔽词库/规则抑制列表”。每周汇总一次数据把高频确认的条目反向写入规则库把高频驳回的模式提取出来加进 Prompt 屏蔽词。这里要特别注意“驳回”的处理。模型报出一个问题人觉得不对点了驳回。这个信号是金矿——它说明模型的判断依据有问题要么是上下文缺失要么是规则注入有歧义要么是模型本身幻觉。把驳回样本汇总起来分析比调提示词更有效。说白了这套系统里人的反馈才是真正的“监督信号”模型只是推理引擎。我建议团队先跑两周灰度AI 审查结果只对资深 Reviewer 可见且不参与合入门禁。两周后看数据——确认率、驳回率、漏报率、误报率——再决定要不要开放给全员并且接入门禁。不要一上来就搞“AI 审查不通过就不能合入”那会把整个流程的口碑毁掉。4.3 性能成本评估几个关键数字和取舍聊工程化不能回避成本和性能。先说延迟纯确定性流水线一般在 10~60 秒内能出结果LLM 审查部分单条小 diff 走本地小模型大概 20~40 秒走商业化大模型 API 大概 5~15 秒。整条流水线的目标应该控制在 5 分钟以内超过这个数开发者就开始烦躁了。成本方面核心变量是 token 消耗。一个常规中型项目的测算逻辑我列一下假设每周 30 个 PR每个 PR 平均 200 行变更审查包大约 8000 token加上规则和回写单次审查的输入输出大概 12000 token。一个月就是 12000 × 30 × 4 144 万 token。如果按商业化 API 单 token 价格算业务机型一个月几百块到一千多人民币其实不算贵但有一个陷阱——如果 review 包设计不合理把无用的全文件上下文塞进去成本会翻 3 到 5 倍而且效果反而更差。想降成本有四个办法启用语义缓存相同文件片段通过哈希判断在上次审查后直接复用小文件合并审查大文件按关注面分片拒绝“全量一次过”优先部署本地开源模型7B~14B 参数量的模型配量化后审查质量已经够用只审查“高风险变更”用改动文件的复杂度、依赖数、历史 bug 数等特征做路由低风险 PR 直接走纯确定性检查4.4 开源方案选型与二次开发建议如果团队想快速起步open-code-review 这套设计思路可以直接作为底座。但很少有团队能直接拿过来就用需要自己二次适配的部分我按优先级排一下首先对接自己的代码托管平台权限体系这不是功能问题是安全底线其次适配自己的语言栈和构建体系不同语言的 AST 差异巨大确定性流水线的 parser 必须换成本地工具链最后建立自己的规则库把团队的代码规范、领域最佳实践、历史事故教训提炼成审查准则。这里要特别提醒一个多语言架构的坑如果你团队是 Java Go Python 混着用确定性流水线的适配成本会陡然上升。每种语言的静态分析工具链完全不同、误报率不同、规则体系不同。有人会说“那让 LLM 看就好了”错。语言适配没做好LLM 会被迫承担原本属于规则层的检查任务上下文被占用、误报率飙升、审查质量断崖下降。我的经验是先将 OK 的语言栈跑通再逐步扩展不要一开始就铺到全语言。另外一定要关注“审查结果的审计日志”。所有 AI 输出、人工确认、驳回、规则命中记录都应该是可追溯的。这不是为了甩锅而是为了让系统持续改良有数据喂养。没有审计数据的 AI Review就是一次性玩具。5. 常见问题与排查技巧实录5.1 误报太多Reviewer 不想看怎么办这是落地时最致命的问题。一次 PR 审查报了 40 条建议其中 35 条是误报或低价值提示开发看完之后直接对 AI 审查失去信任后续全部忽略。排查方法分两步。第一步看确定性层报出来的 40 条里有多少是存量基线没滤干净有多少是规则配置错误有多少是 diff 解析脚本把“格式化变更”当成了“逻辑变更”这一步修好通常能砍掉一半。第二步看 LLM 层报出来的语义问题是不是没有引用具体代码片段是不是问题描述模糊不清给模型加硬约束——没有 code_snippet 字段的建议一律丢弃大于 10 条的审查结果自动截断并且只保留优先级最高的 10 条。我实测经验是把误报率从 40% 压到 15% 左右核心手段不是换模型而是“给模型一个最小证据约束”强制它每条建议都必须带行号和代码引用。模型在没把握时会倾向于少报而不是胡编。5.2 长 Diff 被截断审查覆盖不全单文件变更超过 800 行或者单 PR 涉及 10 个以上文件时模型经常出现“前面写得很详细后面零输出”的情况因为窗口不够了。解决思路是分片 分层。分层是指先做一个全局 summary 任务让模型快速浏览整个 PR 的结构输出一份“本次变更涉及哪些模块、主要风险在哪里”的概览再基于概览做分片深度审查按关注面存储层、并发层、安全层、可读性切分每片单独提交上下文。全局 summary 决定深度审查的优先级分片审查决定具体问题的质量。分片粒度不要按文件机械切而要按函数边界和关注面切。把“订单状态机的迁移逻辑”作为一个审查单元而不是“order 目录下三个文件的分别审查”。模型在理解一个完整逻辑单元时表现远好于拼接碎片。5.3 模型上下文复用与 Token 消耗失控有些团队跑了一周发现 AI 审查每天的 token 消耗高得惊人一查全是把全量文件塞进上下文惹的祸。排查方向很明确审查包日志里看单次审查的输入 token 数如果经常大于“diff 实际内容”的 10 倍说明召回阶段贪多了。缓解机制我用过比较有效的是加一个 token 预算单次审查输入上限比如 8K超出后按优先级丢弃最不相关的上下文增加语义缓存对未变化的文件内容做哈希缓存重复出现的片段不重复计价把文件按“变更密度”排队先审查变更最密、影响最大的文件变更只有一两个字符的文件一律跳过。5.4 数据安全边界与敏感信息处理代码是公司最核心的数据资产这一点在接入 AI 审查时必须摆在最高优先级。我的建议很简单代码优先进入私有化部署的模型环境不要送到公有云 API如果必须使用公有云 API一定要先做敏感信息脱敏——把代码注释和字符串中的密钥、token、IP 地址、员工姓名替换成占位符再送分析API 网关层记录审计日志时不要把完整代码片段入库。在代码审查这个场景下宁可模型笨一点也不要让数据出域。很多团队调研时看到商业 API 效果好马上接上去忘了代码托管平台本身有权限体系而模型服务是绕开这一层的外部系统。让“代码不进公网”成为第一铁律在私有模型质量不够时再考虑混合路由方案。提示所有涉及模型调用和代码传输的链路都应该同步做权限穿透校验确保调用方身份与代码库权限一致而不是“只要拿到 token 就能看任何仓库”。6. 关于标准的规则把架构落地成可复盘的配置清单最后分享一份我在实际项目中总结的配置清单它不是 open-code-review 的标准答案但照着这个框架搭基本能把前面所有经验固化下来方便团队复制。配置项建议值/方案备注确定性检查分层P0 阻断 / P1 规则 / P2 语义按语言栈选型增量隔离粒度函数级变更 调用点提取用git diff -U3基础上二次分析基线过滤主干分支全量扫描每周只重扫一次审查包格式JSON / YAML 结构体必含 line numbers 和关联符号LLM 审查关注面并发/错误处理/资源/安全/可测性单次任务限一个关注面输出 schemablocker/warning/nitpick/question每条必须带 code snippet提示词结构PR 描述 规则库 结构化 diff规则数 ≤ 10 条CI 接入模式异步模式 熔断降级LLM 失败自动降级跳过人工反馈确认/驳回按钮 对账任务每周汇总一次数据安全本地模型优先 脱敏 审计日志全链路权限穿透成本控制语义缓存 token 预算 分片按 token 量设月度上限这套架构里真正难的不是任何一个单点技术而是把“确定性工具、模型推理、人工反馈、数据审计”串成一个闭环。我自己的体会是AI 代码审查进入工程化时代标志不是“模型能看懂代码”而是“模型被嵌进了一条可信的、可追溯的、能持续学习的生产链路”。最后再分享一个小技巧——如果你刚起步别急着让 AI 全面审查。先让它只做两件事标记“可能是 bug”的代码位置和提出“它不确定但值得人确认”的问题。这两件事干好了团队对 AI 审查的信任就有了信任有了再慢慢扩展它的审查范围。其他所有花哨能力都可以往后放。