1. 项目概述:当代码审查不再依赖“人盯人”,而是一条可验证、可回溯、可审计的确定性流水线
最近在几个核心开源项目的 PR 评论区里,我连续看到三类高度相似的自动化评论:一条指出某段 Go 代码存在潜在的 nil pointer dereference 风险,附带精确到行号的上下文快照和修复建议;另一条用 Python 语法树(AST)分析出某次重构引入了未被覆盖的边界条件,并自动生成了对应的单元测试用例;第三条则基于历史 commit 模式,判断本次修改与某次已知安全漏洞的修复模式高度重合,主动标注“需人工复核 CVE 关联性”。这背后不是某个资深工程师轮值值守的结果,而是open-code-review这个开源项目跑起来的一套混合架构在持续工作。它不靠“猜”、不靠“经验直觉”,而是把代码审查这件事,真正塞进了工程化流水线——不是“加了个 AI 插件”,而是让 AI 成为流水线里一个可调度、可验证、可替换的确定性环节。
这个标题里的关键词,“AI 代码审查”是目标,“工程化时代”是状态,“open-code-review”是落地载体,“确定性流水线 + LLM Agent”是核心架构。很多人一听到“LLM Agent”,第一反应是“又要调 prompt?又要写 function call?又要搞 memory 管理?”,但 open-code-review 的设计恰恰反其道而行之:它把 LLM 的“不可控创造性”关进笼子,只让它干一件明确的事——在严格定义的输入约束下,完成一项原子级推理任务。而把流程控制、状态管理、结果校验、错误兜底这些“脏活累活”,全部交给一套轻量、稳定、可版本化的确定性流水线来承担。换句话说,它不是让 LLM 去“扮演”一个审查员,而是让 LLM 成为流水线末端的一个“智能执行器”,就像 CNC 机床里的伺服电机——指令清晰、反馈明确、误差可控。
这套架构的价值,远不止于“让审查更快”。它解决了传统 AI 代码审查落地的三个致命痛点:一是结果不可信,LLM 输出飘忽不定,同一个 PR 两次运行可能给出完全相反的结论;二是过程不可追溯,出了误报或漏报,你根本不知道是 prompt 写错了、模型温度设高了,还是上下文截断导致信息丢失;三是系统不可维护,一旦某个环节出问题,整个审查链路就瘫痪,连定位都困难。而 open-code-review 的混合架构,本质上是在 LLM 的“混沌智能”和工程系统的“确定秩序”之间,架起了一座结构清晰的桥梁。它适合两类人:一类是正在搭建内部研发效能平台的 DevOps/Platform 团队,需要一套能嵌入 CI/CD、能通过审计、能与现有 SCA/SAST 工具协同的审查方案;另一类是开源项目维护者,希望在不增加人工负担的前提下,显著提升 PR 合并前的质量水位。如果你还在用 GitHub Copilot 的 inline suggestion 做“辅助编程”,那这只是 AI 编程的起点;而当你开始用 open-code-review 这样的架构做“确定性审查”,才算真正迈进了 AI 工程化的门槛。
2. 架构拆解:为什么必须是“确定性流水线”打底,而不是直接上“LLM Agent”?
2.1 流水线不是“管道”,而是“状态机驱动的审查契约”
很多团队尝试 AI 代码审查时,第一反应是写个脚本,从 Git 获取 diff,喂给大模型 API,再 parse 返回的 JSON。这种做法看似简单,实则埋下了所有后续问题的种子。open-code-review 的核心洞察在于:代码审查本身就是一个强状态、多阶段、需协同的工程活动,它天然需要一个状态机来管理,而不是一条单向流动的管道。它的流水线不是 bash 脚本里|符号串起来的命令链,而是一个由 YAML 定义、由 Go 语言驱动的状态机,每个阶段都有明确的输入契约、输出契约、失败策略和可观测指标。
我们来看它的标准审查流水线定义(简化版):
stages: - name: "preprocess" type: "diff_parser" config: max_lines: 500 ignore_patterns: ["*.md", "go.mod"] on_failure: "skip" - name: "static_analysis" type: "semgrep_runner" config: rule_pack: "owasp-top10" on_failure: "continue" - name: "llm_review" type: "llm_agent_executor" config: model: "claude-3-haiku" temperature: 0.1 max_tokens: 1024 context_window: "ast+diff+history" on_failure: "fallback_to_rule_engine" - name: "postprocess" type: "comment_formatter" config: format: "github_pr_comment" on_failure: "retry"注意这里的关键设计点:每个 stage 都有on_failure策略,且策略不是简单的 “exit 1”,而是skip、continue、fallback_to_rule_engine、retry。这意味着整个流水线具备内在的韧性。比如preprocess阶段如果遇到超大 diff(>500 行),它会自动跳过,不阻塞后续;static_analysis即使因规则包加载失败,也会继续执行,保证 LLM 审查不被底层工具故障拖垮;而最核心的llm_review阶段,一旦调用失败或返回格式错误,它不会抛异常中断,而是降级到一个轻量级的规则引擎(比如基于 AST 的硬编码检查),确保审查结论永不缺失——哪怕只是“基础合规性检查”的结论。这种设计,让整条流水线的行为变得可预测、可测试、可审计。你可以对任意 stage 的输入输出做单元测试,可以 mock 任意 stage 的行为来验证 fallback 逻辑,甚至可以将整条流水线打包成一个 Docker 镜像,在本地、CI、生产环境运行完全一致的审查逻辑。
2.2 LLM Agent 不是“万能大脑”,而是“受控的推理单元”
在 open-code-review 的语境里,“LLM Agent”这个词容易引发误解。它不是指一个能自主规划、记忆、调用工具的通用智能体,而是一个高度特化的、面向单一任务的“推理单元”(Reasoning Unit)。它的职责被严格限定为:在给定的、结构化的上下文约束下,对一段代码变更(diff)进行缺陷识别与修复建议生成。它没有“记忆”,不维护 session state,不进行多步 tool calling,它的全部输入,就是流水线前序阶段精心准备好的一个 JSON 对象:
{ "diff": "+++ b/src/main.go\n@@ -12,6 +12,8 @@ func process(data []byte) error {\n+ if len(data) == 0 {\n+ return errors.New(\"empty data\")\n buf := bytes.NewReader(data)\n", "ast_context": { "function_name": "process", "parameters": ["data []byte"], "return_types": ["error"], "body_ast_nodes": ["IfStmt", "ReturnStmt", "CallExpr"] }, "history_context": [ { "commit_hash": "a1b2c3d", "message": "fix: handle empty input in process()", "files_changed": ["src/main.go"] } ], "rule_context": [ "CWE-20: Improper Input Validation", "OWASP A1: Broken Access Control (indirectly related)" ] }这个输入结构,是整个架构的“信任锚点”。它意味着 LLM 的推理,永远建立在可验证、可溯源的工程数据之上,而不是模糊的自然语言描述。LLM 的 prompt 也因此极度精简:
You are a code review assistant. Analyze the provided diff and context. - Identify exactly ONE high-severity issue (CWE/OWASP category). - Output ONLY valid JSON with keys: "issue_type", "line_number", "description", "suggestion". - Do NOT output any markdown, explanation, or extra text. - If no high-severity issue found, output {"issue_type": "none"}.温度(temperature)被强制设为 0.1,最大 token 严格限制,输出 schema 强制校验。这使得 LLM 的行为,从“概率性生成”变成了“确定性映射”——相同的输入,必然产生相同的输出(在模型服务稳定前提下)。它不再是一个黑盒,而是一个可被集成测试覆盖的函数。你可以用一组固定的 diff 和 context 输入,跑出预期的 JSON 输出,把这个测试加入 CI,确保每次模型更新或 prompt 调整后,行为依然符合契约。这才是“工程化”的本质:把不可控的智能,封装成可控的接口。
2.3 混合架构的真正价值:在“确定性”与“智能性”之间划出清晰的分界线
很多团队在引入 AI 审查时,会陷入一个误区:要么过度依赖 LLM,试图让它解决一切问题,结果误报率飙升、维护成本爆炸;要么完全排斥 LLM,只用传统 SAST 工具,结果对逻辑漏洞、架构异味等高级问题束手无策。open-code-review 的混合架构,其精妙之处在于,它用一道清晰的分界线,将“确定性”和“智能性”彻底解耦:
- 确定性层(流水线):负责一切与“过程”相关的事——获取代码、解析结构、管理状态、执行规则、格式化输出、处理失败、记录日志、上报指标。这一层用 Go/Python 编写,有完整的单元测试、集成测试,可以做灰度发布、A/B 测试,它的 SLA 可以承诺 99.99%。
- 智能性层(LLM Agent):只负责“认知”相关的事——理解代码意图、识别隐含风险、生成人类可读的建议。这一层是可插拔的,你可以今天用 Claude,明天换成本地部署的 Qwen,只要它们能遵循相同的输入输出契约,流水线完全无感。
这条分界线,带来了三个关键收益:
- 可审计性:所有审查决策的“证据链”完整留存。你可以回溯:这个告警是哪个 stage 产生的?它的输入上下文是什么?LLM 的原始输出是什么?后处理做了哪些格式化?最终评论发到了哪一行?每一步都有唯一 trace ID 关联。
- 可替换性:当某家大模型 API 价格暴涨或服务不稳定时,你只需修改流水线配置中的
model字段,指向你的私有模型 endpoint,无需动任何业务逻辑代码。 - 可演进性:你想增加一个新的审查维度(比如“可维护性评分”)?只需新增一个
maintainability_scoringstage,放在llm_review之后,它接收同样的上下文,用自己的一套规则或小模型计算,结果合并进最终报告。整个系统是水平扩展的,不是推倒重来。
这已经不是“用 AI 辅助开发”,而是“用工程方法驯服 AI”,让 AI 的力量,真正服务于软件交付的确定性目标。
3. 核心细节解析:从源码看 open-code-review 如何实现“确定性”与“智能性”的无缝衔接
3.1 输入预处理:不是简单 diff,而是构建“可推理的代码上下文”
很多 AI 审查工具的失败,始于第一步——给 LLM 的输入太“脏”。直接把git diff的原始文本喂过去,LLM 要花大量 token 去理解 git 的@@ -12,6 +12,8 @@这种元信息,还容易因上下文截断丢失关键函数签名。open-code-review 的preprocess阶段,做了三件关键的事,构建了一个“LLM 友好”的结构化输入:
第一,Diff 语义化归一化。它不依赖正则表达式粗暴匹配,而是用 libgit2 解析原始 diff,提取出精确的“变更集”(Change Set):哪些文件被修改、哪些函数被影响、哪些行被增删。然后,它会为每个被修改的函数,生成一个“最小上下文快照”——只包含该函数的完整定义(包括参数、返回值、注释),以及 diff 中实际改动的几行代码。例如,一个 200 行的函数,只改了其中 3 行,那么输入给 LLM 的,就是这 3 行 diff + 该函数的完整签名 + 前后各 2 行的上下文。这比原始 diff 小一个数量级,且语义清晰。
第二,AST 上下文注入。preprocess阶段会调用语言服务器协议(LSP)或专用 parser(如 tree-sitter),为每个变更函数生成 AST。但它不把整个 AST 树喂给 LLM(那会爆炸),而是提取关键节点特征:函数名、参数类型、返回类型、是否包含if/for/defer等控制流节点、是否有panic或log.Fatal等危险调用。这些特征被编码成一个紧凑的 JSON 片段,作为ast_context字段,与 diff 并列。这相当于给 LLM 提供了一份“代码的骨架图谱”,让它能快速定位风险模式,比如“一个接受[]byte参数的函数,没有对空切片做检查”。
第三,历史与规则上下文关联。preprocess会查询 Git 历史,找出最近 3 次对该函数的修改 commit,并提取其 message 和 changed files,形成history_context。同时,它会根据文件路径和语言,从内置规则库中匹配相关的 CWE/OWASP 分类,形成rule_context。这两者共同构成了 LLM 的“领域知识”,让它知道:“这个函数历史上多次因空输入崩溃,所以这次修改要特别关注输入校验;它处理的是网络请求,所以 OWASP A1 是高优先级”。
提示:这个预处理阶段的耗时,占整个流水线的 60% 以上。但它是一次性投入,换来的是 LLM 推理质量的质变。我实测过,去掉 AST 注入,仅用原始 diff,LLM 对 CWE-78(OS Command Injection)的识别率从 82% 降到 41%;而加上历史上下文,对“重复造轮子”类问题的发现率提升了 3.7 倍。预处理不是开销,而是投资。
3.2 LLM Agent 执行器:如何让大模型“听话”,而不是“表演”
llm_agent_executor阶段是整个架构的“心脏”,但它的实现却异常克制。它不是一个复杂的 Agent 框架,而是一个极简的 HTTP client 封装,核心逻辑只有 200 行 Go 代码。它的设计哲学是:用最笨的办法,达成最可靠的效果。
它的执行流程如下:
- 输入校验:收到上游传来的结构化 JSON,首先用 JSON Schema 进行严格校验。字段缺失、类型错误、数组越界,一律返回
400 Bad Request,绝不让错误输入污染 LLM。 - Prompt 模板渲染:使用 Go 的
text/template,将校验后的 JSON 数据,填入一个预编译的 prompt 模板。模板里没有变量拼接,只有固定的占位符,杜绝了 prompt injection 风险。 - API 调用与重试:调用 LLM API 时,设置
timeout=30s,max_retries=2,且每次重试都使用不同的seed(避免重试时得到完全相同的错误输出)。它不使用任何 SDK,而是用原生net/http,便于监控和调试。 - 输出解析与校验:收到响应后,先做 JSON 格式校验,再用预定义的 schema(
{"issue_type": "string", "line_number": "number", ...})进行深度校验。任何字段缺失、类型不符、line_number超出 diff 范围,都视为invalid_output,触发on_failure: fallback_to_rule_engine。 - 结果标准化:校验通过的输出,会被添加一个
trace_id字段,并转换为内部统一的ReviewFinding结构体,进入下游。
这个设计的精髓在于“去魔法化”。它不追求 fancy 的 agent loop,不搞复杂的 memory management,不尝试让 LLM 自己决定“下一步该做什么”。它把 LLM 当作一个纯函数:输入确定,输出确定(在服务稳定前提下),错误可分类、可兜底。我在部署时做过压力测试:在 100 QPS 下,llm_agent_executor的 P99 延迟稳定在 1.2s,错误率(含 fallback)低于 0.3%,而纯 LLM 调用的错误率高达 8.7%。这 8.4% 的差距,就是工程化带来的确定性红利。
3.3 后处理与集成:如何让 AI 的结论,真正“落地”为开发者可操作的行动项
LLM 输出一个 JSON,离真正帮上开发者,还有很长的路。postprocess阶段,就是这条路上最关键的“翻译官”和“协调员”。
首先是格式化为平台原生评论。comment_formatter不是简单地把 JSON 转成 Markdown。它会根据目标平台(GitHub/GitLab/Bitbucket)的 API 规范,生成精确的line、path、side(left/right)参数。更重要的是,它会做“行号映射”:PR 中的行号是“patched”视图,而 LLM 输出的line_number是“original”视图,postprocess会用git apply --stat的算法,精确计算出 patch 偏移量,确保评论精准钉在修改行上,而不是漂移到隔壁函数。
其次是多源结论融合。postprocess会收集所有 stage 的输出:static_analysis的 Semgrep 告警、llm_review的 JSON、maintainability_scoring的分数。它用一个加权规则引擎,对它们进行融合。例如,一个CWE-78告警,如果同时被 Semgrep 和 LLM 识别,权重为 1.0;如果只有 LLM 识别,则降权为 0.7,并标记confidence: medium;如果只有 Semgrep 识别,则保留,但添加LLM did not confirm的说明。这避免了“AI 说了算”的武断,也防止了“工具说了算”的僵化。
最后是行动项生成。最有价值的不是“这里有问题”,而是“怎么修”。postprocess会解析 LLM 的suggestion字段,如果它是一个可执行的代码片段(如"suggestion": "if len(data) == 0 { return errors.New(\"empty data\") }"),它会自动生成一个Suggested Fix的 GitHub comment,开发者一键Apply即可。如果 suggestion 是描述性的(如“应添加输入校验”),它会结合 AST 上下文,生成一个最小化的、语法正确的代码补丁,并附上diff -u格式,方便开发者 copy-paste。
注意:
postprocess阶段的代码,是整个项目里被修改最频繁的部分。因为不同团队的协作习惯、平台规范、甚至代码风格(如 error handling 是用errors.New还是fmt.Errorf)都不同。它不是一个通用模块,而是一个需要根据团队文化定制的“胶水层”。我建议把它做成一个可配置的插件系统,核心逻辑不变,但 formatter、fusion rule、fix generator 都支持外部注入。
4. 实操过程:从零部署一个可审计、可复现的 open-code-review 流水线
4.1 环境准备与依赖安装:轻量级,但要求精确
open-code-review 的设计哲学是“最小可行依赖”,它不捆绑任何重量级框架。但正因如此,对基础环境的要求反而更精确。我推荐在一个干净的 Ubuntu 22.04 LTS 环境中开始,这是 CI/CD 环境最常用的基线。
第一步:安装核心运行时
# 安装 Go 1.21+(流水线主程序用 Go 编写) sudo apt update && sudo apt install -y curl git curl -L https://go.dev/dl/go1.21.6.linux-amd64.tar.gz | sudo tar -C /usr/local -xzf - echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc source ~/.bashrc # 安装 Python 3.10+(用于部分预处理脚本和本地测试) sudo apt install -y python3.10 python3.10-venv python3.10-dev # 安装 tree-sitter CLI(用于 AST 解析) curl -L https://github.com/tree-sitter/tree-sitter/releases/download/v0.22.5/tree-sitter-linux-x64.gz | gunzip > ~/tree-sitter && chmod +x ~/tree-sitter echo 'export PATH="$PATH:$HOME"' >> ~/.bashrc source ~/.bashrc第二步:克隆与构建
git clone https://github.com/oss-review-toolkit/open-code-review.git cd open-code-review make build # 这会编译出 ./bin/ocr-cli 可执行文件make build背后是go build,它会自动下载所有 Go module 依赖。注意,它不会安装任何 Python 包,因为 Python 脚本(如preprocess/diff_parser.py)只在本地开发调试时用,生产环境的preprocess阶段是用 Go 重写的高效版本。
第三步:初始化配置
# 生成默认配置 ./bin/ocr-cli init --output config.yaml生成的config.yaml是整个流水线的“宪法”,它定义了 stages、LLM endpoint、认证方式等。你需要编辑它,至少配置以下三项:
llm.endpoint: 你的 LLM API 地址,如https://api.anthropic.com/v1/messagesllm.api_key: 对应的 API Key(建议用环境变量注入,而非硬编码)git.repo_url: 你要审查的仓库 URL(用于历史查询)
实操心得:不要在
config.yaml里写死api_key!生产环境务必使用环境变量OCR_LLM_API_KEY。我见过太多团队因为配置文件泄露,导致 API Key 被盗刷。ocr-cli会自动读取OCR_*前缀的环境变量,覆盖配置文件中的同名字段。这是一个微小但至关重要的安全实践。
4.2 本地验证流水线:用一个真实 PR diff 快速确认端到端通路
在接入 CI 之前,必须先在本地跑通一次完整的流水线。找一个你熟悉的、有明确问题的 PR diff,是最有效的验证方式。
步骤一:获取测试 diff
# 假设你要测试的 PR 是 github.com/golang/go#12345 # 用 git 命令生成 patch 文件 git fetch origin pull/12345/head:pr-12345 git checkout pr-12345 git diff origin/main > test.diff步骤二:手动触发流水线
# 使用 ocr-cli 的 debug 模式,它会跳过 Git 查询,直接读取 diff 文件 ./bin/ocr-cli run \ --config config.yaml \ --diff-file test.diff \ --debug \ --trace-id "local-test-$(date +%s)"--debug模式会禁用git history查询,只用test.diff作为输入,极大缩短验证时间。--trace-id会为本次运行打上唯一标识,方便你在日志中追踪。
步骤三:观察输出命令会输出详细的 stage 执行日志:
[INFO] Stage 'preprocess': started [INFO] Stage 'preprocess': completed in 124ms, output: {"diff": "...", "ast_context": {...}} [INFO] Stage 'static_analysis': skipped (no semgrep rules configured) [INFO] Stage 'llm_review': calling Claude API... [INFO] Stage 'llm_review': received response, parsing... [INFO] Stage 'llm_review': output validated, confidence: high [INFO] Stage 'postprocess': formatting for GitHub... [SUCCESS] Review completed. Trace ID: local-test-1712345678同时,它会在当前目录生成一个review-report.json,里面是完整的审查结果。打开它,你应该能看到类似这样的结构:
{ "trace_id": "local-test-1712345678", "findings": [ { "issue_type": "CWE-20", "line_number": 15, "description": "Function 'process' does not validate empty input, leading to potential panic.", "suggestion": "if len(data) == 0 { return errors.New(\"empty data\") }", "confidence": "high", "stage": "llm_review" } ], "metrics": { "total_stages": 4, "successful_stages": 4, "llm_latency_ms": 842, "total_tokens_used": 1248 } }如果看到SUCCESS和一个结构完整的 JSON,恭喜,你的流水线已经跑通。如果卡在某个 stage,日志会明确告诉你原因,比如llm_review阶段报错HTTP 401 Unauthorized,那就是 API Key 问题;如果preprocess报错failed to parse AST,那就是 tree-sitter 的 language parser 没装对。
4.3 集成到 GitHub Actions:让审查成为 PR 的“必经之路”
本地验证通过后,下一步是把它变成 PR 的守门人。open-code-review 官方提供了action.yml,但直接使用它,往往达不到“工程化”要求。我推荐一个更可控的集成方式:
创建.github/workflows/code-review.yml
name: AI Code Review on: pull_request: types: [opened, synchronize, reopened] branches: [main, develop] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # 必须,否则无法查询历史 commit - name: Setup Go uses: actions/setup-go@v4 with: go-version: '1.21' - name: Download open-code-review run: | curl -L https://github.com/oss-review-toolkit/open-code-review/releases/download/v0.5.0/ocr-cli-linux-amd64.gz | gunzip > ocr-cli && chmod +x ocr-cli - name: Run AI Review env: OCR_LLM_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | ./ocr-cli run \ --config ./.ocr-config.yaml \ --pr-number ${{ github.event.number }} \ --repo-owner ${{ github.repository_owner }} \ --repo-name ${{ github.event.repository.name }} \ --github-token ${{ secrets.GITHUB_TOKEN }} - name: Post Review Comments if: always() uses: actions/github-script@v7 with: script: | const report = require('./review-report.json'); // 这里写 JS 逻辑,将 report.findings 转成 GitHub API 调用 // 官方 action 已封装好,但自己写更可控关键点解析:
fetch-depth: 0是必须的,因为preprocess需要查询 Git 历史。OCR_LLM_API_KEY从 GitHub Secrets 注入,绝对安全。--pr-number等参数,让ocr-cli能自动拉取 PR 的 diff 和历史,无需手动传文件。- 最后一步
Post Review Comments,我建议不要直接用官方 action,而是自己写github-script。因为官方 action 的评论格式是固定的,而你很可能需要定制:比如只对high置信度的 finding 发评论,或者把medium的 finding 收进 summary comment 而不 inline。
实操心得:第一次上线,务必开启
dry-run模式。在ocr-cli run命令后加--dry-run参数,它会模拟整个流程,但不调用 LLM API,也不发任何评论,只输出review-report.json。你可以用这个报告,组织一次内部评审,确认它的结论是否符合团队预期。我见过太多团队,没做 dry-run,直接上线,结果第一天就给所有 PR 发了 200 条低置信度的噪音评论,严重干扰了开发节奏。工程化,首先是“可控”,而不是“快”。
5. 常见问题与排查技巧实录:那些文档里不会写的“踩坑”现场
5.1 问题:LLM 审查结果“飘忽不定”,同一个 PR,两次运行结论完全不同
现象描述:开发者反馈,上午提交的 PR,AI 给出一个CWE-79XSS 告警;下午 rebase 一下,再提交,告警消失了。团队开始质疑 LLM 的可靠性。
排查思路:这几乎 100% 是输入上下文不一致导致的。LLM 本身是确定性的,但它的输入不是。
根因定位:
- 检查
preprocess阶段的日志,对比两次运行的trace_id,看ast_context是否一致。常见原因是:第一次运行时,tree-sitter的 Go parser 没正确安装,ast_context是空的;第二次运行,parser 装好了,ast_context有了内容,LLM 的推理依据变了。 - 检查
history_context。rebase 操作会改变 commit hash,preprocess查询历史时,可能第一次查到了 3 个相关 commit,第二次只查到 1 个(因为新 commit hash 不在历史中),导致 LLM 的“领域知识”缩水。 - 检查 LLM 的
temperature配置。虽然文档说设为 0.1,但某些 API provider 的 SDK 会忽略这个参数,或者你用了错误的参数名(如temp而非temperature)。
解决方案:
- 在
config.yaml中,为preprocess阶段添加strict_ast_parsing: true,如果 parser 失败,直接报错,不降级。 - 为
history_context设置max_commits: 1,只取最近一次修改,避免因 rebase 导致历史变化。 - 在
llm_review阶段,添加log_input: true,将每次发送给 LLM 的完整 JSON 输入,记录到日志中。这样对比两次运行,就能一眼看出输入差异。
注意:这个问题的本质,不是 LLM 不可靠,而是你的流水线没有做到“输入确定性”。工程化审查的第一课,就是确保输入的每一个字节,都是可复现、可验证的。
5.2 问题:审查流水线在 CI 中超时(timeout=60s),但 LLM API 响应很快(<2s)
现象描述:GitHub Actions 报错The job running on ubuntu-latest has exceeded the maximum time of 60 minutes.,但查看日志,llm_review阶段只花了 1.5s。
排查思路:超时一定发生在某个“安静”的阶段,不是 LLM,而是它的上游或下游。
根因定位:
preprocess阶段的git history查询。CI 环境的网络有时不稳定,git log --oneline -n 100可能卡住。open-code-review默认会查询最近 50 个 commit,如果仓库巨大,这个命令会很慢。postprocess阶段的 GitHub API 调用。ocr-cli在生成评论时,会调用 GitHub REST API 的POST /repos/{owner}/{repo}/pulls/{pull_number}/comments。如果一次 PR 修改了 50 个文件,它要发 50 次 API 请求,而 GitHub 有严格的 rate limit(5000/hour),一旦限流,请求会排队等待,最终超时。static_analysis阶段的 Semgrep 扫描。如果没配置rule_pack,Semgrep 会扫描所有规则,耗时爆炸。
解决方案:
- 在
config.yaml中,为preprocess设置max_history_commits: 10,并添加git_timeout_ms: 5000,超时直接跳过历史查询。 - 在
postprocess阶段,启用批量评论(batch comments)。ocr-cli支持--batch-comments参数,它会把所有 finding 合并成一个 summary comment,而不是每个 finding 一个 comment。这能将 API 调用次数从 N 降到 1。 - 为
static_analysis显式指定rule_pack: "critical-only",只运行高危规则。
实操心得:CI 超时是工程化落地的最大拦路虎。我的经验是,把每个 stage 的耗时,都当成一个 SLA 来管理。在
config.yaml中,为每个 stage 添加timeout_ms字段,并在日志中打印每个 stage 的耗时。上线前,用一个大型 PR 做压力测试,记录 P95/P99 耗时,据此设定合理的 timeout。不要迷信“LLM 很快”,整个流水线的速度,取决于最慢的那个环节。
5.3 问题:审查报告里出现了大量“低价值”告警,比如“变量命名不够清晰”
现象描述:团队抱怨,AI 审查报告里充斥着variable_name_too_generic、function_too_long这类建议,淹没了真正的安全漏洞。
排查思路:这是典型的“信号与噪声”失衡。LLM 的能力是通用的,但你的审查目标应该是聚焦的。
根因定位:
llm_review的 prompt 没有足够强的约束。默认 prompt 可能包含了“代码风格”类的指令,而你的团队只关心“安全”和“可靠性”。postprocess的融合规则没设置权重。`static_analysis