代码评审流程一旦开始由 LLM 参与,开发者首先感受到的不是“评审变强了”,而是“评审这件事被拆开了”。Hacker News 上有一个讨论帖,标题就是 “Ask HN: What happens to code review process when using LLMs?”,问的正是这个问题:当模型能读懂 diff、能写评论、能指 bug 之后,原本属于人类的评审环节还剩多少价值,流程应该怎么重新组织。
这个问题没有标准答案,但过去一年的开源工具和团队实践已经给出几条相对清晰的路径。LLM 最擅长的是在 PR 进入人工评审之前,完成一轮机械性、规格性的初筛:缩进对不对、命名规不规范、有没有明显的空指针、配置项是不是写死了、测试有没有覆盖新增分支。它做不了的事情同样明显:没法真正理解一个跨服务调用的业务正确性,没法判断这次改动是否符合团队很久以前口头约定过的架构约束,也没法为数据安全这种高风险变更拍板。
所以更稳妥的判断是:LLM 不会取消代码评审,而是把评审分层。AI 负责初筛和提示,人类评审员的注意力从“看每一行”转移到“看 AI 筛过之后的重点、风险和我自己的专业知识能覆盖的盲区”。这篇文章就从工程落地的角度,把这件事拆开讲清楚:LLM 代码评审适合什么场景、怎么接入现有 Git 和 CI/CD 流程、怎么验证模型审得准不准、批量审多个文件要怎么设计,以及最容易踩的坑。
1. LLM 代码评审核心能力速览
| 能力项 | 说明 |
|---|---|
| 定位 | 辅助评审,不替代人类决策 |
| 核心能力 | diff 审查、风格检查、常见缺陷检测、测试缺口提示、提交信息把关 |
| 不擅长领域 | 深层架构权衡、跨服务数据流验证、业务正确性、组织隐性规范 |
| 接入方式 | IDE 插件、本地 CLI、CI Action、直接调用模型 API |
| 前置条件 | 可访问的 LLM 服务(云端 API 或本地推理)、Git 仓库、diff 提取能力 |
| 资源成本 | 以 Token 计费;本地模型需考虑显存和推理速度 |
| 适合场景 | PR 初筛、新人代码指导、风格类问题、安全配置提示、跨团队协作时减少低水平往返 |
| 不适合场景 | 关键模块未经人工复核直接自动合并、需要完整理解业务语义的强制把关 |
先给结论:LLM 如果只被当成“自动写评论的机器人”,价值不大;如果被当成“第一轮评审员”,把人类的注意力集中到模型覆盖不了的问题上,价值立刻不一样。
从当前主流实践看,模型能承担得比较好的检查项集中在几类:代码规范与命名、明显的空引用和未捕获异常、资源未关闭、日志打印敏感信息、测试是否覆盖新增分支、配置文件是否存在硬编码。这些检查的共同点是规则相对固定、局部就能判断,不需要依赖整条业务链路。反过来,凡是需要“知道这次改动为什么存在”的问题,比如这个接口为什么设计成这样、性能瓶颈是不是在这里、服务之间的事务边界有没有被破坏,模型在没有充分上下文的情况下只能给出泛泛建议,这类问题还是要靠人。
2. 适用场景与使用边界
2.1 适合谁用
第一类是 PR 量大的中大型团队。每天十几个 PR,人工评审排队,开发等待合并时间变长。用一个 LLM 先跑一轮,把明显问题提前打回去,能显著减少“小问题来回改”的轮次。
第二类是个人开发者和小团队。没有专职 reviewer,代码质量主要靠自觉。LLM 能扮演一个永不疲倦的初评者,至少在提交前帮你检查一遍基本问题。
第三类是规范化要求高的项目,比如对外 SDK、支付相关逻辑、涉及数据导出的模块。这类项目最需要的是稳定一致的检查清单,LLM 配合固定 prompt 可以充当执行标准检查的自动化环节,但最终授权仍然取决于人。
2.2 能解决什么问题
- 缩短评审周期:AI 初筛在前,人工复核在后,减少等待。
- 降低低级错误密度:空指针、未关闭连接、异常吞掉、敏感信息打印这类问题,模型检出率稳定。
- 提升评审反馈质量:模型给出的建议通常带文件位置和修改方向,开发者收到的是可执行的反馈,而不是“感觉这里不太对”。
- 缓解团队评审疲劳:人只需要看模型标出的中高等级问题,而不是逐行通读。
2.3 不适合什么场景
- 不允许代码出内网的场景。把代码明文发送给外部 API 之前,必须确认公司数据政策。没有授权就不要用云端模型处理核心业务代码。
- 需要绝对准确裁决的场景。LLM 的评审结果天然带概率性,误报和漏报都存在,不能作为质量门禁的唯一依据。
- 业务语义强的模块。模型不知道这次 PR 对应的需求背景、用户场景和事故历史,不能代替业务负责人判断“这么做对不对”。
- 大型历史仓库全量扫描。Token 成本和上下文窗口都是硬约束,上来就跑全量扫描通常只会得到一堆噪音。
2.4 合规与安全边界
代码本身是公司资产,里面还可能包含密钥、用户数据处理逻辑、内部架构信息。接外部模型前要过三道检查:第一,数据出境和数据隐私要求是否允许;第二,是否需要对代码做变量名替换、删除注释、局部截取;第三,模型服务商的协议是否写明不利用你的输入做训练。内部部署一个开源模型(通过 Ollama、vLLM、llama.cpp 等方式)是更可控的选择,代价是需要自己管推理资源。
另外要明确,AI 评审建议不构成最终质量结论。涉及人身安全、资金交易、隐私数据的代码,必须由具备资质的工程师复核签字,不能把决策责任转嫁给模型。
3. 接入前置与环境准备
接入 LLM 代码评审不需要特殊硬件,如果你用云端 API,一台普通开发机加一个 Git 仓库就够了。要做的事情分成四块。
第一块是代码仓库。GitHub、GitLab、Gitea 都可以,统一要求能拿到 PR 或 MR 的 diff。Git 本身已经提供了git diff能力,后续脚本基本都建立在“提取两个分支之间的变更内容”这个操作上。
第二块是模型访问方式。推荐先走兼容 OpenAI API 格式的服务,无论你用的是 OpenAI、Azure OpenAI、国内大模型平台还是本地部署的模型,接口路径大多是/chat/completions,带上Authorization头就能调用。环境变量建议统一设置:
export OPENAI_API_KEY="your-api-key" export OPENAI_BASE_URL="https://api.openai.com/v1" export REVIEW_MODEL="gpt-4o-mini"本地推理则常用 Ollama 启动一个兼容接口,模型名按实际拉取的模型填写。需要注意,不同模型对代码评审的指令遵循能力差异很大,小模型容易把“找出问题”理解成“每行都夸一遍”,所以模型选型很关键。
第三块是运行环境。一个简单的 Python 脚本加requests库就能跑通;如果要在 CI 里运行,准备好能执行 shell 命令的 Runner,以及存放密钥的 Secrets 管理。本地验证时建议准备一个测试仓库,专门放几段故意写错的代码。
第四块是评审范围。先在配置文件里明确哪些目录跳过评审,例如vendor、dist、node_modules、生成的 protobuf 代码、锁文件。这些文件要么不是人写的,要么体积大且无评审价值,过滤掉可以节省大量 Token。
4. 把 LLM 接入代码评审流程
4.1 第一级:IDE 内提示
最轻量的接入方式是在 IDE 里安装支持自定义 prompt 的 AI 插件,选中有问题的方法或文件,让模型在提交前先做一轮“自检”。这个方式的优点是没有流程改造,缺点是评审结果不沉淀、不强制,全凭开发者自觉。适合个人使用,不适合团队质量门禁。
4.2 第二级:本地 CLI 评审脚本
在本地提交前手动跑一遍,是最容易实现也能立刻见效的方式。核心逻辑就是两条命令:先用git diff拿到变更,再喂给模型。
git diff origin/main...HEAD > /tmp/change.diff python scripts/review_diff.py /tmp/change.diffreview_diff.py的核心部分如下:
import os import sys import requests def read_diff(path): with open(path, "r", encoding="utf-8") as f: return f.read() def call_review(diff_text): api_key = os.environ["OPENAI_API_KEY"] base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1") model = os.environ.get("REVIEW_MODEL", "gpt-4o-mini") system_prompt = ( "你是一位资深代码评审工程师。请针对下面的 diff 输出评审意见。\n" "要求:\n" "1. 每条意见必须指出文件路径、行号、问题等级(critical/warning/nit)。\n" "2. 只报告确定存在的问题,不要泛泛而谈。\n" "3. 如果没有严重问题,直接回复“未发现明确问题”。\n" ) resp = requests.post( f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json={ "model": model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"请评审以下代码 diff:\n\n{diff_text}"}, ], "temperature": 0.2, }, timeout=180, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] if __name__ == "__main__": diff = read_diff(sys.argv[1]) output = call_review(diff) print(output)这里有两个细节值得注意。第一,temperature要调低,评审类任务希望输出稳定,太高会出现“编造问题”的情况。第二,prompt 里明确要求“只报告确定存在的问题”,否则模型会用“建议考虑优化一下”这类空话填满输出。
这个脚本跑通之后,可以包装成 Git 钩子,在pre-push阶段自动执行。这样每次推送前都会先过一轮 AI 初筛。
4.3 第三级:CI 自动评审
更进一步是把评审放进 Pull Request 流程,每当有人提交 PR,自动触发一次模型评审,并把结果回写到 PR 评论区。这样评审记录会沉淀在 PR 上下文里,后续人工评审可以直接引用。
GitHub Actions 的典型配置如下:
name: llm-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: 提取变更 diff run: | git diff origin/${{ github.event.pull_request.base.ref }}...origin/${{ github.event.pull_request.head.ref }} > /tmp/change.diff wc -l /tmp/change.diff - name: 运行 LLM 评审 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} run: python scripts/review_diff.py /tmp/change.diff跑完之后,评审结果默认只在 CI 日志里显示。如果要自动评论到 PR 上,需要额外一步把脚本输出写入 GitHub API。更简单的办法是选择现成的 AI 评审 Action,它们通常已经实现了评论回写、文件过滤和按严重级别展示。自己写脚本的好处是可控,坏处是要处理评论接口、限流和重试,适合有一定自动化经验的团队。
4.4 本地模型接入方式
如果代码不能出内网,可以用 Ollama 跑一个本地模型,然后让原来的脚本指向本地地址。只需要改一个环境变量:
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1" export REVIEW_MODEL="qwen2.5-coder:7b-instruct"优点是完全不出网,缺点是模型能力上限受硬件约束,评审质量和云端大模型有明显差距。常见的情况是:7B 级别模型能稳定识别空指针和命名问题,但遇到跨文件逻辑、复杂边界条件时基本靠猜。实际占用的显存要按模型量化版本实测,不能只看参数数字。
5. 功能测试与效果验证
接入之后第一件事不是全面上线,而是拿一组历史 PR 做回测。验证维度包括:模型能不能发现真问题、会不会大量误报、输出是不是可执行。下面给出一套通用测试流程。
5.1 缺陷检测测试
准备一段故意写错的代码,观察模型能否给出准确指认。
def get_user_name(user_id): user = load_user(user_id) return user.name # 当 user_id 不存在时,user 可能为 None预期结果:模型应指出user可能为空,并建议增加判空处理,同时给出对应行号。判断标准是意见是否准确、是否包含修改方向。如果模型只输出“请确保代码健壮”,说明 prompt 约束不够,或者模型没有真正理解代码逻辑。
5.2 风格与规范测试
输入一段命名混乱、未使用变量的代码,看模型是否给出风格类意见。这类检查模型通常做得很好,但要注意控制噪音。许多模型会把“变量名可以更好”也列为问题,导致 PR 上出现过多低价值评论。建议在 prompt 里把问题分级,风格类问题统一归到nit,低于warning,让开发者可以选择忽略。
5.3 安全与配置泄漏测试
测试模型能否识别密钥提交、敏感日志输出、SQL 拼接。这部分如果评审结果可靠,价值非常大,因为人工评审很容易漏掉散落在大量文件里的密钥。
def connect(): password = "abc123" conn = mysql.connect(user="root", password=password) logger.info("password is %s", password)预期结果:模型应至少提示两点,一是密码硬编码,二是将密码写入日志。如果模型没有发现密钥问题,可以考虑在 prompt 中补充安全审查专项说明,或者换一个更强的模型。
5.4 大 diff 与多文件测试
拿一个改动十几个文件的 PR 测试。重点观察三点:模型是否忽略部分文件;是否存在上下文窗口截断;意见是否集中在少数几个文件里。大 diff 是本方案最容易翻车的地方。处理思路是不要让一次请求吞掉全部 diff,而是按文件分组,每个文件单独请求,最后汇总。这样既避免超长输入,也方便定位问题属于哪个文件。
5.5 评审质量量化评估
建议建立一张简单的评估表,对每个测试 PR 记录:
| 指标 | 统计方式 | 目标 |
|---|---|---|
| 检出真问题数 | 模型意见中被人工确认有效的问题数 | 越多越好 |
| 误报数 | 模型意见中实际不存在的问题数 | 越少越好 |
| 覆盖率 | 模型检出的问题占全部应发现问题比例 | 结合人工评审统计 |
| 平均耗时 | 从提交 diff 到返回意见的耗时 | 控制在可接受范围 |
| Token 成本 | 每个 PR 评审消耗的输入和输出 Token | 按预算控制 |
这套指标要连续跑一段时间才能有结论。最忌讳的做法是测一个 PR 觉得“挺准”就上线,因为单个样本的偶合性太强。
6. 接口 API 与批量任务设计
6.1 直接调用模型 API
如果你的目标是自建评审服务,而不是依赖现成 Action,核心接口就是模型提供商的/chat/completions。下面是一个 curl 调用示例:
curl -X POST "https://api.openai.com/v1/chat/completions" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [ { "role": "system", "content": "你是资深代码评审工程师。只报告确定存在的问题,输出 JSON 数组,每项包含 file、line、severity、comment。" }, { "role": "user", "content": "请评审以下 diff:\n@@ -1,5 +1,6 @@\n def foo():\n- return None\n+ return load_bar()\n" } ], "temperature": 0.2 }'返回结果里最重要的字段是choices[0].message.content,模型会按要求输出 JSON 或纯文本,取决于 prompt 里怎么约束。
6.2 让模型输出结构化数据
评审建议如果不结构化,没法直接回写 PR、没法做统计分析。推荐在 prompt 里要求输出 JSON 数组,每条意见包含以下字段:
{ "file": "src/main.py", "line": 28, "severity": "warning", "comment": "user 可能为 None,建议加判空处理", "suggestion": "if user is None: raise UserNotFoundError()" }拿到结构化结果后,后续做评论回写、汇总报告、按严重度过滤都很方便。
6.3 批量任务与并发控制
批量审一个 PR 的全部文件时,最简单的方案是串行循环,改造成本低,但耗时随文件数线性增长。更高效的方案是控制并发数量同时处理多个文件。下面是一个并发的批量评审思路:
from concurrent.futures import ThreadPoolExecutor def review_one_file(file_path, file_diff): # 组装该文件的局部 prompt result = call_review(file_diff) return {"file": file_path, "result": result} with ThreadPoolExecutor(max_workers=4) as executor: futures = [ executor.submit(review_one_file, path, diff_text) for path, diff_text in file_diffs.items() ] for future in futures: results.append(future.result())注意三点:并发数过高会触发模型服务限流,需要根据实际接口配额调整;失败的任务要记录并重试,通常采用指数退避;批量任务要写日志,至少记录每个文件耗时、成功失败状态,否则大面积失败时只能瞎猜。
6.4 评审服务的整体流程
一个稍完整的批量评审服务设计如下:
- 接收参数:base 分支、head 分支、可选的目录过滤规则。
- 提取 diff,按文件拆分。
- 过滤无评审价值的文件。
- 并发调用模型,限定 max_workers。
- 解析结构化结果,写入报告文件。
- 将结果回写到 PR 评论区或发送到消息服务。
这个流程已经足够替换掉市面上一些轻量 AI 评审工具的底层逻辑,缺点是维护成本在你这边,优点是模型选择、prompt、成本完全自由。
7. 资源消耗与成本观察
LLM 代码评审的主要资源不是 CPU 和内存,而是 Token。一个 PR 的 diff 会被整体计算为输入 Token,模型的回帖内容是输出 Token。成本估算公式是:
一次评审成本 = (输入Token数 × 输入单价 + 输出Token数 × 输出单价) / 1000具体单价以你选择的模型服务商为准,不同模型差别很大。控制成本的实践有几个方向。
第一,只审真正的代码文件。过滤锁文件、二进制文件、生成的代码和超长测试数据,一次 PR 的输入 Token 能省三分之一甚至更多。
第二,按文件切片而不是整体提交。一个 20 个文件的大 PR,如果整体拼进一次请求,很可能突破上下文窗口或超出单次请求限制。按文件分组后,每次调用都是“局部上下文”,成本稳定,而且某个文件失败不会拖垮整个评审。
第三,用更小、更便宜的模型做常规检查,只对关键模块用大模型二次评审。常见组合是:用速度快的小模型跑风格和明显缺陷,当文件涉及安全、支付、数据导出等目录时,再路由到大模型。
第四,设置 Token 预算上限。在脚本里对每个 PR 的 diff 总行数做限制,超过阈值就只评审改动量最大的前 N 个文件,避免一次异常 PR 产生过高成本。
显存方面,如果走云端 API,本地不需要 GPU。如果本地部署模型,显存取决于模型规模和量化方式,实际占用需要按你的模型版本和推理框架实测。本地 7B 级别量化模型在普通消费级显卡上可以运行,但推理速度对大型 diff 的实时评审来说可能偏慢,更适合离线批量扫描。要观察本地推理资源,可以用nvidia-smi看显存占用,用推理框架自带的请求日志看单次调用耗时。
延迟是另一个要关注的点。云端 API 单个请求通常几秒到几十秒不等,取决于 diff 长度和模型负载。CI 里串行评审会导致 Pipeline 明显变慢,所以大 PR 一定要并发或异步化。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 评审意见全是空话,没有具体问题 | prompt 缺少“只报告确定问题”的约束 | 检查系统提示词和 temperature | 重写 prompt,要求输出文件路径和行号 |
| 明显 bug 没被发现 | 模型只看到局部 diff,缺少函数上下文 | 检查输入中是否包含相关上下文 | 将相邻函数或相关文件片段一并送入 |
| 误报太多,开发者不再看 | 模型能力不足或过度生成 | 统计误报率 | 换更强模型,调低 temperature,提高“存疑不报”的约束 |
| API 返回 401 | API Key 错误或未配置 | 检查环境变量 | 确认 Key 权限和过期时间 |
| API 返回 429 | 请求超过速率限制 | 查看服务商限流文档 | 降低并发数,增加指数退避重试 |
| 大 PR 结果被截断 | 超出上下文窗口 | 查看报错信息和 diff 行数 | 按文件切片、按量截断、只审关键文件 |
| 敏感代码被发送外部 | 没有配置目录过滤和授权评估 | 检查脚本传入的 diff 内容 | 内部部署模型,或做变量脱敏后再发送 |
| CI 评审耗时过长 | 串行调用或模型过大 | 查看每步耗时日志 | 并发调用,小模型跑常规检查,大模型只跑关键文件 |
| 模型返回格式无法解析 | 模型输出不符合 JSON 要求 | 打印原始返回内容 | 增加 JSON 格式示例,用response_format固定输出 |
排查时有一条核心原则:先确认输入是什么,再判断输出为什么不对。把每次请求的 diff 截断内容、模型 raw 输出全部记录到本地日志里,很多问题一眼就能看出来。
9. 最佳实践与使用建议
9.1 固定评审标准
不要每次评审都现场写 prompt。把评审标准沉淀为一份固定的系统提示词模板,包含问题分级、输出格式、必查项(空指针、资源泄漏、密钥硬编码、日志敏感信息、测试覆盖)。团队内部可以像维护规范文档一样维护这份模板。
9.2 分层评审,不要把 AI 当最终裁决
最稳妥的分层方式:模型初筛出所有疑似问题,打上等级;低级问题自动反馈给提交者,提醒修改;中等级问题进入人工评审队列;高级问题必须由人工专门处理,模型意见只作为参考。默认不要做“无人工干预自动 approve”,否则一旦模型漏掉关键问题,责任归属会非常模糊。
9.3 用历史 PR 做回测
上线前至少准备 20 个历史 PR,其中有已知缺陷也有正常改动。让模型对这 20 个 PR 跑一遍评审,对比人工当时发现的问题,计算检出率和误报率,再决定是否扩大范围。这个步骤能筛掉大量“看起来能用但实际全是噪音”的模型和 prompt 组合。
9.4 数据安全与合规
涉及公司核心代码、密钥、客户数据的仓库,必须先确认代码能不能发送到外部模型服务。不能确认的时候,就用内部部署的开源模型,或者对 diff 做脱敏处理:替换字符串字面量、删除注释、只发送结构和逻辑骨架。同时要提醒开发者,模型可能会把代码片段保留在服务端日志里,这本身就是一种数据风险。
9.5 评审记录要沉淀
模型的每一次评审建议都应该记录下来,和 PR 关联。这样后续可以做两件事:一是评估模型每周检出的问题趋势,二是当模型建议和人工判断冲突时,把冲突作为 prompt 迭代和模型升级的依据。没有记录的评审工具只是一个聊天窗口,不是质量体系。
9.6 别让噪音淹没信号
LLM 评审最大的失败模式不是漏报,而是误报。一旦模型反复在 PR 里提“这里建议优化一下”这类无效建议,开发者会养成“AI 评论不看”的习惯,真正有用的建议也会被忽略。宁可设置更保守的 prompt,让模型只报确定的问题,也不要让它刷存在感。
10. 总结与下一步
回到最开始的问题:LLM 进入代码评审流程之后发生了什么?答案是流程从“一个 PR 等一个人看”变成了“一个 PR 先由模型初筛,再由人处理模型筛出的重点”。人类评审的角色上移,从逐行检查变成架构判断、业务正确性把握和最终授权。这个转变不是自动化替代人,而是把人的时间重新分配到价值更高的地方。
最值得先尝试的做法,是写一个类似上文review_diff.py的脚本,把自己近期的历史 PR 拉出来跑一轮回测,看看模型在实际代码里能检出什么。这一步能让你立刻判断这个方案值不值得继续投入。
最容易踩的坑有两个。一个是把模型评价当真理,忽略误报和漏报,直接让它决定代码能不能合并;另一个是不控制成本,把所有文件所有历史一次性灌给模型,Token 账单出来才发现比请人还贵。
如果你的团队已经跑通“AI 初筛 + 人工复核”,下一步可以往两个方向扩展:第一,把评审结果量化成指标,接进质量看板,让“评审检出率”“误报率”“评审等待时长”变成每天可见的数据;第二,把模型评审从“事后提示”变成“提交前建议”,在pre-push阶段就拦住明显问题。时间长了你会发现,真正有价值的不是模型找到的那个 bug,而是流程因为模型介入而重新变得高效这件事本身。