上周发了个PR,半小时没人审。自己看了一遍,有个SQL注入没发现。合了,上线了,被安全团队找上门了。
——这场景不陌生吧?
代码审查有两个老问题:没人审和审不细。前者是人的问题,后者是精力问题。连续审5个PR,第6个你基本就在走形式了。
我搭了一个AI代码审查机器人,接在GitHub上,每次开PR自动审查,几分钟出结果。Bug、安全漏洞、性能问题、代码风格,四个维度逐个扫描。不取代人工审查,但能把"走形式"那部分接过去。
今天把整个搭建过程拆开讲,包括架构设计、核心代码、踩过的坑,还有跟商业方案的对比。代码开源,你clone下来就能跑。
先说清楚:它能做什么、不能做什么
能做的:
不能做的:
一句话:它是一个不知疲倦的初级审查员,不是资深架构师。
把它当成第一道过滤,人工审查集中在它搞不定的部分。
架构:一条流水线
GitHub PR Event (Webhook / Actions) ↓ 获取 PR Diff (GitHub API) ↓ 解析 Diff → 按文件分组 → 过滤非代码文件 ↓ 逐文件调用 LLM 分析(Bug/安全/性能/可维护性) ↓ 汇总审查结果 → 发布 PR Review Comment
五个步骤,每一步都很明确。重点在中间三步。
第一步:拿到Diff
通过GitHub REST API获取PR的diff。关键细节——请求头要用Accept: application/vnd.github.v3.diff,这样直接返回unified diff文本,不需要处理JSON。
async def get_pr_diff(owner, repo, pr_number): url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}" headers = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff", } async with httpx.AsyncClient() as client: resp = await client.get(url, headers=headers) resp.raise_for_status() return resp.text
为什么用异步?因为后面要调LLM API,同步请求会阻塞。FastAPI + httpx的组合在Python生态里最成熟。
第二步:解析Diff
这是最容易踩坑的一步。unified diff的格式看起来简单,但有几个容易忽略的点——
1. a/ 和 b/ 前缀
git diff的输出,文件路径带a/和b/前缀(--- a/src/main.py / +++ b/src/main.py)。但GitHub API返回的diff有时不带。解析时要注意。
2. 行数不一定对
@@ -1,6 +1,8 @@表示原文件从第1行开始共6行,新文件从第1行开始共8行。但有时候count为0(比如纯新增文件),需要处理边界情况。
3. 大diff会撑爆上下文
一个改了50个文件的PR,diff可能有几万行。LLM的上下文窗口装不下,必须分块处理。
我的方案:按文件分组,过滤非代码文件,超大文件跳过。
# 只审查代码文件 REVIEW_EXTENSIONS = {".py", ".js", ".ts", ".java", ".go", ".rs"} # 超过500行的文件跳过 MAX_DIFF_LINES = 500
过滤非代码文件不是偷懒——审查README.md或package-lock.json的变更没有意义,只会浪费token和产生噪音。
第三步:调用LLM分析
这是核心。prompt设计直接决定审查质量。
我试过三种prompt策略——
策略A:自由审查("请审查这段代码") → 结果:泛泛而谈,说了一堆正确的废话
策略B:Checklist式审查(列出检查项,逐项打分) → 结果:太机械,每个文件都差不多,有用的信息被淹没
策略C:维度引导 + 约束条件(最终方案)
审查维度: 1. Bug风险:逻辑错误、空指针、资源泄露、并发问题 2. 安全问题:SQL注入、XSS、硬编码密钥 3. 性能问题:不必要的循环、N+1查询 4. 可维护性:命名、冗余、魔法数字 约束: - 只指出真正的问题,不要为了审查而审查 - 每个问题给具体行号和修改建议 - 代码没问题就直接说"LGTM"
"只指出真正的问题"这句话很关键。不加这句,LLM会觉得必须找出问题,然后开始编——把正常代码硬说成有Bug。加了之后,审查的精准度明显提升。
另一个关键参数:temperature=0.3。代码审查需要确定性输出,不是创意写作。低温度让LLM更稳定地输出一致的结果。
⚠️ 踩坑提醒:LLM在代码审查上的F1分数并不高。
Meta 2025年发布的CodeReviewEval benchmark显示,GPT-4o在"发现真实缺陷"任务上F1约0.58。也就是说——它会漏掉约40%的真实Bug,同时误报约40%。
所以:AI审查是第一道防线,不是最后一道。
第四步:发布评论
通过GitHub API创建PR Review。这里有个坑——
Review Comment vs Issue Comment:
POST /issues/{n}/comments):评论挂在PR上,不关联代码行POST /pulls/{n}/reviews):正式的代码审查,可以关联具体行用Review Comment,因为它在GitHub的PR界面上显示得更正式,有"Approved" / "Changes Requested" / "Comment"三种状态。我选了"Comment"——只提建议,不block PR。让AI有block PR的权力?那是在给自己找麻烦。
第五步:幂等性
同一PR推了新commit,会触发新的synchronize事件。如果不做处理,之前commit的审查评论还会在。两个选择:
我选了方案1,在评论里加上commit SHA作标识。
简单可靠,不引入状态管理。
两种部署方式
▪ 方式一:GitHub Actions(推荐零运维方案)
# .github/workflows/review.ymlon: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pip install httpx pydantic - run: python scripts/review.py --owner OWNER --repo REPO --pr PR --commit SHA env: OPENAI_API_KEY: {{ secrets.OPENAI_API_KEY }} GITHUB_TOKEN: {{ secrets.GITHUB_TOKEN }}
优点:不需要服务器,GitHub免费额度够用。
缺点:每次审查要冷启动,大概多10-20秒。
▪ 方式二:Webhook服务器(适合自托管)
pip install -e ".[dev]" python -m src.main # FastAPI跑在8080端口
需要在GitHub仓库的Webhook设置里配你的服务器地址。
优点:响应更快,可以加缓存和限流。
缺点:要维护一台服务器。
我的建议:先用Actions跑起来,审查量大再切Webhook。
用哪个模型?
模型 | 效果 | 成本 | 推荐场景 |
|---|---|---|---|
GPT-4o | 全面性好,有时过度建议 | ~$0.01/次 | 不差钱 |
GPT-4o-mini | 够用,偶尔漏检 | ~$0.001/次 | 性价比首选 |
DeepSeek-chat | 跟4o-mini接近 | ~$0.0003/次 | 国内首选 |
本地模型(通过Ollama) | 看模型大小,7B以下别想了 | 免费 | 隐私敏感场景 |
支持任何兼容OpenAI API的模型——改OPENAI_BASE_URL和OPENAI_MODEL两个环境变量就行。
⚠️ 踩坑提醒:别用小模型做代码审查。
7B以下的模型(包括deepseek-r1:1.5b)在代码审查任务上几乎不可用——要么漏检严重,要么把正常代码批成Bug。至少用9B以上的模型,或者直接用API。
跟商业方案比,差在哪?
维度 | 自建(本文方案) | CodeRabbit | GitHub Copilot Review |
|---|---|---|---|
成本 | API费用,几毛钱/次 | 免费层+付费 | GitHub企业版 |
定制度 | 完全自定义prompt和规则 | 有限自定义 | 基本不可自定义 |
隐私 | 代码可以不走公网(本地模型) | 代码经过第三方 | 微软生态 |
维护成本 | 自己维护 | 零 | 零 |
审查质量 | 取决于模型和prompt | 有优化过的prompt | 有GitHub数据加持 |
选哪个?
完整代码
GitHub仓库:https://github.com/helloworldtang/ai-pr-reviewer
clone下来,配好环境变量,跑通7个测试,接上你的仓库。10分钟搞定。
git clone https://github.com/helloworldtang/ai-pr-reviewer cd ai-pr-reviewer cp .env.example .env # 填入你的配置 pip install -e ".[dev]" pytest tests/ -v # 7个测试全过
💡 一句话带走:AI代码审查不是替代人工,是帮你把"走形式"的部分自动化——省下的精力用在真正需要思考的地方。
你们团队做代码审查吗?是人工审还是用工具?最大的痛点是什么?说说你的情况,帮你看看怎么接AI。
参考来源: