ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

确定性流水线+LLM Agent:AI代码审查的工程化混合架构

确定性流水线+LLM Agent:AI代码审查的工程化混合架构 1. 从单点魔法到工程化流水线混合架构出现的必然性1.1 先聊聊纯 LLM 代码审查的痛点过去一年多我见过不少团队把代码审查直接交给 LLM做法很简单把 diff 贴给 ChatGPT 或者自家模型让它帮我看一下有没有问题。初期确实惊艳但用久了你会发现这条路走不长远。最核心的问题是结果不可复现。同一个 diff 同一套提示词今天跑出来说这里有内存泄漏明天跑出来说没问题甚至同一个轮次里不同采样温度出来的结论都互相矛盾。代码审查本质上是工程流程的一环工程流程要求的是稳定、可预期、可审计——这轮评审为什么没拦住这个 bug这个问题如果答案只是模型那天心情不好在正经团队里是交不了差的。第二个问题是幻觉边界模糊。LLM 很容易在你不给任何约束的情况下产生看起来专业的错误。它可能指着一段完全没有问题的代码说这里存在 SQL 注入风险也有可能对真正的注入点视而不见。更麻烦的是它会把建议包装成问题把风格偏好包装成缺陷——在没有确定性依据兜底的时候人很难分辨哪些结论是可靠的。第三个问题是成本和延迟不可控。把整仓库代码或者整个大文件塞进上下文一次评审的 token 消耗高得惊人响应时间几十秒甚至几分钟这在 review 一个只有几十行改动的小 PR 时完全无法接受。1.2 确定性工具为什么不够用那反过来传统的静态分析工具呢ESLint、SpotBugs、golangci-lint 这些工具很可靠同样的输入永远有同样的输出执行快、规则透明。但它们的问题在于——只能识别语法和模式层面的问题。拿一个最典型的场景来说一段代码把用户输入的字符串拼接进 SQL 查询。静态规则能识别字符串拼接但很难判断这个字符串是不是真的来自请求参数、有没有经过过滤、底层用的是不是参数化查询框架。这些判断需要上下文语义理解。再比如业务逻辑问题一个订单状态流转里漏了已取消订单不能重复支付的校验。这种问题静态规则根本不可能发现因为它在语法上是完全合法的问题出在你对业务规则的理解上。所以纯确定性工具能守住底线但守不住上限纯 LLM 能扩展上限但兜不住底线。这就是 open-code-review 这类项目采用确定性流水线 LLM Agent混合架构的根本原因——让确定性的部分负责必须对的事让 LLM 负责需要理解的事。1.3 混合架构的核心理念分工而非替代我自己的理解是混合架构不是简单地把两种工具拼在一起而是重新定义了各自的职责边界确定性流水线负责采集变更、解析语法、执行规则引擎、生成结构化的事实——这段 diff 改了哪些文件、哪些函数、哪里匹配了已知的反模式、圈复杂度是多少。这一层输出的东西是客观的是可以测试的是能够写进审计日志的。LLM Agent则在确定性事实的基础上做二次加工——基于变更范围和规则命中的线索生成深度的语义分析、给出修复建议、判断规则命中是误报还是真问题。这个分工最重要的价值是LLM 不再是凭空判断而是在一个被约束的、上下文裁剪过的、有确定结论支撑的框架内做推理。它的幻觉空间被大幅压缩因为它的注意力被引导到真正值得看的地方同时确定性规则覆盖不到的语义问题又能被 LLM 的推理能力补上。open-code-review 之所以值得拆解就是因为它把这条思路落成了可运行的工程实现。接下来我从整体结构、核心细节、实操落地、问题排查四个层面把它拆开讲。2. 整体架构拆解两级流水线是如何组织的2.1 第一级确定性流水线的五个阶段从工程实现的视角看open-code-review 的确定性流水线大致可以切成五个阶段每个阶段职责单一输出物都是结构化的数据方便下一个阶段消费。阶段一变更采集。通过 git diff、git log 或者直接读 CI 系统传入的 MR/Patch 文件拿到代码变更集。这一步看起来简单但有个关键设计它只关心变更的行而不是整个文件。这个决策直接影响后续所有环节的成本和准确度。阶段二语言识别与文件分类。根据文件后缀、工程配置文件package.json、go.mod、pom.xml识别出这是什么语言、属于哪个模块、是不是测试文件、是不是生成代码比如 .pb.go、.min.js。这一步决定了后面用哪个 AST 解析器、挂载哪套规则集、以及 LLM Agent 需要补充工程上下文。阶段三AST 解析与结构提取。这一步是整个流水线最有技术含量的地方。open-code-review 没有用正则去匹配代码而是真正把变更文件解析成抽象语法树然后提取变更涉及的函数签名、类名、调用关系、控制流信息。举个例子如果你改了一个函数的参数列表AST 层能明确告诉下游这个函数的 4 个调用点都受这次改动影响——这类精确的关联关系靠字符串匹配是不可能稳定得到的。阶段四规则引擎执行。把提取出的 AST 结构喂给确定性规则引擎。规则引擎里维护着一批可配置的规则每条规则本质上是检查 AST 上某个节点是否满足某个条件。规则命中后输出一个结构化的 Issue 对象包含规则编号、严重级别、位置、命中的模式说明。阶段五结果归一化与评分。把所有规则命中结果汇总按文件和严重级别分组并给出一个初步的风险评分。这个评分不直接作为最终结论而是作为 LLM Agent 的输入参考——如果规则命中了 20 个高危项Agent 需要优先处理这些区域。这五个阶段每一层都对应一个清晰的模块边界可以单独测试、单独替换。比如你想把 ESLint 换成 Biome只影响规则引擎这一层想支持一门新语言只需要新增一个 AST 解析适配器。2.2 第二级LLM Agent 的编排方式第二级不是简单地把 diff 丢给模型而是一个有状态的、多步骤的 Agent 工作流。open-code-review 里的 LLM Agent 核心是一套目标-上下文-工具-输出的循环目标注入告诉 Agent 当前评审任务的类型。是常规 PR 审查、安全专项审查还是需要跟踪某个历史缺陷模式的复查。不同的目标会切换不同的提示词模板和评审清单。上下文组装把确定性流水线分阶段输出的内容组装成上下文。这里有一个我在其他项目里反复强调的原则——上下文不是越多越好而是越相关越好。open-code-review 的做法是给 Agent 四类信息当前变更的行级 diff必须受影响的函数/文件的结构摘要来自 AST 层规则引擎命中但需要二次确认的 Issue 列表来自规则层少量的相关历史代码片段根据引用关系选择性带入工具调用Agent 收到上下文后如果发现信息不足——比如某个变量的初始化逻辑不在 diff 范围内——它有权调用工具去读取指定文件的具体行段。注意这个读取路径是受控的Agent 不能漫无目的地扫描整个仓库。结构化输出最后 Agent 必须以规定格式输出结果而不是自由发挥的对话文本。每条结论包含文件位置、行号、问题类型、置信度、修复建议以及判断依据。这个结构化输出直接决定了后面能不能做自动标注和自动过滤。这套编排的精髓在于Agent 的所有推理过程都发生在确定性流水线划定的舞台上。它能做语义理解但理解的半径被限制在一组相关文件和相关函数内不被允许猜diff 之外的代码长什么样。2.3 两层之间的桥让它对话起来的接口设计很多人把混合架构做砸问题出在两层之间没有顺畅的接口。open-code-review 的做法我觉得很值得借鉴它用统一的结构化数据格式串起所有阶段。确定性流水线内部各阶段之间传递的是 Protocol Buffer 定义的数据结构到了规则引擎输出层统一转成一个ReviewIssue对象。这个对象大概长这样我用伪代码示意message ReviewIssue { string rule_id 1; string file_path 2; int32 start_line 3; int32 end_line 4; Severity severity 5; string message 6; repeated string tags 7; mapstring, string evidence 8; }LLM Agent 消费的输入也是基于这套结构展开的。这样设计的好处是确定性层产生的每一个问题都带身份证rule_id和证据链evidenceAgent 可以直接基于证据做二次判断而不是自己重新翻代码。桥接层还有一个重要职责是做一次优先级重排。规则引擎命中的 50 个问题不可能全部丢给 LlM 去复核那样既费 token 又拉低响应速度。open-code-review 的做法是高严重级别且疑似误报的才进入 Agent 复核队列低严重级别问题直接随规则结果输出不占用模型推理。这个取舍我特别认同——LLM 的计算量是稀缺资源必须花在最需要判断力的地方。3. 核心实现细节从 diff 采集到 Agent 推理的关键环节3.1 变更范围裁剪为什么只评审改动涉及的影响面我最早做 AI review 工具时犯过一个典型错误拿到一个 PR直接把整个文件甚至整个模块丢给模型。后果是——模型注意力被大量无关代码稀释经常在一处历史代码里挑出风格问题却漏掉了真正改动行的逻辑缺陷。open-code-review 在这块的处理值得学习它把评审范围定义为变更直接涉及 变更间接影响两个圈层。第一圈层变更行本身。来自 diff 的 additions 和 deletions。这是必须逐行看的内容。第二圈层变更影响到的关联代码。这一层通过 AST 提取的符号引用来确定。比如某个被修改的公共函数它在同文件里有两处调用在其他文件里还有一处调用这三处调用点都要拉进上下文。但注意只看调用点的签名和参数传递方式不需要把整个调用链后端的实现也拉进来——那是无底洞。裁剪策略上open-code-review 给了两个可配置参数context-lines控制 diff 上下文的保留行数我一般设 5~10 行max-associated-files控制关联文件的最大数量默认 5 个超过的按引用频次截断。这两个参数直接影响 token 消耗和评审效果后面我会讲怎么调。3.2 规则引擎的三类规则硬规则、软规则、语义锚点open-code-review 的确定性规则引擎不是简单把 ESLint 拿过来复用它自定义了一套规则体系分成三类。硬规则Hard Rules检测到就必须报告的规则。典型的如禁止eval、禁止调试断点提交、禁止硬编码密钥。这类规则的误报率趋近于零输出即结论不需要 LLM 复核。软规则Soft Rules可能有问题但需要人工/模型判断的规则。比如函数圈复杂度超过 15在循环体内调用可阻塞的同步 IO直接拼接了 SQL 字符串。这类规则命中后不直接下结论而是作为证据标记推送给 LLM Agent让模型结合上下文判断是不是真问题。语义锚点Semantic Anchors这是 open-code-review 比较特别的设计。它用 AST 提取出一些语义事实本身不触发问题但可以作为 LLM 的推理线索。比如这个函数的返回值在调用方被用作金额计算这个变量被赋值后没有在后续分支中重新赋值。这些锚点相当于给 LLM 提供了解题思路的起点让它的推理有方向感。三类规则的划分体现了混合架构的核心设计哲学能确定的事情绝不交给模型需要理解的事情尽量给模型铺好路。3.3 提示词与输出约束让模型稳定产出工程可用的结果LLM Agent 的提示词设计是另一个关键。我在拆 open-code-review 的提示词模板时注意到它有几个值得复用的设计原则。原则一给角色但更给任务边界。提示词里不只是写你是一名资深代码审查专家而是明确写清楚你只需要基于给定的 diff 上下文和我标记的疑似问题区域做判断禁止猜测超出上下文范围的代码逻辑。角色定义是给模型一种表达风格任务边界是限制它的推理范围。原则二用清单式问题引导推理。与其让模型自由发挥说请审查这个 diff不如给它一份评审清单。清单大致包括变更是否引入了未处理的分支/异常路径是否有资源分配后没有在异常路径释放并发场景下是否存在竞态条件错误处理是否吞掉了原始异常信息是否破坏了现有接口的兼容性清单式引导的效果远好于开放式提问。原理在于它把模型的注意力分配到几个高价值维度上而不是让它均匀地浏览所有代码。原则三强制结构化输出。我在多次实操中发现如果你不限制输出格式模型给的建议是这篇代码写得不错注意一下 XXX 可能有问题这种文本根本没法进入工程闭环。open-code-review 的做法是在提示词末尾给出一个 JSON Schema并要求模型严格按 Schema 输出。格式如下简化示意{ findings: [ { file: src/order/service.go, line_start: 124, line_end: 130, severity: high, category: concurrency, confidence: 0.85, summary: 在未加锁的情况下修改共享的 map, rationale: orders 这个 map 在并发回调中被读写存在 data race 风险, suggestion: 使用 sync.Mutex 保护该 map或改用 concurrent-map 库 } ] }有了这套约束模型的输出可以直接喂给后续的自动标注、自动评论、甚至自动修复模块。这里提醒一句JSON Schema 一定要在提示词里附上字段说明和示例值空口说output json会出现各种 key 拼写变体处理起来非常痛苦。4. 实操落地把 open-code-review 接进团队开发流程4.1 本地跑通最小可用配置先说本地怎么跑起来。open-code-review 的安装方式很常规用 Go 编写的二进制一条命令就能拉取go install github.com/open-code-review/corelatest # 或者如果项目提供了 Docker 镜像 docker pull open-code-review/core:latest本地执行的基本形式是对一个 git diff 做评审open-code-review review \ --diffgit diff origin/main...HEAD \ --languagego \ --rulesetstrict \ --outputjson这里--ruleset参数可以指定规则策略open-code-review 内置了default、strict、security、relaxed四套预设策略。我的建议是本地调试时用default接入 CI 时用strict因为本地的目的是把明显问题揪出来CI 的目的是拦截所有可疑项进入主分支。如果选了 strict需要把 LLM Agent 也打开否则那些软规则命中项会变成悬空问题。配置模型参数我习惯用配置文件的方式而不是命令行参数因为配置多了之后命令行会变得难维护# review.yaml llm: provider: openai model: gpt-4o api_timeout_sec: 60 max_tokens: 2048 temperature: 0.2 pipeline: context_lines: 8 max_associated_files: 5 review_refs: true rules: profile: strict custom: - rule_id: CUS_DISALLOW_TODO pattern: TODO severity: warning description: 禁止提交带 TODO 的代码关于 temperature 这个参数我要多说一句。评审任务务必把 temperature 设成 0 或者接近 0。我见过有人默认用 0.7结果同一个 diff 两次评审结论差异巨大直接被团队质疑系统的可靠性。代码审查是判卷任务不是创作任务越确定越好。4.2 CI 集成在合并前自动拦截本地跑通之后真正发挥价值的场景是接入 CI。open-code-review 设计了一个guard子命令专门用于 CI 场景它的行为是执行完整流水线然后根据预设阈值决定进程退出码——如果高风险问题数超过阈值返回非 0 退出码从而阻断本次流水线。GitHub Actions 的接入非常简单name: ai-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - uses: open-code-review/actionv1 with: api-key: ${{ secrets.LLM_API_KEY }} rules: strict fail-on-severity: high comment-on-pr: false这里两个参数值得说清楚。fail-on-severity: high表示只有 high 级别问题才会阻断合并warning 级别问题只做记录避免因为风格问题阻塞开发节奏。comment-on-pr: false我建议前期先关掉让结果只输出到 Actions 日志里等规则稳定之后再打开自动评论能力——不然每轮 PR 都刷几十条机器人评论开发同学会直接把机器人拉黑。CI 模式下的另一个建议是给 LLM Agent 的调用加上结果缓存。open-code-review 默认会对相同 diff 的 hash 做结果缓存同一份提交内容不会重复消耗 token。这个设计在反复重跑 CI 时能省不少钱。4.3 自定义规则与私有模型的接入再讲两个实际应用中几乎必然会碰到的需求自定义规则和私有模型接入。自定义规则的写入格式和 open-code-review 内置规则一致本质上是一段基于 AST 的查询表达式。以 JavaScript 项目为例想禁止使用console.log提交到生产代码可以这样写规则custom_rules: - rule_id: CUS_NO_CONSOLE languages: [javascript, typescript] query: NewExpression CallExpression MemberExpression Identifier[nameconsole] severity: warning message: 生产代码中不允许出现 console.log请改用业务日志框架这条查询表达式的含义是在 AST 上查找console.log这种 MemberExpression 结构。AST 查询的好处是它能精准匹配成员调用不会把用户自己定义的名为 log 的函数误伤掉。这里要特别注意写自定义规则前一定要先弄清楚目标语言的 AST 节点结构最简单的方式是先跑一次open-code-review parse --dump-ast看真实代码解析出来的结构长什么样再照着结构写查询。私有模型接入方面只要你的 LLM API 兼容 OpenAI 格式就可以通过配置 base_url 接入llm: provider: openai base_url: https://llm.internal.example.com/v1 model: internal-review-model api_key_env: LLM_INTERNAL_KEY我实际测下来私有化部署的 7B~14B 模型在代码评审上的表现和老牌大模型有明显差距主要体现在误报率偏高。所以建议私有模型接入初期把置信度阈值调高到 0.9过滤掉低置信度的 LLM 结论等积累一轮数据之后再逐步放宽。4.4 评审结果的分流处理最后要讲的是结果分流这一步决定了整个系统能不能被团队接受。open-code-review 把输出分成三类阻塞项high 级别的确定性规则命中直接让 CI 失败。需要确认项软规则命中 LLM 高置信度结论自动在 PR 中生成评论相关负责人确认。建议项低置信度建模结论只写进汇总报告不打扰开发者。千万不要把所有结论都当成阻塞项否则一天下来团队会陷入机器人报警疲劳最后所有人对评审结果变得麻木。让流水线有轻重缓急的意识是它能不能融入团队协作的关键。我见过太多团队把 AI 审查做成电子警察结果被开发者集体抵制最后只能关掉功能。open-code-review 的三级分流设计在这个问题上提供了一个成熟的参考——它追求的从来不是AI 说了算而是AI 帮你把注意力放在刀刃上。5. 常见问题与排查实录5.1 误报和漏报怎么平衡这是所有接入 AI 代码审查的团队问得最多的一个问题。误报多了团队嫌吵漏报多了工具形同虚设。我的经验是分阶段调整规则策略。第一阶段前两周只开启硬规则 LLM 的 high 置信度结论目标是把误报率压到 10% 以下让团队建立对系统的信任。第二阶段逐步开放软规则但汇总报告里要保留误报申诉入口——开发者可以对一条评审结果标记误报这些反馈回传到规则引擎形成拒绝样本。第三阶段基于积累的样本微调提示词、调整置信度阈值让系统越来越贴近团队的真实代码风格。temperature设 0、confidence阈值从 0.7 逐步上调到 0.9是我实测最有效的两招。另外注意不要在每轮 PR 上都改阈值至少要跑一两周、积累几十个样本后看统计数据再动。5.2 LLM 上下文截断问题上下文超长是 AI 代码审查的经典问题。我遇到过一个 2000 行改动的巨型 PR直接把上下文塞给 gpt-4o输入长度超限被截断结果截断之后模型漏掉了最关键的改动评审结论奇差。open-code-review 的思路是从源头限制进入上下文的代码量。除了前面说的范围裁剪它还有一个chunk-and-aggregate机制——把大 PR 按文件拆成多个评审单元每个单元独立过模型最后把各单元的结论聚合去重。我实操下来单次评审的输入控制在200 行 diff 500 行关联代码以内效果和稳定性最好。如果你们的 PR 普遍很大建议在 CI 层叠加一个宽松限制diff 行数超过 800 行的 PR先强制人工评审AI 评审结果单独附上避免超大 PR 把流水线拖垮。5.3 成本和延迟怎么控制成本问题方面最费钱的是每次 CI 都对全部变更跑一遍 LLM。我分享几个省钱的实测经验设置重复 diff 缓存相同 hash 内容直接复用上次结果不再调模型。PR 更新后如果没有实质变更这一轮几乎零成本。对低严重级别文件测试文件、配置文件的非逻辑改动直接跳过 LLM 环节只跑确定性规则。大部分场景下测试文件的评审价值确实有限。用更便宜的模型做初筛只有初筛标记为疑似问题的片段才用大模型做深度复核。这种两级模型方案能省 60% 左右的 LLM 费用。延迟方面影响最大的是 LLM 推理时间。把max_tokens限制到合理范围评审任务一般 1024~2048 够用了比放任模型长篇大论快得多。确定性流水线本身是非常快的一般几十毫秒到几百毫秒所以整条流水线的延迟几乎等于 LLM 调用延迟——控制好上下文大小单次评审能稳定在 10 秒以内完成。5.4 与团队规范冲突的排查思路还有一种常见情况AI 审查结果和团队人力审查结论冲突。比如团队认为错误处理不完善的问题必须修复但 AI 给的是 warning 级别合并前没有强制拦截。我的排查思路是先看规则配置再看置信度阈值。团队规范往往反映的是项目特有的历史教训这些教训大概率不在 open-code-review 的通用规则库里。解决办法不是改全局阈值而是针对团队场景写自定义规则。比如你们的支付模块连续出过未校验订单状态的 bug那就写一条语义锚点规则专门检测支付相关函数里订单状态的校验逻辑设置成 high 级别。这样既保留了通用规则的覆盖面又把团队特有的高优关注点焊死在确定性流水线里。排查这类问题时open-code-review inspect子命令很有用它能输出某一条评审结论的完整推理链哪条规则命中、哪个 AST 节点、LLM 的判断依据是什么、置信度多高。我强烈建议团队接 AI 审查的头一个月每月拉一次推理链日志复盘把反复出现的误报规则、反复被团队忽略的火警规则都找出来调整。6. 顺着这条链路还能做什么扩展写到这里关于 open-code-review 的拆解已经覆盖了架构、实现、实操和排障。最后分享几个我实际觉得值得继续深挖的方向。一个是把评审结论沉淀成知识库。AI 每次评审发现的真实缺陷经过人工确认后可以转成新的确定性规则或补充进语义锚点库。跑半年后这套系统的确定性占比会越来越高LLM 的调用量会越来越低效果却越来越稳定——这就是混合架构让人上瘾的地方它不是一个静态工具而是一个能自我进化的系统。另一个方向是和自动化修复打通。现在 open-code-review 的输出是结构化的 JSON理论上可以直接接入 codemod 或者基于 LLM 的自动修复合集。我试过针对硬编码密钥这类模式清晰的问题做自动修复成功率和安全性都还不错但涉及业务逻辑的修复建议还是建议保持人工确认后应用的模式不要全自动落库。还有一个我最近在探索的点让 Agent 关注变更之间的交互。单个 diff 文件内部的问题确定性流水线加单 Agent 已经能覆盖得很好但跨文件的 API 契约变更、多个文件同时修改导致的并发行为变化这种系统级问题单 Agent 的能力还是有限。open-code-review 的多 Agent 编排预留了扩展位可以按模块各自评审再做交叉汇总。这一块目前成熟度还不高但我觉得是下一代 AI 代码审查的核心方向。拿我自己团队的经验来说接入这套混合架构最明显的体感变化是每轮 PR 回退沟通成本显著下降评审结论的争议少了而且所有结论都有据可查。我认为比单个环节更重要的是设计上让确定性流水线守住纪律、让 LLM Agent 负责判断这套分工原则——理解了它换成任何一个工具你都能做出同样可靠的工程化代码审查方案。
返回列表