代码审查遇上LLM:流程会变成什么样?人机协同Review工作流落地指南
如果你们团队已经开始用LLM辅助写代码,那一定会遇到一个新问题:代码审查还按原来的流程走吗?以前是开发写完、提交MR、维护者打开Diff逐行看;现在代码可能有一半是AI写的,人工Review到底该盯哪里,AI又能帮忙盯哪里,流程怎么改才不至于让审查变成形式主义。
这次我们聊的就是这个主题。文章从流程重构角度出发,覆盖AI代码审查能做什么、实际落地时怎么接环境、怎么设计人机协同工作流、怎么验证审查效果、怎么接CI自动化,以及最容易踩的坑。适合已经在团队里引入LLM辅助开发的技术负责人、CI/CD工程师和需要重新梳理代码审查流程的开发者阅读。
1. 核心能力速览
LLM不是用来替代人工Review的,而是把代码审查从“纯人工读代码”改成“AI先做一轮机械检查,人做一轮深度判断”。这是流程层面最本质的变化。
| 能力维度 | 传统人工Review | LLM辅助Review | 落地难度 |
|---|---|---|---|
| 格式与风格检查 | 靠人眼和Lint工具 | LLM可结合Lint结果做解释性总结 | 低 |
| 逻辑漏洞排查 | 完全依赖Reviewer经验 | LLM能发现常见空指针、边界条件、错误处理遗漏 | 中 |
| 跨文件上下文理解 | 依赖Reviewer对项目熟悉度 | 长上下文模型可以聚合多个文件Diff | 中 |
| 安全检查 | 靠人工经验和扫描工具 | LLM可标出可疑的注入、硬编码密钥、危险函数 | 中 |
| 自动化程度 | GitLab/GitHub Billable Review | Webhook触发AI Review机器人,自动评论MR | 中高 |
| 批量化 | Review队列靠人排队 | 多个MR并行做首轮AI审查 | 高 |
| 审查一致性 | 不同人标准不同 | 提示词固定后标准相对稳定 | 高 |
| 私有代码合规性 | 不出内网 | API型方案要评估数据外发风险 | 高 |
从表格能看出来,LLM真正解决的是“首轮过滤”和“机械性检查”这两件事。人省出来的精力,正好用在架构合理性、业务语义、扩展性这些LLM不可靠的环节上。
2. 适用场景与使用边界
2.1 适合什么团队
- 代码提交频繁、MR数量多、Reviewer资源紧张的团队。AI先做一轮初筛,人只要看AI标出来的问题和自己关心的部分。
- 正在从单体仓库转向微服务、接口变更频繁的团队。LLM可以把“这个接口改了,调用方是否同步修改”这类跨文件问题找出来。
- 有明确编码规范、希望用提示词把规范固化成Review标准的团队。
- 有CI/CD流程、愿意用Webhook接入自动化机器人的团队。
2.2 不适合什么场景
- 刚起步的小项目,代码量不大、Review成本很低,接入LLM反而增加维护成本。
- 涉及高度敏感业务代码,且无法接受代码片段外发给第三方API的场景。如果非要使用,只能考虑本地私有化部署模型。
- 团队Review主要解决“设计合理性”和“长期演进”问题,纯语法和规范类问题不是痛点,那LLM的价值会被大幅削弱。
- 依赖LLM作为最终把关者且无人复核,这属于风险极高的用法,不建议在正式分支上这么干。
2.3 使用边界与合规提醒
- 使用第三方API审查代码前,必须确认代码是否包含密钥、内部地址、客户信息、未公开业务规则。建议先做脱敏或直接用私有化模型。
- 涉及人脸、隐私、保密协议相关内容不能往外部模型传。代码审查也一样,私有仓库代码不等于可以自由发送到外部服务。
- 需要合法授权。在开源项目中使用AI Review时要确认工具是否符合平台条款和项目许可证要求。
- LLM的审查结果是建议,不是结论。最终合入代码必须有真人Reviewer确认,尤其是高危变更。
3. 环境准备与前置条件
先说明一下,这里我们不绑定某个具体产品,而是给出一套通用的本地接入和调用准备思路。你只需要准备三类东西:代码托管平台的访问能力、一个可调用的LLM服务、一套脚本或机器人逻辑。
3.1 操作系统与工具链
- 操作系统建议Linux或macOS。Windows也可以,但Webhook本地回调调试比较麻烦,最好用内网穿透或直接在CI节点上跑脚本。
- Python 3.9以上。脚本需要
requests、git相关库。 - 代码托管平台需要支持Webhook和API。GitLab、GitHub、Gitea都支持;如果有自建GitLab,集成更灵活。
- 准备一个专用机器人账号,用于提交审查评论,避免用个人账号。
3.2 LLM服务的两种接入方式
| 接入方式 | 优点 | 缺点 |
|---|---|---|
| API型(云端模型) | 部署简单、效果强、无需显卡 | 代码要外发,需确认合规;按调用量计费 |
| 本地私有化部署(开源模型) | 数据不出内网、可控 | 需要GPU资源,显存占用以模型量级和推理参数为准;部署成本高 |
如果选API型,只需要一个API Key和接口地址。如果选本地部署,需要一台有独立显卡的机器,显存建议按模型参数规模评估,实际占用要用nvidia-smi实测。CPU推理能跑但很慢,不适合做高频审查任务。
3.3 磁盘和依赖
- 本地部署模型:模型文件通常几个GB到几十GB,预留充足磁盘空间。
- Python依赖:
requests、PyYAML。如果要做Diff解析,需要git命令行工具。
4. 代码审查流程重构思路
传统流程是“写代码 -> 提交MR -> 人Review -> 修改 -> 合入”。LLM介入后,应该改成下面这样的闭环:
写代码 -> 提交MR -> Webhook触发AI审查 -> AI生成Review评论 -> 开发者处理评论 -> 人工Reviewer复核AI评论和修改 -> 合入这个流程的关键点是:
- AI审查要在人工Review之前执行,作为“首轮过滤器”。
- AI产生的评论要标记为“AI建议”,避免和人工评论混在一起。
- 开发者要先处理AI评论,再进入人工Review环节,否则AI评论就白做了。
- 人工Reviewer负责重点验证AI评论的准确性和遗漏项,而不是从头再看一遍全部代码。
这样流程从“人来过滤所有代码”变成“AI先过滤,人复核重点”,效率提升最大。
5. 功能测试与效果验证
接入LLM审查前,先用一个测试MR验证效果。下面给出一套通用验证流程,不需要绑定具体平台。
5.1 准备测试Diff
假设提交了一个包含明显问题的Python文件:
def get_user(user_id): if user_id is None: return "user not found" conn = db_connect() cursor = conn.cursor() cursor.execute("SELECT * FROM users WHERE id = " + str(user_id)) result = cursor.fetchone() return result这段代码有几个典型问题:user_id为None时返回字符串类型,但正常返回是元组,类型不一致;SQL拼接存在注入风险;连接没有关闭。
5.2 让LLM审查这段代码
把下面的提示词发给LLM服务:
你是一名资深代码审查员。请审查以下代码Diff,只输出存在实际问题的位置,按严重程度排序。 每条评论格式为:文件路径:行号 - 问题描述 - 修改建议。 代码: [paste_diff_here]预期输出:
src/user.py:5 - 类型不一致:返回字符串但正常路径返回tuple,建议统一返回类型或改为抛异常。 src/user.py:7 - SQL注入风险:应使用参数化查询。 src/user.py:3 - 空值处理方式不符合函数语义,建议早退出并抛异常。5.3 判断审查质量的标准
- 问题定位是否准确到行号。
- 是否区分了“阻塞问题”和“建议优化”。
- 是否出现幻觉:比如代码里没有的问题,AI却一本正经地指出来。这类情况要记录并加入提示词负面约束。
- 是否漏掉测试数据里埋的问题。如果漏了,就要调整提示词或增加上下文信息。
5.4 制订回归测试集
找10个已经人工Review过的历史MR,把Diff喂给LLM,对比AI评论和人工评论的重合度。这一步的目标不是让AI完全覆盖人工,而是确认AI能在人工开始前帮忙过滤掉哪几类问题,然后把这几个类型写进提示词。
6. 接口API与自动化集成
代码审查要真正降本增效,必须接入到现有代码托管平台。下面是通用方案,具体接口路径需要按实际项目调整。
6.1 GitLab Webhook触发
在GitLab项目设置里配置Webhook,推送Merge Request事件到你的AI审查服务:
URL: http://your-ai-review-service:8000/webhook Secret Token: your_secret_token事件类型:Merge Request Events。
6.2 用Python写一个审查服务
下面是一个简化的服务模板,实际项目需要替换为自己的接口地址、模型服务地址和鉴权方式。
import hashlib import hmac import json import subprocess import requests from flask import Flask, request, jsonify app = Flask(__name__) GITLAB_URL = "https://gitlab.example.com" GITLAB_TOKEN = "your_gitlab_token" LLM_API_URL = "http://your_llm_service:8000/api/generate" WEBHOOK_SECRET = b"your_secret_token" def verify_signature(payload_body, signature_header): computed = hmac.new(WEBHOOK_SECRET, payload_body, hashlib.sha256).hexdigest() return hmac.compare_digest("sha256=" + computed, signature_header or "") @app.route("/webhook", methods=["POST"]) def handle_webhook(): if not verify_signature(request.data, request.headers.get("X-Hub-Signature-256")): return jsonify({"error": "invalid signature"}), 403 event = request.json if event.get("object_attributes", {}).get("action") in ("open", "update"): project_id = event["project"]["id"] merge_request_iid = event["object_attributes"]["iid"] review(project_id, merge_request_iid) return jsonify({"status": "received"}) def fetch_mr_diff(project_id, merge_request_iid): url = f"{GITLAB_URL}/api/v4/projects/{project_id}/merge_requests/{merge_request_iid}/changes" headers = {"PRIVATE-TOKEN": GITLAB_TOKEN} response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() data = response.json() return data.get("changes", []) def call_llm(diff_text): payload = { "prompt": "你是一名资深代码审查员。请审查以下代码Diff,只输出问题。\n" + diff_text, "max_tokens": 1024, "temperature": 0.2 } response = requests.post(LLM_API_URL, json=payload, timeout=120) response.raise_for_status() return response.json().get("choices", [{}])[0].get("text", "") def post_comment(project_id, merge_request_iid, comment): url = f"{GITLAB_URL}/api/v4/projects/{project_id}/merge_requests/{merge_request_iid}/notes" headers = {"PRIVATE-TOKEN": GITLAB_TOKEN} data = {"body": "[AI Review]\n" + comment} requests.post(url, headers=headers, json=data, timeout=30) def review(project_id, merge_request_iid): changes = fetch_mr_diff(project_id, merge_request_iid) diff_text = "".join(change.get("diff", "") for change in changes) if not diff_text.strip(): return comment = call_llm(diff_text) if comment.strip(): post_comment(project_id, merge_request_iid, comment) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)这个模板直接跑可能跑不通,需要根据你自己的GitLab地址、Token、模型接口返回格式做调整。核心逻辑是通的:Webhook收到事件 -> 拉取Diff -> 拼提示词 -> 调用LLM -> 把评论以机器人身份发回MR。
6.3 GitHub Action方案
如果项目在GitHub上,更简单的做法是写一个Action:
name: ai-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v4 - name: get diff id: diff run: | git fetch origin ${{ github.event.pull_request.head.ref }} git diff origin/main...HEAD > /tmp/diff.txt echo "diff_length=$(wc -l < /tmp/diff.txt)" >> "$GITHUB_OUTPUT" - name: AI Review run: | python review.py /tmp/diff.txt7. 资源占用与性能观察
7.1 API型服务怎么看性能
关键指标是延迟和成本。
- 延迟:一个几百行Diff,LLM生成审查结果通常需要几十秒。如果超过两分钟还没返回,基本可以判定超时或服务不可用,需要在调用层做超时控制和失败重试。
- 成本:按Token计费的服务要考虑单次审查消耗的Token数。Diff越大成本越高,建议只把变更较大的文件拆出来审查,而不是整包发给模型。
7.2 本地部署模型的资源观察
本地部署时,用nvidia-smi观察显存占用:
watch -n 1 nvidia-smi观察要点:
- 启动模型服务时的显存占用。
- 推理过程中的显存波动。
- 并发请求时的显存上限。
要注意的是,显存占用取决于模型参数规模、量化方式、上下文长度和并发数。不要照搬别人的数字,必须在本机实测。如果显存不足,可以降低上下文长度、限制并发请求数、使用量化模型,或者把审查服务设计成串行处理而不是并行。
7.3 如何降低性能开销
- 限制一次审查的文件数量,只审查新增和修改较多的文件,跳过纯删除文件。
- 对Diff做裁剪:去掉大段无意义的格式调整,只保留逻辑变更。
- 增加缓存:相同文件的相同版本只审查一次,避免重复调用。
- 批量任务一定要有队列。GitLab Webhook是并发的,如果没有队列,多个MR同时触发时模型服务可能直接被压垮。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Webhook收到事件但服务没有回应 | 服务未启动、端口不对、密钥验证失败 | 查看服务日志,检查Webhook配置和签名 | 重启服务,确认端口监听和Secret一致 |
| LLM调用返回超时 | 模型服务负载高、Diff过大、网络不通 | 查看模型服务日志,测试单独调用接口 | 减小Diff长度,加长超时时间,限制并发 |
| AI评论质量太差、全是废话 | 提示词没有约束输出格式和范围 | 打印原始提示词,看是否是提示词本身引导了空泛回答 | 用固定格式模板,增加负面约束,如“不要表扬代码” |
| 评论不准确、出现幻觉 | 缺少上下文,模型无法理解项目结构 | 尝试把相关文件内容附进提示词,或使用更大上下文模型 | 限制审查范围,附带相关代码片段 |
| 批量审查任务卡住 | 没有队列,串行阻塞;依赖某个外部服务失败 | 检查任务执行日志,看卡在哪个调用环节 | 加消息队列,对失败任务做重试和告警 |
| 显存不足导致推理中断 | 模型太大、上下文太长、并发太高 | 查看nvidia-smi和日志中的OutOfMemory错误 | 换量化模型、降低并发、限制上下文长度 |
| 评论发不回去,接口报401 | GitLab Token无效或没有MR评论权限 | 用curl手动测试API | 申请有api权限的机器人Token |
9. 最佳实践与使用建议
9.1 先做小范围试点,再推开
不要一上来就把AI审查接到所有仓库。先选一个活跃度适中、Review沉淀较好的项目,跑两周,对比AI评论和人工评论的重合情况,形成一份“AI能查、AI查不了”的清单。这份清单就是后续优化提示词的依据。
9.2 提示词要工程化
不要每次现写。把审查提示词版本化,存到Git仓库,像管理代码一样管理它。
一套基础提示词至少包含:
- 角色定义:资深代码审查员、安全工程师、性能工程师等。
- 审查范围:只看Diff涉及的文件,不扩大范围。
- 输出格式:
文件路径:行号 - 问题 - 建议。 - 禁止项:不要表扬、不要写流水账、不要给出与代码无关的建议。
- 重点项:安全、错误处理、资源释放、类型正确性、跨文件影响。
9.3 分类处理AI评论
建议把AI评论分成三类操作:
- 直接采纳类:格式问题、明显的语法错误、明显的资源泄漏。开发者看完就可以改。
- 需确认类:需要结合业务语义来确定的逻辑问题。开发者要在评论下回复确认逻辑。
- 忽略类:AI幻觉或与业务无关的建议。直接标记为忽略,维护自己的规则清单。
9.4 保护代码隐私和数据安全
- 私有仓库代码是否外发到外部API,要由团队负责人确认。
- 在Webhook服务里做脱敏设计:自动过滤看起来像密钥的字符串、内网IP、邮箱地址,再发给模型。
- 本地部署模型是更稳妥的选择,但只适合有GPU资源的团队。没有GPU就优先选有数据合规承诺的商业API服务。
9.5 批量任务和队列设计
每次MR触发一次AI审查,其实就是一个批量任务队列。建议:
- 用Redis或简单的数据库表做任务队列。
- 每个任务要有状态:pending、processing、done、failed。
- 失败任务自动重试两次,超过重试次数告警给管理员。
- 多个MR同时到达时,任务入队,模型服务只消费队列,避免并发把显存打满。
9.6 注意版权和合规风险
- 使用AI审查代码时,如果模型生成“参考某某开源许可证代码实现”的评论,不要直接照抄,要人工判断是否涉及版权。
- 审查涉及第三方开源代码时,模型输出不能作为许可证合规判断依据。
- 如果你把别人的私有代码发给外部模型,这是数据泄露风险,不是技术问题。
10. 总结与下一步
代码审查流程在LLM介入后,正在从“人海战术”变成“人机分层”:AI处理机械性、重复性、模式化的检查,人处理架构、语义、演进和最终决策。这两者不是替代关系,而是上下层关系。先让AI完成首轮过滤,再让人去复核AI的判断,这个流程基本能覆盖大多数团队的代码审查场景。
如果你想在团队里落地,第一件要做的事不是写复杂的机器人,而是先沉淀一份属于自己的“AI审查测试集”:找10到20个历史MR,让模型只输出问题列表,对比人工评论,看它能命中哪些、漏掉哪些、幻觉哪些。这一步能帮你判断这个方向值不值得继续投入。
比较容易踩的坑有三个:一是把AI评论当人工评论直接合入,出了事没人负责;二是把所有Diff不分大小全发给模型,成本和延迟都失控;三是没有做任务队列,多个MR一并发来直接把模型服务压垮。这三个问题在流程设计阶段就应该提前堵住。
后续扩展方向可以考虑:接入本地大模型实现私有化长期运行;支持更多审查维度,比如性能、安全、可测试性;增加多语言支持;把AI审查结果汇总成统计报表,帮助团队分析历史代码质量趋势。不管选哪条路,代码审查的最终目标没有变:用更少的成本,把问题挡在合入之前。