
最近几个月我一直在折腾 GitHub 上的开源项目PR 多的时候一天能堆十几条靠人肉 review 根本看不过来。后来我试着用 DeepSeek 的 API 给仓库加了 PR 自动 Review让机器先扫一遍代码把低级错误和明显问题挑出来我再集中精力看逻辑层面的事。跑了两周效果超出预期今天把整套思路和踩坑过程完整写出来。这套方案适合两类人一是维护开源项目、PR 流量大但人手不足的 maintainer二是团队想提升 code review 效率、又不想给商业 AI Review 工具付费的技术负责人。只要你懂一点 GitHub Actions 和 Python照着本文流程就能在半小时内给自己的仓库配上自动审查。1. 为什么要在 PR 阶段引入 AI 自动 Review1.1 PR Review 真的值得被自动化吗很多人对“自动 Review”的第一反应是AI 能看懂代码吗能替代人吗我的观点很明确AI 替代不了人但能帮人干掉最耗时的脏活累活。一个典型的 PR review 流程中重复性工作占了很大比例——检查代码风格、发现拼写错误、找出明显的空指针风险、确认资源没有泄漏、判断日志是否打全。这些事情技术含量不高但非常耗费注意力而且人工检查容易漏。尤其是当一个 PR 改了十几个文件、几百行代码的时候reviewer 很难每一行都盯住。AI 恰好擅长这类工作。它不会累不会因为凌晨三点收到 review 请求就烦躁更不会因为改动了业务核心就产生“这应该没问题吧”的侥幸心理。DeepSeek 这类大模型在代码理解上的表现已经相当稳定把 diff 丢给它让它按 reviewer 的视角去挑问题它能给出很具体的反馈甚至能标注出疑似出问题的行号。我建议把自动 Review 定位成“第一道粗筛”而不是“最终判决”。它先把明显问题筛掉让人去关注架构设计、业务正确性、未来扩展性这些 AI 很难替代的部分。这样整个 review 效率能提升一倍以上。1.2 DeepSeek 凭什么适合这个场景选择 DeepSeek 而不是其他模型我主要基于三点考虑。第一是上下文长度和代码理解能力。DeepSeek 的 API 支持的上下文窗口足够大能容纳大部分 PR 的完整 diff。对超过窗口的限制可以通过拆分文件的方式处理后面我会详细讲。在实际测试里它对 Python、JavaScript、TypeScript、Go、Rust 等主流语言的语法和常见坑理解都比较透彻尤其在识别“格式化问题”“未使用变量”“重复代码”这类静态问题上非常准。第二是成本和部署便利性。DeepSeek 的 API 定价相对便宜一次普通规模的 PR 审查token 消耗通常在几千到一万多折算下来的成本几乎可以忽略。它不需要你自己部署推理服务注册账号拿 API Key 就能用这对个人开发者和中小团队来说非常友好。第三是它的国内访问稳定性。这一点很多人可能深有体会某些国外模型 API 在本地网络环境下偶尔会出现连接不稳定、超时等问题而 DeepSeek 的接口在国内访问很顺畅。配合 GitHub Actions 的服务器节点访问整个链路非常稳定我跑了两个星期基本没有因为 API 连接导致任务失败。2. 技术方案选型与整体设计2.1 自建工作流 vs 商业 AI Review 工具市面上其实已经有了不少 AI Code Review 工具比如 CodeRabbit、PR-Agent、OpenCode 等还有一些 CI 平台自带 AI 审查插件。那为什么我还要自己搭一套我从四个维度做了对比看完你就明白了。维度商业 AI Review 工具自建 DeepSeek Actions接入成本注册账号、安装 App 即可需要写 workflow 和脚本约半小时定制能力受平台配置项限制完全可控Prompt、触发规则、输出格式都能改数据隐私代码会发送到第三方平台diff 只发给 DeepSeek API可控性更高成本通常按月订阅或按量计费按 token 计费个人项目几乎忽略不计我并不是说商业工具不好事实上很多商业工具在集成度上做得非常出色。但问题在于商业工具的黑盒属性太强。比如我想让 AI 在评论里只输出中文、只关注安全性问题、忽略格式建议甚至想让它只在 PR 标题包含某个前缀时才触发这些需求在商业工具里往往很难精准配置。而自建方案里这些规则都只是一段 Prompt 或几行 if 语句的事。另外自建还有个隐形优势GitHub Actions 的免费额度对开源项目和私人小仓库来说完全够用。跑一次 review 任务消耗的 Actions 分钟数很少等于说这套自动审查几乎是零成本长期运行。2.2 整体架构一个事件、三个步骤、一条评论整个自动 Review 的架构其实非常简单拆开看就三个环节。第一步是触发。GitHub 的 Webhook 会在 PR 被创建或代码更新时触发 Actions 工作流这一层由 GitHub Actions 的事件监听完成不需要自己搭服务器。第二步是核心处理。工作流会先 checkout 代码计算当前 PR 的 diff然后把 diff 打包成 Prompt调用 DeepSeek 的 chat completions 接口让模型扮演资深代码审查者。第三步是反馈。拿到模型返回的结果后通过 GitHub API 把评论发布到对应的 PR 页面下。整个过程从 PR 提交到评论出现大约两分钟左右。这里有个设计上的关键点为什么要让 Actions 去拉 diff而不是直接把整个仓库发给模型原因很简单token 成本。一个大型仓库可能有几十万行代码全量发送既不经济也容易超过上下文限制。只发送本次改动涉及的 diff既能精准定位审查范围又能大幅降低 token 消耗审查质量反而更高。3. 实操过程从零搭好 PR 自动 Review 流水线3.1 前置准备密钥、权限与仓库配置动手写代码前有三件事需要先准备好。第一件事是 DeepSeek 的 API Key。登录 DeepSeek 开放平台创建一个 API Key这个 Key 后面要存到仓库的 Secrets 里。创建的时候注意把 Key 复制保存好关闭页面后就只能重新生成了。第二件事是配置 GitHub 仓库的 Secrets。进入仓库的 Settings - Secrets and variables - Actions点击 New repository secret创建一个名为DEEPSEEK_API_KEY的密钥把刚才的 API Key 粘贴进去。这一步很关键不要在 workflow 文件里明文写 KeyGitHub 会扫描并告警而且有泄露风险。第三件事是理清权限模型。GitHub Actions 默认使用一个内置的GITHUB_TOKEN这个 Token 的权限范围由 workflow 文件中的permissions字段控制。我们要让机器人给 PR 发评论就需要pull-requests: write权限。顺便说明GITHUB_TOKEN 会自动以github-actions[bot]的身份操作所以评论不会以你的个人账号发出不用怕影响自己的账号风控。3.2 编写 Workflow 触发器与 Job 配置准备工作做完接下来就是核心部分——编写 workflow 文件。在仓库根目录创建.github/workflows/ai-review.yml内容可以直接参考下面这份。name: deepseek-pr-review on: pull_request: types: [opened, synchronize] permissions: contents: read pull-requests: write jobs: ai-review: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 with: fetch-depth: 0 - name: Generate PR diff id: diff run: | BASE_SHA${{ github.event.pull_request.base.sha }} HEAD_SHA${{ github.event.pull_request.head.sha }} git diff $BASE_SHA...$HEAD_SHA pr.diff echo diff_size$(wc -c pr.diff) $GITHUB_OUTPUT echo diff_lines$(wc -l pr.diff) $GITHUB_OUTPUT - name: Run DeepSeek review run: | python3 review.py env: DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} REPO: ${{ github.repository }} DIFF_FILE: pr.diff PR_TITLE: ${{ github.event.pull_request.title }}几个容易踩的坑先说在前面。fetch-depth: 0是必须的如果写成默认值Actions 只会拉取最新一次提交无法获取 base 分支的历史diff 就计算不出来。on.pull_request.types监听opened和synchronize两个事件分别对应“新建 PR”和“PR 代码更新”这样既能审查新 PR也能在作者推新代码时自动更新审查意见。关于git diff的写法用三个点的...和两个点的..区别很大。三个点表示 base 到 head 共同祖先的差异能避免把 base 分支的最新提交也带入 diff更适合 PR 审查场景。实际应用中如果遇到 base 分支快速前进的情况可以先执行git merge-base计算共同祖先但大部分场景下三点写法就够用。3.3 核心脚本实现Diff 提取与模型调用workflow 配好了现在写真正的核心逻辑——review.py。这个脚本负责读 diff、组装 Prompt、调用 DeepSeek API、发评论四件事一步到位。import json import os import urllib.request api_key os.environ[DEEPSEEK_API_KEY] repo os.environ[REPO] pr_number os.environ[PR_NUMBER] diff_file os.environ[DIFF_FILE] pr_title os.environ[PR_TITLE] with open(diff_file, r, encodingutf-8, errorsignore) as f: diff_content f.read() # 如果 diff 太大截取前 20000 字符避免超出模型上下文 if len(diff_content) 20000: diff_content diff_content[:20000] \n\n... (truncated) prompt f你是一位资深代码审查专家请对以下 Pull Request 进行代码审查。 PR 标题{pr_title} 审查要求 1. 重点检查逻辑错误、空指针风险、资源泄漏、安全问题 2. 检查明显的代码质量问题如重复代码、未使用变量、命名不规范 3. 忽略纯格式问题如换行、缩进等 4. 对每个问题给出文件路径、行号如能判断、问题描述和修改建议 5. 如果代码没有问题直接回复本次审查未发现明显问题 6. 使用中文回复保持简洁专业 代码变更如下 diff {diff_content} payload { model: deepseek-chat, messages: [ {role: system, content: 你是一名严谨的代码审查工程师只输出真实存在的问题。}, {role: user, content: prompt}, ], temperature: 0.2, max_tokens: 4000, stream: False, } req urllib.request.Request( https://api.deepseek.com/chat/completions, datajson.dumps(payload).encode(utf-8), headers{ Content-Type: application/json, Authorization: fBearer {api_key}, }, methodPOST, ) try: with urllib.request.urlopen(req, timeout120) as resp: result json.loads(resp.read().decode(utf-8)) review_comment result[choices][0][message][content] except Exception as e: review_comment fDeepSeek API 调用失败错误信息{e} # 发布评论到 PR comment_payload json.dumps({body: review_comment}).encode(utf-8) comment_req urllib.request.Request( fhttps://api.github.com/repos/{repo}/issues/{pr_number}/comments, datacomment_payload, headers{ Content-Type: application/json, Authorization: fBearer {os.environ[GITHUB_TOKEN]}, Accept: application/vnd.githubjson, }, methodPOST, ) try: with urllib.request.urlopen(comment_req, timeout30) as resp: print(Review comment published successfully.) except Exception as e: print(fFailed to publish comment: {e})这段脚本是整套系统的核心我解释几个关键设计。模型参数里temperature设为0.2是为了让输出更稳定、更少发散。代码审查追求的是准确和一致不需要太多创造性温度越低越好。max_tokens设为 4000避免模型输出超长导致回复被截断。deepseek-chat是 DeepSeek 的标准对话模型速度较快响应时间一般在三五秒到十几秒之间比较适合在线审查场景。关于 diff 截断我写了简单的前 20000 字符截取。理论上不截断也能跑但一个几百行的 diff 加上 Prompt 模板可能消耗几万的 token响应时间也会明显变长。截断后模型只能看到前一部分代码这会导致遗漏后半部分的审查。更完善的方案是按文件拆分 diff逐个文件发送审查再汇总结果。这个在第五部分我会详细说。还有一处要注意评论发布用的是 GitHub REST API 的issues/comments端点。PR 本质上也是一个 Issue所以往这个端点发评论评论就会出现在 PR 页面的评论区。这种方式简单可靠不需要额外的依赖库。当然如果你想做成 inline comment也就是直接标注到某一行代码上那就需要用repos/{owner}/{repo}/pulls/{pull_number}/comments端点并且要提供commit_id、path、line等参数实现复杂度会高一些但审查体验会更好。4. 核心环节调优Prompt、成本与评论体验4.1 Prompt 设计让模型知道自己是审稿人真实跑下来我最深的体会是审查质量不只取决于模型本身更取决于你怎么跟它描述任务。同一个 diff用“帮我看看这个代码”和用“你是一名严谨的代码审查专家请逐行检查以下变更”得到的回复完全是两个量级。前者会给你一堆泛泛的建议后者会深入分析具体逻辑。我的 Prompt 模板里有几个细节值得展开。第一是明确输出约束。我写“忽略纯格式问题”是为了避免模型把精力花在报缩进、换行这些琐碎事情上。GitHub 的 diff 视图已经能很好地展示格式问题不需要 AI 再重复一遍。相反我突出强调“逻辑错误、空指针风险、资源泄漏、安全问题”这些是人工 review 最容易遗漏、AI 反而更容易发现的点。第二是规定输出形式。要求模型给出“文件路径、行号、问题描述、修改建议”是让它面向实际问题做结构化输出而不是泛泛而谈“建议优化代码结构”。实测下来结构化要求能让模型输出更具体、更有操作性的反馈。第三是角色设立。Prompt 开头用 system message 强调“只输出真实存在的问题”这段额外的约束能有效减少 AI 的“礼貌性废话”。大模型天然倾向说“你的代码整体不错但有以下建议”这在大多数场景下是好事但在自动化 Code Review 场景里会浪费 token也干扰阅读。明确告知只报问题输出会干净很多。4.2 成本控制一个 PR 到底花多少钱直接说结论对我个人项目来说跑一个 PR 的成本大概在 0.01 到 0.03 元人民币基本可以忽略。这个数字是怎么算出来的一个典型的中等规模 PR改动 200 到 400 行代码diff 文本量大约在 2 万字符左右。送入 DeepSeek API 时输入 token 和输出 token 都会计费。按 DeepSeek 当前定价输入部分包含 diff 和 Prompt大约 1 万 token输出部分审查意见平均 1500 token 左右综合成本就是一次千分之几元。但也别掉以轻心。如果你审查一个改动非常大的 PRdiff 超过几万行token 消耗会指数级上升。所以我在脚本里做了 20000 字符的截断并且后续计划按文件拆分。对开源生态来说日常 PR 大多是小步提交这个成本基本可以忽略。还有一点要提醒GitHub Actions 的执行时间也是成本。调用 DeepSeek API 的过程中Actions job 会挂在Run DeepSeek review这一步等待 API 响应。如果模型响应慢整个 job 的时间就会拉长。GitHub 对私有仓库的 Actions 免费额度是每个月 2000 分钟个人项目完全够用。如果你的仓库是 publicActions 分钟数几乎是无限的完全不用考虑这个问题。4.3 让评论发布得优雅一点一开始我用的是最简单的方式每次 PR 有更新就发一条新评论。跑了几次发现评论区会变得很乱第一次提交发一条修复后又发一条两三轮下来整个评论区全被机器人的评论刷屏。这个问题可以通过“先删后发”的策略解决。思路是在发新评论之前先通过 GitHub API 搜索该 PR 上来自github-actions[bot]的评论把它们删除再发新的。这样 PR 页面上永远只有一条最新的审查意见非常清爽。实现也很简单在发评论之前加两步# 查找已有机器人评论 list_url fhttps://api.github.com/repos/{repo}/issues/{pr_number}/comments list_req urllib.request.Request( list_url, headers{ Authorization: fBearer {os.environ[GITHUB_TOKEN]}, Accept: application/vnd.githubjson, }, ) with urllib.request.urlopen(list_req) as resp: comments json.loads(resp.read().decode(utf-8)) for comment in comments: if comment[user][login] github-actions[bot]: delete_url comment[url] delete_req urllib.request.Request( delete_url, headers{ Authorization: fBearer {os.environ[GITHUB_TOKEN]}, Accept: application/vnd.githubjson, }, methodDELETE, ) urllib.request.urlopen(delete_req)删除接口返回 204 表示成功不需要处理响应体。加上这段逻辑后PR 页面看起来就像一个专业机器人在持续维护审查意见不会产生信息噪音。5. 常见问题与排查技巧实录5.1 Workflow 不触发或没有评论我遇到的最常见的问题是PR 创建了但是 Actions 没有跑起来或者跑了但没有评论出现。先排查有没有触发进入仓库的 Actions 标签页看有没有出现对应的 workflow run。如果连 run 都没有多半是 workflow 文件路径不对或者on.pull_request.types配置有问题。确保文件放在.github/workflows/目录下且文件名以.yml结尾。另外GitHub 默认对 fork 过来的 PR 有一些安全限制——第一次贡献者的 PR 不会直接触发 Actions需要 maintainer 手动批准。如果你是在自己的仓库里测试就绕开了这个问题。如果 Actions 跑了但没评论重点看 job 日志。在 workflow run 页面点进具体 job展开Run DeepSeek review步骤查看脚本输出。最常见的原因是DEEPSEEK_API_KEY没配置或配置错误脚本会打印“DeepSeek API 调用失败错误信息401 Unauthorized”。另一个常见原因是GITHUB_TOKEN权限不足检查 workflow 文件里的permissions段必须写了pull-requests: write才能发评论。还有一个很隐蔽的坑手动重新触发 workflow 时github.event.pull_request.base.sha在某些场景下可能是空值。解决方法是让脚本先判断环境变量是否存在存在才读取 diff。当然正常流程下不会出现这个问题只有从 Actions 页面点击“Re-run”时才可能遇到知道就好。5.2 API 超时、限流与上下文溢出DeepSeek API 在高峰期偶尔会出现响应慢的情况。我在脚本里把 urlopen 的超时时间设成 120 秒如果你经常遇到超时可以考虑在 workflow 里加一个“重试机制”——比如用 GitHub Actions 内置的continue-on-error或者轮询重试。更简单的方法是用 shell 层重试for i in 1 2 3; do python3 review.py break sleep 5 done这个写法会让脚本失败后自动重试最多三次。重试之间有 5 秒间隔给 API 端一点恢复时间。上下文溢出是另一个高频问题。当 diff 太大、模型限制 max_tokens 太小或者单个文件的 diff 特别长时API 会返回类似maximum context length exceeded的错误。我目前的截断方案能缓解但会丢失后半部分内容。更科学的方案是按文件拆分 diff每次审查一个文件然后把结果合并。比如用git diff --name-only获取改动的文件列表遍历每个文件调用一次模型最后汇总所有结果再发布。这样即使仓库里有超大文件也能稳定运行代价是 API 调用次数增加但对小型 PR 来说区别不大。5.3 审查结果不理想噪音评论太多这个问题是最需要调的。刚跑通时模型几乎每个 PR 都会找出一堆问题其中不少是“伪问题”——比如它认为某个变量应该换成常量、某段逻辑建议抽象成函数这些主观建议对实际维护者来说并不总是有价值反而会干扰正常 review。控制噪音评论我有三个层层递进的手段。第一是在 Prompt 里加“只报告确定性高的问题”。我后来在 Prompt 里加了一句“必须有明确依据才提出问题推测性建议一律不要输出”。这句话对抑制发散非常有效模型会更谨慎地输出。第二是设置问题置信度门槛。可以在 Prompt 里要求模型在每条问题后面标记置信度等级比如[高]、[中]、[低]然后我们在脚本里过滤掉[低]置信度的内容。这个方法比单纯靠 Prompt 更可控因为你可以精确控制什么级别的内容上墙。第三是维护一个“已知问题忽略列表”。某些项目有特定的代码风格或临时性解决方案AI 可能不熟悉而误报。可以在脚本里加载一个ignore_patterns.txt文件里面写一些关键词如果模型输出包含这些关键词就直接跳过。这个方案在实战中很实用但维护需要一点成本适合长期使用同一套审查体系的团队。6. 踩坑总结与后续还能怎么玩6.1 我会在下一版里改掉哪些问题跑了两周我总结了几个要在下一版重点改进的点。第一个是 diff 获取的稳定性。目前依赖github.event.pull_request.base.sha在某些边界场景下比如 force push 后 PR 的 base 发生变化可能拿到旧的 sha。更稳的方案是让 Actions 在 checkout 时拉取完整的两个分支然后用git merge-base动态计算 diff 范围和结果。这个改动量不大但对长期运行的仓库来说非常值得。第二个是增量审查。现在的流程是 PR 每次更新都全量重新审查整个 diff如果代码更新很频繁每次都全量跑既浪费 token 也浪费时间。理想状态是记录上一次审查的 head sha只 review 新增的 diff。这个改动相对复杂需要持久化一个状态文件我打算后续用 Actions 的 cache 功能实现。第三个是支持 inline comment。直接在代码行上标注问题比整段评论在可读性上要好很多。虽然实现要复杂一点但 GitHub API 是支持的只是需要精确计算每个 hunk 的 position这个值得花时间研究。6.2 这套链路还能扩展出什么玩法把 DeepSeek 接到 GitHub 自动审查之后我发现整个思路是通用的不只是 PR review 能用。比如你可以做一个“Issue 自动总结机器人”仓库里每次有新 Issue 提交就调用 DeepSeek 生成一段完整的问题描述摘要方便 maintainer 快速了解需求。再比如你可以把 commit message 规范检查接进去让模型在代码提交时自动检查 commit 信息是否符合团队规范不符合就发评论提醒。还可以和本地的 AI 编码工具配合使用——现在很多开发者已经习惯把 DeepSeek 接入 VSCode 或 Codex 等工具做代码补全日常写代码时就能提前规避一部分问题再加上 CI 里的自动审查基本上能做到“写的时候有小助手提交之后有大模型把关”。我现在甚至考虑给线上项目加一个“变更风险分析”能力每次 PR 合入前除了常规 review再让模型对比改动前后的整个函数链路输出一个“影响面分析”。这个对于大型单体项目来说价值非常大相当于每次改动都多了一双眼睛帮你盯着潜在的影响范围。用到今天我最真实的感受是AI 自动 Review 不会让维护者失业也不会让 Code Review 变得可有可无但它确实把最消耗精力的“初筛”环节承包了。我现在每天打开 GitHub先看机器人留下的审查意见把明显的问题让作者改掉剩下的时间只专注看架构和逻辑层面的事工作效率提升非常明显。如果你也在维护仓库、处理大量 PR建议你照着上面的流程试一次半小时的投入带来的收益会很可观。