
先说个我最近的感受我在多个仓库里试过纯靠大模型直接读PR评论代码结论很一致——AI代码审查的工具很多但能放进CI里稳定跑的没几个。丢给LLM一个diff让它看看有没有问题输出往往飘忽不定有时候能揪出真bug有时候对着格式问题长篇大论还有时候干脆幻觉出一个根本不存在的漏洞。真正让我觉得这玩意能用了的转折是我接触到open-code-review这个项目之后才发生的。它的核心思路不是让LLM自由发挥而是把整个审查过程拆成两层确定性流水线负责收集、过滤、分类、去重LLM Agent只在一个被严格约束的范围内做语义判断。这篇文章我就基于open-code-review的实际拆解把这种确定性流水线 LLM Agent的混合架构讲清楚包括每一步为什么这么设计、参数怎么定、哪些坑我替你先踩了。这个架构解决的核心问题有三个漏报该查的没查、误报无关问题刷屏、成本失控每个PR烧掉大量token。如果你正在搭建团队的AI代码审查能力或者单纯想理解Agent系统怎么和传统工具链配合这篇内容应该能给你一套可以直接落地的参考框架。1. 为什么纯LLM审查走不远三个绕不开的坎先说结论纯LLM做代码审查不是效果差而是不可控。工程化最忌讳的就是不可控。1.1 上下文窗口的物理限制一个中型PR的diff通常在几百行到上千行但如果牵扯到跨文件改动你需要的上下文可能包括改动文件本身、依赖这些文件的调用方、相关类型定义、历史变更记录、项目规范文档。把这些全部塞进上下文窗口token消耗会迅速膨胀到几十万级别。成本还只是其一更麻烦的是LLM在长上下文里的注意力会衰减——实测下来超过一定长度后模型对前面文件内容的记忆明显变弱导致审查质量断崖式下跌。这不只是open-code-review遇到的问题是所有LLM Agen类工具的共同瓶颈。解决的思路不是无限加大上下文而是把需要大模型看的和不需要大模型看的分开。1.2 幻觉与误报的代价比漏报更高很多人在意LLM会不会漏掉bug但真正用过以后你会发现幻觉hallucination比漏报更折磨人。一个根本不存在的空指针风险LLM能给你编出一整套调用链来佐证。reviewer在PR里看到这种评论第一反应是这工具又在胡说八道第二次就不再看了——信任崩塌之后工具价值归零。从工程化角度看误报的成本是信任损耗信任损耗的累积速度远快于漏报的修复速度。所以架构设计的首要目标不是查得多而是查得准。1.3 规则一致性问题代码审查里有很多确定性规则禁止直接使用console.log提交、新代码必须包含测试、禁止在循环里创建对象、import顺序必须符合规范。这类规则用LLM去执行每次结果都可能不同——同一个PR上午审和下午审结论不一样这在工程流程里没法接受。规则类的检查应该由确定性工具执行只有需要语义理解的部分才交给LLM。这是open-code-review架构的第一原则。2. 混合架构的整体设计把确定性留在流水线把智能留给Agentopen-code-review的架构可以概括成一句话流水线负责有没有问题Agent负责是不是问题。这句话听起来简单但拆分清楚了整个系统的稳定性、成本、可维护性都会有本质改善。2.1 架构分层整个系统分为四层层级职责技术构成确定性采集层获取MR信息、diff、元数据Git API、文件系统100%加工层提取函数/类/import/调用关系AST解析、静态分析100%过滤层规则引擎、格式检查、死代码识别Regex、AST规则、lint规则100%决策层语义理解、逻辑推理、优先级判断LLM Agent部分前两层是纯粹的确定性流水线第三层是确定性优先、LLM兜底的混合过滤第四层才是Agent发挥的空间。这个分层的核心逻辑是越底层越要确定越顶层越要智能。如果你的静态分析器漏了一个格式问题你可以在规则里加上去行为是可预期的但如果你的LLM漏了一个格式问题你没法通过简单的规则修复——它每次的行为都是概率性的。2.2 流水线为什么不能只靠LLM实现有一个试验过的方案不做任何前置处理直接把整个PR的diff喂给LLM让它输出JSON格式的审查意见。结果很快暴露了两个问题一是JSON输出不稳定。模型偶尔会在JSON前后加markdown标记偶尔会在JSON里写注释偶尔直接输出纯文本。你不得不花大量精力做解析容错。二是审查颗粒度不可控。模型有时候一条意见写800字有时候列十个问题每个就一句话reviewer没法统一处理。把diff预处理成结构化数据后LLM的输入就变成了文件A的函数X调用了文件B的函数Y函数Y的第三个参数可能为空请判断是否存在空指针风险这些精确的问题。模型只需要做判断不需要做发现任务的复杂度大幅下降输出质量稳定很多。2.3 Agent在这里不是自主行动体而是受限判断器现在很多Agent框架强调自主规划、自主执行在代码审查场景里这其实是个误区。你不需要Agent自己去翻代码、自己决定看哪里——该看哪里流水线已经知道了。Agent需要做的只有两件事判断某个潜在问题是否真实存在判断这个问题的严重性级别。这两个判断恰恰是确定性工具做不了的。比如这个变量命名不符合语义AST工具无法判断什么叫符合语义比如这个函数在并发场景下可能出问题静态分析能告诉你这里有共享变量但无法判断实际风险。这些就是Agent存在的价值。Agent的价值不在于自主而在于在正确的地方做判断这是混合架构与纯Agent方案的本质差异。3. 确定性流水线的核心拆解代码地图的构建open-code-review的流水线第一阶段是构建代码地图——把diff变成一份结构化的、可以被后续处理的数据。3.1 diff的精确提取与解析第一步是拿到PR的完整diff。注意不是直接用GitHub API返回的原始diff字符串而是要解析成结构化信息新增了哪些文件删除了哪些文件每个文件新增/删除的具体行号每个改动所属的函数或类关联的测试文件如果修改了src/foo.py对应测试在tests/test_foo.py这个阶段我用下来觉得最麻烦的是获取准确的改动行号。GitHub的diff格式里有块头部包含起始行号和块长度但不同平台的diff格式略有差异GitLab和GitHub返回的数据结构不一样。open-code-review在适配层做了统一处理如果你自己实现建议直接封装一个DiffParser类屏蔽平台差异。# 伪代码示意实际实现需处理更多边界情况 class DiffParser: def parse(self, unified_diff: str) - list[FileChange]: files [] current_file None for line in unified_diff.splitlines(): if line.startswith( ): current_file FileChange(pathline[4:]) files.append(current_file) elif line.startswith( ): current_file.hunks.append(self.parse_hunk(line)) return files3.2 AST解析找到函数与调用关系拿到diff之后需要对改动文件做AST解析目的是回答几个问题这个改动在哪个函数里这个函数被谁调用这个改动涉及哪些变量这里有个性能优化技巧不要对整个仓库做全量AST解析只解析diff涉及的文件然后通过import关系拉入被依赖的文件。大多数PR只涉及几个文件全量解析在大型monorepo里会慢到不可接受。AST解析结果包括改动函数列表名称、参数、返回类型函数间的调用关系图新增/删除的全局变量、类属性import变更情况这些信息会在最后组装给Agent的时候作为基础证据。我自己的经验是让Agent自己读代码分析调用关系不如直接给它调用关系图——一是省token二是避免了Agent幻觉出不存在的调用链。3.3 静态规则的确定性检查流水线里要跑一组静态检查规则这些规则的特点是可以被精确判定不需要任何语义理解。例如新增代码是否包含TODO、FIXME、console.log是否引入了subprocess但缺少白名单校验新文件是否缺少对应的测试文件是否有未使用的import是否有硬编码的密钥正则匹配API key模式方法长度/复杂度是否超过阈值这些规则的执行结果有三个去向直接拦截返回给MR评论/CI失败、作为低优先级提示进入信息收集区、作为后续Agent的输入候选。比如新增代码里出现了console.log就不要让Agent来判断了直接report确定性规则能做到100%准确。注意规则引擎的配置建议做成可声明式的YAML或JSON不要写死在代码里。团队之间的代码规范差异很大开放规则给团队自己调整工具的可接受度会高很多。4. LLM Agent的智能决策层设计让模型只做判断题确定性流水线把代码地图构建好之后就轮到Agent出场了。但我强调过你不是把整个diff丢给LLM让它自由发挥而是把证据组织成精确的问题交给LLM去做判断题。4.1 两阶段Agent设计三明治结构open-code-review的Agent分层设计很有参考价值我称之为三明治结构第一层Precheck Agent功能前置判断在每个文件/每个函数级别先让Agent做一轮粗筛判断这个改动潜在风险高不高。输出结果只有三个选项高风险、中风险、低风险。高风险的进入深度检查队列中风险的进入标准检查队列低风险的只做记录。这一步的作用是大幅减少深度检查的次数降低token消耗。实测下来一个PR里约60%的改动是低风险的格式调整、变量重命名、注释修改这些完全没有必要做深度语义分析。第二层Deep Review Agent核心审查针对高风险和中风险的改动做深度审查。输入信息包括改动的完整代码段相关函数调用链涉及的数据流路径相关的测试用例定义最近的类似变更记录如果存过历史要求Agent输出的维度有逻辑正确性、潜在异常分支、并发与状态风险、安全风险、性能隐患、可维护性。每个维度输出问题描述、相关代码位置、严重级别、修复建议。输出格式用JSON且用JSON Schema做一次结构校验不通过的重新生成。第三层Suggestion Synthesizer建议聚合把多个Agent的结果汇总做冲突检测和去重。比如两个Agent都发现同一个函数有空指针风险就合并成一条避免重复评论。这个分层设计的好处是每一个层都不需要做太多事但合起来覆盖了审查的完整链路。单一Agent塞入所有职责Prompt会变得臃肿且行为不可控。4.2 Prompt设计的正确姿势有一件重要的事Prompt里不要写你是一个资深代码审查专家直接描述任务结构反而效果更好。我试过很多种方式效果最好的Prompt模板结构是明确输入格式说明你会收到JSON里面包含哪些字段明确输出格式要求JSON Schema明确判断标准列出关键检查点给出1-2个正例和反例强调不要编造不存在的调用关系只基于提供的信息判断有一个细节影响很大给Agent输入时要明确区分事实和推断。调用关系图是事实测试覆盖情况是事实但这个改动可能导致性能下降是推断。Agent只有在事实基础上做推断才不会产生无根据的结论。具体做法是在输入数据结构里加一个source_type字段标注fact或inferred。4.3 温度与采样参数的设置聊到参数设置很多同学直接默认temperature0.7。但代码审查场景里temperature要尽量低我建议0.1-0.2top_p设置在0.9左右。过高的随机性会让输出在同样的事实下给出不同结论这在工程流程里是致命的。低温让模型保守但代码审查本来就是保守的任务——拿不准的宁可说有风险也不能为了讨好而沉默。不过有个反直觉的点温度太低也会导致漏报。如果你发现Agent对某些类型的问题总是沉默比如对性能问题完全不做评论可以针对该类型单独提高温度到0.3左右最大程度覆盖不同检查维度。这个小技巧我在实践里试过有用。5. 流水线与Agent的协同机制数据流与状态管理架构拆完之后下一步要解决协同问题流水线产生的数据怎么交给AgentAgent的结论怎么回流到流水线5.1 统一数据结构Issue Reportopen-code-review定义了一个统一的数据结构——IssueReport它贯穿整个链路{ issue_id: f3a9c2e1, file: src/auth/login.py, line_start: 42, line_end: 58, category: security, severity: high, title: Timing attack risk in password comparison, description: Usage of built-in string compare may lead to timing-based enumeration, confidence: 0.92, source_type: llm_agent, suggestion: Use hmac.compare_digest or similar constant-time comparison }这个结构与确定性检查工具的输出保持同构这样不管是规则引擎产生的还是Agent产生的最终都能统一进入同一个报告管道。我认为这是决定混合架构是否优雅的关键细节——如果你用两套完全不同的数据结构去承接两种来源的审查结果合并和去重会非常痛苦。5.2 协同流程图的核心节点虽然不能用mermaid画图我直接用文字描述协同流程Diff获取阶段通过Git API拉取PR信息生成统一diff代码地图构建AST解析生成函数图、调用图静态规则检查确定性规则跑完结果直接进报告Agent任务分发根据代码地图静态规则结果筛选出需要Agent深度检查的文件/函数Agent执行对每个文件/函数执行Precheck再决定是否进行Deep Review结果回流Agent输出转换统一格式进入Issues列表去重与优先级排序合并重复问题按严重度和文件热度排序生成最终审查建议评论/报告生成发布到MR/PR评论区或发送到Webhook5.3 增量审查的状态缓存这个细节很容易被忽视但工程上影响很大同一个PR反复推送新commit时你的审查系统不能每次都全量重跑。open-code-review的解决方式是维护一个审查状态缓存每个文件/函数有一个hash基于文件内容和所属commit计算如果某个文件的hash没变直接沿用上一次的审查结论只有hash变化的文件才会重新执行Agent检查这套缓存机制我实测下来能把第二轮之后的审查成本降到原来的20%~30%。很多MR会有4、5轮迭代如果每一轮都全量跑Agent成本会失控。6. 工程化落地性能、成本与CI集成的坑架构设计得再漂亮最终都要在CI里跑起来才算数。落地阶段暴露出来的实际问题比架构阶段多得多。6.1 延迟预算与并发策略一个PR的审查如果超过5~8分钟开发者的体验已经很不舒服了。全量Agent审查在代码量大时极容易超时。我的经验是给Agent层设置严格的延迟预算超时直接降级为静态检查结果。具体策略流水线层diff获取、AST解析、静态规则必须在30秒内完成Agent Precheck层控制在45秒内Deep Review层控制在3分钟内整个审查全流程控制在5分钟内为了满足这个预算并发是必须的。open-code-review对多个文件的Deep Review是并行执行的每个文件一个独立任务并发数可以通过配置调整。我建议初始并发设置在3~5太高会撞上模型API的rate limit。另外要设置API调用的超时时间。市面上大多数模型API不设超时的情况下可能挂起几分钟无响应。设到60秒比较安全超时后标记该文件为审查失败并通知重试而不是卡住整个流水线。6.2 成本控制token是怎么烧掉的一个容易被低估的问题是token消耗。用CLAUDE级别的模型跑一次全量审查一个中型PR可能要烧掉相当于几万token的输入输出。一个月跑几百个PR成本确实很可观。省token的实操方法Diff压缩策略不要把所有变更代码块原样喂给Agent先用AST提取函数签名、关键变量、控制流骨架用这些结构化信息代替完整代码。减少代码上下文冗余。只查Changed Code只把diff涉及的函数代码作为输入不把整个文件传给模型。设置输入上限单个任务的输入超过一定长度比如2万字符时优先截断或分块分块之间采用独立判断不互相引用。使用便宜模型做第一层筛选Precheck层用轻量模型比如更小的参数量或低价的推理模型只有深度检查才调用强模型。第一层负责发现问题难度低但数量大的任务第二层负责精确判断的任务。结果缓存复用同仓库同函数的审查结果按版本缓存只对变更内容重新审查。这些策略叠加在一起实际单MR审查成本可以控制在很小的范围内比不用缓存直接全量审查省60%以上。6.3 CI集成不要成为MR的阻塞者AI代码审查在CI里的角色定位要想清楚它应该是辅助而非门禁。一开始我们尝试把Agent的高危问题设置为CI失败条件结果开发体验非常差——Agent偶尔的误判直接阻塞了合入团队怨声载道。后来改成把结果作为机器评论写入MR仅警告不强制大家反而更愿意看。从定位上来说确定性规则的P0/P1问题可以设为CI失败条件比如密钥泄露、危险函数调用Agent的审查结论只作为评论/建议存在不阻塞合入提供忽略此问题的按钮开发者可以标注误报这些标注可以反馈到系统里做后续的规则校准一旦开发者产生这东西不懂装懂还卡我不能合代码的抵抗情绪工具的寿命就到头了。宁可让它安静地给建议不要让它大声地Say No。6.4 部署形态独立服务还是挂载在CI里open-code-review的部署可以做成独立的HTTP服务由CI脚本调用API触发也可以作为GitHub Action/GitLab CI插件直接运行。两种方式各有利弊独立服务的好处是状态缓存、历史记录、配置管理都可以集中维护多个仓库共用一套基础设施缺点是运维成本高一些需要自己托管和监控。直接挂载在CI里的好处是部署简单跟CI生命周期绑定不需要额外服务缺点是每次运行都是无状态的除非配置外部存储历史数据不好沉淀。我的建议是早期直接用CI挂载方式跑起来跑出效果后再抽象成独立服务。先验证业务价值再优化架构形态这样风险最低。7. 常见问题与排查技巧实录最后记录一些实际踩过的坑有些问题我在配置open-code-review的过程中花了不少时间才定位到原因直接整理出来供参考。7.1 Agent输出不稳定JSON解析失败这是高频问题。有些模型即使Prompt里明确写了只输出JSON也会在前后加说明文字。我的解法是用正则先把{到最后一个}之间的内容提取出来再解析如果解析失败整条结果标记为格式错误并请求重试一次重试仍失败则降级为只输出静态规则结果。不要为了强行解析而写复杂的容错逻辑超过两次失败直接放弃比花一堆时间去解析更划算。同样的道理我在并行审查里设了最大重试次数限制避免单个任务的坏请求阻塞整个队列。7.2 误报率失控Agent在编造调用链之前遇到过比较头痛的问题Agent会虚构出实际上根本不存在的函数依赖关系然后用这个虚构的依赖来佐证自己的判断。定位后发现根因在于Prompt里给了Agent过宽的权限——它自己读了代码自己提取了调用关系然后基于自己有误的提取结果做判断。修正方案就是回到我们的架构调用关系由流水线的AST解析器生成Agent只基于list中的事实推断绝不能自己额外读代码。设置好之后这类幻觉明显减少。如果你发现Agent总是在分析一个在diff里根本不存在的函数先检查你给它的输入数据里是不是包含了无关字段或者Prompt里是不是模棱两可。7.3 严重级别判断标准不统一最初我们把严重级别完全交给Agent定结果一个PR里所有问题都是Medium或High级别体系失去意义。解决方案是定义明确的分级标准并写进Prompt。Critical可能直接导致生产事故、数据丢失、安全问题High明确的功能缺陷、异常处理缺失Medium潜在的边界条件问题、代码结构不佳、缺少错误处理Low风格类、命名类、注释类同时流水线会根据改动文件的线上流量权重做修正核心服务的Medium可以升级为High边缘模块的High也可以降级为Medium。这套修正逻辑放在确定性流水线里做不依赖Agent保证一致性。7.4 一个PR里Review Comment太多曾经有位同事的新手PR被AI审查刷了80多条评论大部分是风格问题和低优先级建议。开发者看到就崩溃了一条都没看完。后来我们采用这样几个策略降低噪音同类问题只报一次比如10个文件都有命名问题只在第一个出现的位置报一次然后说明其他位置类似。增量式反馈新commit只对新产生的diff做评论历史已评论过的问题不再重复。按reviewer的偏好过滤手动设置这个仓库只关注安全和正确性问题风格类问题直接不进评论。反馈质量比反馈数量重要得多。也是经验之谈。7.5 缓存击穿同一份内容重复审查当两个分析任务并发处理同一个文件时可能会出现缓存击穿——两个任务同时发现缓存未命中同时执行了Agent调用。解决方法是给状态缓存加一个简单的锁机制# 简化示例 cache_lock {} def get_or_review(file_hash: str, review_func: callable) - ReviewResult: if file_hash in cache: return cache[file_hash] with cache_lock.setdefault(file_hash, threading.Lock()): result review_func() cache[file_hash] result return result多进程部署的环境建议用Redis做分布式锁单进程环境一个dict就够了。这个细节不算复杂但忽略它会导致同一文件同一commit被重复审查多次token浪费很浪费排查起来又很隐晦。8. 已经是尾声的时候说几句大实话聊了这么多架构和机制最后说点主观的体会。我在多个团队里推过AI代码审查最大的感受是工具解决的是判断题流程解决的是信任题。open-code-review这种混合架构本质上是把哪些问题应该被提出这个发现环节交给确定性流水线把这个问题是不是真的值得改这个判断环节交给LLM Agent。这种分工意味着每一条审查意见都同时具备确定性的来源和智能的解释力——带着调用关系图来论据说服你比一句我觉得这里有问题可信得多。落地这套东西的过程中我最想提醒别人的一点是不要追求审查意见的数量不要想着替换掉人的review。在很长一段时间内AI都只是前置漏报过滤器和重复劳动消化器真正的架构决策、业务语义合理性判断还是得靠人。把自己系统中所有重复性、确定性、规则性的审查内容交给流水线和Agent让人力专注于真正的架构评审——这样的组合效率最高团队的接受度也最强。如果你也在搭类似的东西建议从小仓库、小规则集起步先把流水线层做扎实再逐步放开Agent的权限。跑通一轮之后再看哪些环节值得优化——大概率会发现Agent的价值比预想的晚显现但确定性流水线的价值比预想的早见效。