资讯动态

三步接入AI PR门禁:用GitHub Actions + 大模型卡住合并前最后一次检查

发布时间:2026/10/17 13:19:39 来源:尧图企业网站定制
AI PR 门禁这词听起来唬人拆开就是三件事pull request 到达时自动跑一条 GitHub Actions 流水线把这次改动喂给大模型做代码审查再把模型的结论变成合并分支前必须通过的检查项。我最早接触这个需求是因为团队 reviewer 经常在深夜被拉起来看 PR后来干脆把“是不是有明显低级问题”这件最耗人的事交给机器人人来复核和拍板。这篇文章就按我实际落地的路径写三步把门禁接进 GitHub Actions并把最可能踩到的坑一起列出来。适合在搞研发效能、平台工程或者想给仓库加一道自动化保护层的开发者参考。1. 为啥要把 AI 塞进 PR 流程这个门禁到底在卡什么1.1 没人 review 的 PR问题往往出在最容易被跳过的地方不用门禁的时候质量完全靠人。小团队两三个人还好团队一扩张到十几个review 速度慢、标准不统一、漏检立刻暴露。我自己记忆里最典型的一次是某位维护者在改配置时顺手留了一行带私钥的调试日志负责人夜里 pair review 都过了直到第二天安全平台报警才把密钥轮换掉。人工 review 不是不认真而是注意力天然会被代码的主干逻辑吸引错误处理、危险配置、密钥泄露这类边角项最容易漏。实际协作里还能列出一串痛点规范执行不一致A 同学要求每个函数带注释B 同学认为没必要同一个 PR 会被反复打回夜间和跨时区协作时 reviewer 不在线合并被拖到第二天流程卡死改动某个共享函数时影响到的调用方很难靠肉眼全部看清楚。这些场景里让 AI 在 merge 前先过一遍低层次高风险的问题给人留出时间去看结构、设计和业务正确性是收益最高的分工。1.2 AI 门禁卡什么、不卡什么门禁不是把所有问题都 block要分等级。我会让模型把发现的问题分成四类P0安全风险或明确会导致故障的问题例如硬编码密钥、命令注入、SQL 拼接、错误处理直接被吞掉。这类必须 block。P1明显的功能缺陷例如空指针可能、资源句柄不释放、异步任务没有等待。这类同样 block。P2质量层面的小问题例如重复代码、魔法数字、未使用变量。这类在 PR 评论里提示不卡合并。P3风格与偏好例如命名习惯、格式化差异。这类只作为建议允许人工过滤。门禁判定就是 P0 或 P1 的计数大于 0 就失败否则通过。代码风格这类主观问题如果也交给模型强制会让团队吵起来所以宁可不碰。我经常跟团队说AI 适合当“保洁员”负责把地面上的钉子扫出来不适合当“装修设计师”因为你没法让它理解你们业务里那种微妙的取舍。1.3 这套方案适合谁、不适合谁推荐给已经有 git 仓库、正打算建分支保护规则的团队或者维护开源项目想快速提升 PR 质量的开发者。它不需要自建 bot 服务配置即代码clone 到任何有 Actions 的仓库都能跑。不建议给完全没人 review 的团队当唯一保险。AI 门禁只能兜住模式化问题业务语义、架构取舍、需求合理性它都判断不了。它适合当一个前置过滤网不适合当最终负责人。整个方案定位清楚之后下面这些前置准备要花十分钟认真看很多坑都发生在这一步。2. 开搞之前方案选型与前置准备2.1 我最终用的架构模型 API GitHub Actions 事件触发方案核心就两层。第一层是 GitHub Actions 的 workflow负责监听pull_request事件在每次 PR 推送变化时启动一个任务。第二层是一个轻量审查脚本用代码去拉取 diff、拼 prompt、调用模型服务、再把结论写回 GitHub。为什么不用一个常驻 botActions 的好处是零常驻服务、免费额度里通常够跑、事件和权限模型都是现成的。配置放在.github/workflows下就能版本化谁改了配置一目了然。为什么不用市面上已经封装好的自动 review 插件那些插件通常把判定逻辑写死在平台侧团队想自定义规则、阈值、审批流程会受限。自己写脚本等于把“什么算通过”的决策权完全拿在手里后续可以针对自家代码库不断调整 prompt。2.2 需要准备的材料动工之前先检查这张表能少踩一半坑材料用途说明GitHub 仓库承载 workflow 和脚本私有公有都可以模型服务 API Key调用模型做审查放到 Repository secrets不要明文模型服务接口地址替换不同模型时不需要改代码也放到 secretsGitHub token拉取 PR diff、上报检查状态用 workflow 默认的GITHUB_TOKEN即可这里有一个关键认知门禁的所有判断都在模型服务端但“这个仓库允不允许合并”的最终裁决权在 GitHub 的分支保护规则。脚本负责上报 check 结论分支规则负责拦住人两者缺一个都构不成门禁。很多人第一步就把顺序搞反——先去买模型 API再回来想怎么接其实应该先把仓库侧权限和 Secrets 规划好再研究模型怎么调用。2.3 运行环境与依赖代码我选 Python 3.12不依赖第三方库标准库的urllib.request就能完成 HTTP 调用避免在 runner 上临时拉依赖导致失败。模型服务我建议选上下文窗口大、支持返回 JSON 格式的那种因为需要同时塞下 PR 描述和 diff 文本输出又希望是结构化数据方便脚本解析后做门禁判定。runner 环境就用 GitHub 官方提供的ubuntu-latest跑一个轻量脚本绰绰有余。如果你所在网络环境访问模型服务不稳定记得在脚本外层加超时和重试这个问题后面排查章节还会展开。3. 三步接入 AI PR 门禁每一步都有可复制的文件3.1 第一步把密钥和权限准备好打开仓库Settings - Secrets and variables - Actions新增两个 repository secretMODEL_API_KEY模型服务的访问密钥没有它脚本无法鉴权。MODEL_API_URL模型服务的接口地址不同服务商地址不同单独存是为了换模型时不用改代码。这里要强调一个新手常犯的错secret 不能直接在 workflow 的env里写死要用${{ secrets.XXX }}引用。哪怕仓库是私有的一旦写进 yml 文件能读仓库的人都能看到等于白设密钥。随后确认 workflow 的权限声明。新增文件时GitHub 默认继承仓库的 token 权限但我建议显式声明permissions: contents: read pull-requests: write checks: write这个声明决定了脚本在 API 层能做什么contents: read用来读仓库文件pull-requests: write用来给 PR 写评论checks: write用来上报 check run。后两个权限缺了脚本即使能跑也无法真正变成门禁。权限配好以后第二步写脚本时就可以专心处理逻辑不用再回来看 403 报错。3.2 第二步写审查脚本让模型看懂这次 PR 改了什么先说我踩过的坑不要试图把整个仓库丢给模型。仓库可能几十万行token 成本高、模型上下文也不够而且绝大多数文件和这次改动无关噪音反而会掩盖问题。正确做法是只把下面这些信息喂给模型PR 标题和描述修改过的文件列表每个文件的新增/删除 diff 文本下面这个脚本就是按这个思路写的文件名我放在仓库根目录的scripts/review_pr.py。import json import os import urllib.request import urllib.error GITHUB_API https://api.github.com def get_github_token(): return os.getenv(GITHUB_TOKEN, ).strip() def get_pr_diff(owner, repo, pr_number): url f{GITHUB_API}/repos/{owner}/{repo}/pulls/{pr_number} req urllib.request.Request(url, headers{ Authorization: fBearer {get_github_token()}, Accept: application/vnd.github.diff, User-Agent: ai-pr-review }) try: with urllib.request.urlopen(req, timeout30) as resp: return resp.read().decode(utf-8, errorsreplace) except urllib.error.HTTPError as e: print(ffetch diff failed: {e.code}) raise def call_model(system_prompt, user_payload): api_key os.getenv(MODEL_API_KEY) api_url os.getenv(MODEL_API_URL) body json.dumps({ model: code-review-model, messages: [ {role: system, content: system_prompt}, {role: user, content: user_payload} ], temperature: 0.2, max_tokens: 800, response_format: {type: json_object} }).encode(utf-8) req urllib.request.Request(api_url, databody, headers{ Authorization: fBearer {api_key}, Content-Type: application/json, User-Agent: ai-pr-review }) with urllib.request.urlopen(req, timeout120) as resp: result json.loads(resp.read().decode(utf-8)) content result[choices][0][message][content] return json.loads(content) def report_check(owner, repo, pr_sha, conclusion, summary): url f{GITHUB_API}/repos/{owner}/{repo}/check-runs body json.dumps({ name: AI PR Review, head_sha: pr_sha, status: completed, conclusion: conclusion, output: { title: AI PR Review, summary: summary } }).encode(utf-8) req urllib.request.Request(url, databody, headers{ Authorization: fBearer {get_github_token()}, Accept: application/vnd.githubjson, Content-Type: application/json, User-Agent: ai-pr-review }) with urllib.request.urlopen(req, timeout30): pass def add_comment(owner, repo, pr_number, comment): url f{GITHUB_API}/repos/{owner}/{repo}/issues/{pr_number}/comments body json.dumps({body: comment}).encode(utf-8) req urllib.request.Request(url, databody, headers{ Authorization: fBearer {get_github_token()}, Accept: application/vnd.githubjson, Content-Type: application/json, User-Agent: ai-pr-review }) with urllib.request.urlopen(req, timeout30): pass def main(): owner, repo os.getenv(GITHUB_REPOSITORY).split(/) pr_number os.getenv(PR_NUMBER) pr_sha os.getenv(PR_SHA) diff get_pr_diff(owner, repo, pr_number) system_prompt ( 你是一名代码审查助手。请根据给定的 diff 内容找出 P0 和 P1 级别的问题。 P0 指安全风险或必然故障P1 指明确的功能缺陷。 请严格输出 JSON格式如下 {verdict:pass|fail,issues:[{level:P0|P1,file:...,line:null,reason:...}]} 不要把风格、命名等主观问题列入 issues。如果没有 P0/P1 问题verdict 为 passissues 为空数组。 ) user_payload fPR 标题与描述\n{os.getenv(PR_TITLE)}\n\n{os.getenv(PR_BODY)}\n\nDiff: \n{diff} model_result call_model(system_prompt, user_payload[:30000]) issues model_result.get(issues, []) if model_result.get(verdict) fail or any( i[level] in (P0, P1) for i in issues ): conclusion failure else: conclusion success summary_lines [fAI 发现 {len(issues)} 个需要关注的问题。] for issue in issues: summary_lines.append(f- {issue[level]} {issue[file]}: {issue[reason]}) summary \n.join(summary_lines) report_check(owner, repo, pr_sha, conclusion, summary) if issues: comment \n\n.join([ f### {i[level]} {i[file]} (f:{i[line]} if i.get(line) else ) f\n{i[reason]} for i in issues ]) add_comment(owner, repo, pr_number, AI 审查发现以下问题请处理后提交新的 commit\n\n comment) if __name__ __main__: main()这段脚本的运行顺序很关键先拿 diff再调模型最后把结论写成 check run对应门禁的两个出口。模型输出的 JSON 我用response_format约束成对象规避了“模型给你一段散文脚本解析失败”的尴尬。如果你的模型服务商不支持这个字段直接删掉它同时在 prompt 里加重“只输出 JSON”的说明效果也接近。脚本里有个细节值得解释report_check用的是 check-runs API而不是 commit status。check run 是 GitHub 新一代的检查体系支持分支保护规则直接引用也可以带详细 output。如果团队老项目还在用 status API也能替换但 check run 的展示更清晰PR 页面能直接看到“AI PR Review”这一项。3.3 第三步定义 Actions 工作流让门禁自动跑起来在仓库.github/workflows/ai-pr-review.yml下创建文件name: AI PR Review on: pull_request: types: [opened, synchronize, reopened] permissions: contents: read pull-requests: write checks: write jobs: ai-review: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkoutv4 - name: Set up Python uses: actions/setup-pythonv5 with: python-version: 3.12 - name: Run AI PR Review env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MODEL_API_KEY: ${{ secrets.MODEL_API_KEY }} MODEL_API_URL: ${{ secrets.MODEL_API_URL }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_SHA: ${{ github.event.pull_request.head.sha }} PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} run: | python scripts/review_pr.py事件我选了opened、synchronize、reopened覆盖 PR 创建、代码更新、重新打开三个状态。注意这里没有用pull_request_target。这个事件常被推荐用来给 PR 加标签但它运行在目标分支的上下文里对第三方 fork 的 PR 而言等于在你的仓库里执行外来代码风险很高。做审查门禁拉取 PR 内容让模型看是没有问题的但千万别让工作流去 checkout 并执行 PR 里的代码。最后去仓库Settings - Branches - Add rule把AI PR Review加入 Required status checks。这样分支保护规则才真正生效check 失败时合并按钮会被禁用。没有这一步前两步只能算“AI 评论插件”称不上门禁。这一步配置完整套流程已经能跑通接下来我把自己调参和排查阶段的核心决策拆开讲这部分比跑通更能决定门禁好不好用。4. 关键决策与参数讲解为什么这么配置4.1 为什么只看 diff不看整个仓库这个取舍背后就是成本和准确率的平衡。完整仓库上下文会让模型理解得更充分但代码库一大token 消耗会涨得很快一次 review 很可能需要好几美元。而 PR 审查的核心对象就是 diff改动之外的老代码不该在这次 review 里反复讨论。实际操作里还可以再收敛。比如跳过lock文件、生成文件和二进制文件这些文件既占 token 又没审查价值。我一般是拿 pull requests files API 先过一遍只保留后缀在允许列表里的文件再对 diff 做行数限制。超过 500 行的 diff分批喂给模型或者让模型只做摘要级检查。这个阈值写在脚本常量里每个团队可以根据仓库存量自己调不要照搬。4.2 模型参数怎么调预算怎么算模型调用参数我固定成这套组合temperature0.2控制输出稳定max_tokens800足够覆盖二三十个 issue 的描述response_formatjson_object强制结构化输出。温度设太高会让审查意见忽左忽右review 是给机器判定的场景不需要创造性。成本方面给一个直观感受普通代码文本大约是 4 个字符折算 1 个 token一次 300 行的 PR diff 大概 1 万字符也就是 2500 token 左右。按现在主流模型服务每百万 token 几美元甚至更低的价格一次 review 成本基本在分钱级别远低于人工 review 的机会成本。真正需要注意的不是单价而是频率一个活跃仓库一天几十次 push每次 push 都全量调模型月底账单才吓人。所以要加后面说的去重和限频。4.3 门禁的强制力来自分支保护规则而不是脚本本身脚本里设置conclusionsuccess/failure只是状态上报真正卡住合并的是分支保护规则。很多新手把脚本跑通就算完PR 页面上能看到 “AI PR Review succeeded”但没有人配 required status checks于是它只是个提示。等到出现问题才想起来回去补规则。分支保护规则里把AI PR Review设成 required 后任何绕过它合并的手段都会被 GitHub 拦住。仓库管理员可以强行合并这本身就是你希望保留的“人类 override 通道”。实际使用中我还见过一种更精细的玩法把 P0/P1 严格 blockP2 在评论里提醒但不进 check如果以后再想收严就只改 prompt 里的判定阈值和脚本里的结论逻辑无需动分支规则。5. 落地实测会遇到的坑与排查思路5.1 常见问题对照表我把在多个仓库里真实遇到过的报错整理成一张表按出现频率排序现象常见原因处理方式Actions 任务秒挂日志显示fetch diff failed: 404token 权限不足多半是permissions没写或contents: read缺失对照 3.1 的 permissions 声明补全脚本能跑但 PR 页面没有评论缺少pull-requests: write补权限后重新触发 workflowcheck run 有输出但分支保护不生效没有把AI PR Review加入 required checks或名字拼写不一致到分支规则里重新选择同名 check模型报 401 或 403MODEL_API_KEY没传递到 env或 secret 名拼错检查 workflow env 引用不要直接打印 secret模型输出解析报 JSONDecodeError服务商不支持response_format或 prompt 没约束好删掉 response_format在 system prompt 里强调只输出 JSON每次 push 都触发一次 review成本上涨synchronize事件天然如此未做去重参考 5.2 的经验加去重和限频排查时最有效的办法是先用workflow_dispatch事件手动触发一次把脚本日志完整打出来。Actions 日志里如果看到fetch diff failed: 404多半是 token 权限范围不对先去看permissions而不是怀疑 URL 拼错。5.2 四个我吃过亏才记住的经验第一prompt 一定要做回归测试。把过去三个月的 PR 拉出来让模型跑一遍看它的 P0/P1 判断和人工 review 的重合度。AI 对注入风险、密钥泄露这类模式化问题很敏感但“这条分支少了个兜底判断”这种业务问题经常会漏你要知道它的边界别全信。第二模型报告里要带修复建议不要只报问题。脚本在评论的每个 issue 里加上suggestion字段后开发者处理速度会明显更快。我的 prompt 最后改成每个 issue 必须包含suggestion评论里的每条问题都带上具体改法团队的反馈是“终于不用看完机器人发言自己再去猜要干嘛了”。第三第一次上线别直接 block。先用两周时间把 check 结论设为neutral让团队适应 AI 意见也让你有机会校准误报率。实现上给 workflow 加一个env: REVIEW_MODEobserve脚本里判断如果处于 observe 模式原本的failure结论降级为neutral问题只在评论里出现不卡合并。误报太多会让团队产生“反正都是机器乱说”的信任崩塌这比不加门禁更糟糕。第四成本防抖要尽早加。synchronize事件下每次 push 都会触发 review有人一天 push 十几次模型费和 Action 分钟数就悄悄涨上去了。我加了两层保护一个是最新的 commit 如果和上一次 review 的 SHA 相同就跳过另一个是同一 PR 一小时内只允许跑一次强制审查后续 push 只更新状态不重复调模型。根据我个人体会这套门禁上线后reviewer 被叫起来看 PR 的次数明显变少初版代码里带密钥、裸异常这类低级问题的比例也降下来了。真正值得投入精力的地方不在 workflow 有多炫而在于 prompt 写得越来越贴合团队的实际问题。后续如果想扩展还可以把测试覆盖率、静态扫描结果也接入同一个 check run 的 summary 里做成一个聚合门禁这个方向比单纯调更多模型参数有意义得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑