尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用 Hermes 实现 GitHub PR 自动化代码评审:从原理到部署实践

用 Hermes 实现 GitHub PR 自动化代码评审:从原理到部署实践 去年年底最后一天我们团队把积压的 27 个 PR 一次性合入主干第二天线上就翻了车。空指针、配置项写错、SQL 忘加索引全是 Code Review 阶段本该拦下来的低级问题Hermes 这套 GitHub PR 自动化代码评审工具也是从那天开始被我正式提上议程的。不是同事不负责是那段时间大家真的看不过来PR 越堆越多每次切换 review 都要重新加载上下文到后来能扫一眼 diff 就算不错了。后来我把 Hermes 接到日常 PR 流水线上让机器先兜底扫一遍明显问题人再集中精力看逻辑review 质量才慢慢救回来。这篇文章不打算写成产品说明书我会按自己真实落地的顺序来梳理先讲为什么需要这样一个机器人再拆解 Hermes 从 Webhook 到发评论的完整链路然后给出一套能跑到生产环境的部署配置最后把上线两个月踩过的坑和调优经验一起放出来。适合团队里 PR 评审压力大、想让机器人先兜底、又不想直接上商业产品的人参考。1. 一个人盯不过来的 PR 流水线为什么需要机器评审1.1 复盘一次让我尴尬的 Code Review那个月我们团队有 87 个未关闭 PR人均要处理 6 到 8 个单个 PR 平均 diff 在 500 行上下。说实话人脑在这种压力下是扛不住的。连续 review 六七个 PR 之后再认真的人也会开始只扫标题、看目录、跳过测试文件甚至直接点 Approve。我自己就在一次 review 里漏掉了一个写得极其隐蔽的密钥硬编码还好上线前被安全扫描拦下不然后果很难收场。那次之后我承认了一个反直觉的结论Code Review 质量不取决于人够不够认真而取决于这套流程有没有兜底机制。高级工程师在连续高负荷 review 下的漏检率并不会比新人好太多只是漏的东西更高级而已。既然人的注意力是稀缺资源那就应该把最耗注意力、最重复的任务拆出去让人只干机器干不了的事。1.2 机器人该管什么不该管什么把评审拆成机器适合做的和人适合做的是落地自动化的第一步。在我现在的配置里适合交给机器人的是这几类密钥、Token、AK/SK 扫描这个必须放到前置门槛不能漏调试残留比如print、console.log、debugger、TODO/FIXME未处理空catch、空except吞异常的行为明显反模式比如eval、exec、rm -rf这种危险调用缺少测试文件、超大 PR 预警、依赖锁定文件被改动但没同步 lock 文件格式、导入顺序、命名风格这些可以通过规则快速判定的内容。不适合让机器人管的也很明确业务逻辑对不对、架构选型是否合理、产品意图是否被正确实现、某些只可意会的团队约定。这些地方机器强行给意见只会制造噪声最后让整个团队对机器人失去信任。我更愿意把 Hermes 定位成一个守门员加陪练它先把低级问题过滤掉人的注意力就留给真正需要判断力的部分。即使完全不用大模型只靠静态规则层也能拦截掉相当一部分低级回归。1.3 Hermes 的定位规则守门员加语义副手我团队内部把 Hermes 分成三层能力第一层是毫秒级静态规则用正则和 AST 做快速扫描第二层是基于大模型的语义评审它像一位资深工程师扫一眼 diff根据上下文提出这里可能有问题第三层是从仓库已合并 PR 里学习项目风格属于可选能力。最初我对语义层很怀疑怕它像某些 AI 助手那样胡言乱语。实际使用下来只要提示词约束得当、输出必须有代码行证据它的确定性高、值得改的意见准确率是可以接受。关键前提是不要让 AI 去判断业务对错只让它做代码味道级别的判断。团队里如果有初中级开发这个语义层非常有价值它可以充当一个从不抱怨、随叫随到的老师。2. Hermes 从收到 Webhook 到发评论的完整链路2.1 为什么用 GitHub App 而非 Personal Token接入 GitHub 的第一步是选择机器人身份。网上很多脚本教程喜欢用 Personal Access Token但我在生产环境强烈建议用 GitHub App原因很直接对比维度GitHub AppPersonal Access Token权限粒度按仓库、按类型细粒度授权账号级权限范围大身份展示独立的 bot 身份评论不会混在个人账号下所有操作都代表你的个人账号凭证时效Installation Token 最长 1 小时自动过期长期有效泄露了很难及时发现适用场景长时间运行的服务端应用个人临时脚本、一次性操作用 GitHub AppHermes 在 PR 页面上会以 bot 身份出现。它申请了什么权限能做什么操作都是显式的。万一私钥泄露也可以在后台一键吊销并重新签发风险可控。个人 Token 虽然配置起来更简单但挂在一个长期运行的服务里等于把你的账号钥匙放在门口脚垫下面风险太大了。2.2 事件接入后的去重、增量与并发控制Webhook 到达之后不能直接一股脑进评审流程第一件要做的事是校验签名。GitHub 会带一个X-Hub-Signature-256请求头用 Webhook Secret 做 HMAC-SHA256 验证。签名对不上直接拒绝这一步能挡掉很多伪造请求。签名通过后进入事件分发。以pull_request事件为例我只看四个 actionopened、synchronize有人新推 commit、reopened、ready_for_review。其它如labeled、assigned、closed一律忽略。接下来是去重和增量设计。GitHub 的synchronize事件在开发者频繁git push --force时会触发得很密集如果每个请求都触发完整评审机器人会被自己搞死。我的做法是用repo pr_number head_sha作为去重索引已经评审过的head_sha直接跳过。同时只对base...head的增量 diff 做评审不重扫整个 PR。换句话说PR 更新了 3 个文件Hermes 就只评审这 3 个文件的变化之前看过的文件不再重复评论。并发控制也很重要。早期我让多个 worker 并行处理同一个仓库的不同 PR结果出现评论顺序错乱、同一条规则重复评论、GitHub API 限流等问题。后来改成同一仓库的任务串行不同仓库并行用 Redis 的分布式锁来控制。这样既不会跨仓库互相拖累又避免同一个仓库内评论打架。2.3 把评审结果回写到 PR 的正确姿势评审完成后回写是门学问。最早我直接用 Issue Comment 接口发一条大评论结果每次 push 都新起一条PR 评论区很快变成机器人刷屏现场。后来改成 GitHub 的 Pull Request Review API一条 review 聚合所有意见并且可以带上 eventCOMMENT只发表意见不通过也不拒绝APPROVE通过REQUEST_CHANGES请求修改会阻塞合并。这个 API 还支持把评论定位到 diff 的具体行非常合适。但有个坑GitHub 要求评论的line必须位于 hunk 的范围内如果模型找到的问题不在 diff hunk 里你就得降级成文件级评论否则接口会报错。还有一个很现实的机制同一个head_sha上只能提交一次 review。意思是如果 Hermes 第一轮给了REQUEST_CHANGES开发者提交新 commit 后head_sha变了Hermes 需要再次触发 review 并把结论更新为APPROVE。这个re-review闭环一定要设计好否则团队会发现改完了机器人还在那儿挂着请求修改非常打击积极性。3. 评审引擎规则、模型、上下文组合出的判断3.1 毫秒级静态规则层评审引擎第一层是静态规则我设计成了一个可配置的规则集。每种规则有自己的严重级别还有ignore_paths和max_file_size这类控制参数。下面是我仓库里一份简化版配置rules: secret_pattern: level: blocker empty_except: level: warning debug_residue: level: warning unsafe_function: level: warning missing_test_on_feature: level: warning ignore_paths: - *.lock - vendor/* - dist/* max_file_size: 600secret_pattern匹配高熵字符串、常见云厂商 AK/SK 格式、私钥块命中直接给 blockerempty_except检测到空异常处理块给 warningdebug_residue找print、console.log、debugger、TODO/FIXMEunsafe_function查eval、exec、innerHTML、rm -rf这些危险调用ignore_paths一定要配好否则 lock 文件、生成的 protobuf、vendor 目录会带来大量假阳性max_file_size超过 600 行的文件跳过语义层只跑静态规则避免模型被超长上下文拖垮。这几个规则的执行成本极低纯正则加 AST 遍历毫秒级完成基本不占用计算资源。实际效果却最值得因为团队里大量低级错误都是这一层拦下来的。3.2 给大模型构造会看 diff 的评审上下文静态规则再强也看不出这个函数改了返回值但调用方没处理新错误这类语义问题。这时候才轮到第二层语义评审。很多人试用 AI 评审后觉得没用是因为直接把git diff塞给通用提示词然后让模型找问题。模型没有方向当然只能给你一堆正确的废话。我给 Hermes 设计的提示词包含这几块内容系统提示你是资深代码评审专家只报告确定性高、能直接依据 diff 片段判断的问题禁止臆测禁止输出没有代码行证据的意见任务信息仓库主语言、PR 标题、PR 描述、变更文件列表代码上下文每个文件的 diff 片段如果文件过大就只取关键 hunk 和相邻函数定义输出格式强制 JSON 数组字段包括文件路径、起始行、严重级别、问题分类、标题、描述、修改建议。还有一个极其关键的约束输出必须是严格 JSON并且每一条意见必须带上它引用的代码片段。解析完 JSON 后如果校验失败宁可丢弃也不入库。这让模型很难糊弄也大幅减少了幻觉式评审。模型推理参数设成temperature0不要让它自由发挥。大 PR 是这层最大的敌人。我一开始把 1500 行的 diff 一次塞进去模型生成时间长得离谱还经常截断。后来改成单次最多处理 200 行 diff 上下文超过就按文件拆分多次请求最后把结果聚合起来。大文件超限就回退到只跑静态规则不硬扛。3.3 仓库历史风格的增量学习可选能力仓库学习层是我后加的。原理很简单拉取最近 20 个已合并 PR 的标题、描述、变更文件路径和当时的 review 意见用统计方法提取高频模式。它能学到一些挺有意思的东西比如我们 Go 项目要求 error 必须 wrap、Python 项目禁止import *、前端代码希望组件拆到单一职责级别。但我要给个提醒这个能力非常依赖样本量。当仓库里合并 PR 少于 50 个时统计出来的团队风格基本都是噪声容易给出奇怪的建议。我的经验是新接入的仓库先关掉这一层等积累了上百个高质量 PR再打开学习能力让它慢慢形成规则库。3.4 评审意见分级与噪声控制好用的评审机器人一定要知道什么话该说、什么话不该说。Hermes 把意见分成四级级别含义默认处理示例blocker必须修改否则会出事故对应 REQUEST_CHANGES密钥泄露、调用明显错误warning强烈建议修改写入 review 评论空 catch、错误被吞掉nit风格类建议默认不写回 PR变量命名、格式调整info提示性信息只进日志不打扰人文件数超限、测试缺失这层的核心是控制噪声。机器人的一句话是人愿意看十句话就开始烦一百句话就会有人申请把它踢出仓库。我把置信度低于阈值的意见全部过滤掉同一条规则重复命中时只汇总一条还支持在 PR 里回复/hermes ignore rule_id临时关闭某个规则。每周导出一次 bot 评论让技术负责人标出可采纳/可忽略再反过来调整规则权重。4. 部署配置从 GitHub App 到第一次成功入手4.1 准备一个最小可跑的环境先说一个最基本的判断如果想让 GitHub 通过 Webhook 稳定呼叫到 Hermes服务必须部署在一个 GitHub 能访问到的公网地址上。开发调试阶段可以用临时公网地址顶一下但正式跑起来还是放到一台固定服务器上更靠谱。硬件配置不用太高2 核 4G 起步就能带一个小团队的使用量。软件方面需要安装 Docker 和 Docker Compose另外要有 Redis。如果你不用 Docker坚持本地起 Python 环境建议用 conda 创建一个 Python 3.11 环境先确认默认软件源能正常拉取依赖否则装包那一步就会消耗你半天耐心。我自己图省事生产环境直接走 Docker本地调试才用 conda。大模型这一层可以接商业 API也可以接自部署模型。两种方式我都试过商业 API 速度快、效果稳但要注意数据不外传的合规要求自部署模型隐私更好但需要至少一张像样的显卡评审质量也更依赖调优。中小团队起步阶段拿商业 API 跑规则加上基础语义评审性价比最高。4.2 注册 GitHub App 的权限和事件配置在 GitHub 后台进入 Settings → Developer settings → GitHub Apps新建一个 App核心配置如下Webhook URL 填https://your-domain/hermes/webhookWebhook secret 用随机生成的强密码后面服务端验证签名要用Permissions 里Pull requests 必须给 Read Write否则评不了 PRContents 给 ReadMetadata 给 Read如果以后要创建检查项再给 Checks WriteSubscribe to events 里必须勾选pull_request建议同时勾pull_request_review和issue_comment。我一度以为默认会订阅pull_request结果创建的 App 默认只选了push第一版上线后完全没反应白白排查了很久。创建后会生成 App ID还要下载私钥.pem文件注意这个私钥只提供一次必须存好。随后把 App 安装到你的组织或指定仓库记录安装后生成的 Installation ID。GitHub App 的认证流程需要先用 App ID 加私钥生成 JWT再用 JWT 换取 Installation Token我贴一段核心代码换成 Python 比较容易理解import jwt import time import requests def get_installation_token(app_id, private_key_path, installation_id): with open(private_key_path, r) as f: private_key f.read() now int(time.time()) payload {iat: now, exp: now 10 * 60, iss: app_id} jwt_token jwt.encode(payload, private_key, algorithmRS256) resp requests.post( fhttps://api.github.com/app/installations/{installation_id}/access_tokens, headers{ Authorization: fBearer {jwt_token}, Accept: application/vnd.githubjson, }, ) return resp.json()[token]这段代码验证了 GitHub App 的核心逻辑实际在 Hermes 里还要加上缓存和过期前自动刷新避免每个请求都走一遍 JWT 流程。4.3 docker-compose 一键拉起服务Hermes 的服务端我拆成了两个进程API 进程只负责接收 Webhook、验签、把任务丢进 Redis 队列然后立刻返回Worker 进程从 Redis 拉任务执行静态规则和模型调用最后回写 GitHub。这样拆分的好处是GitHub 要求 Webhook 在 10 秒内响应如果让重活阻塞在请求里会频繁超时重试。一个简化版的docker-compose.yml长这样services: redis: image: redis:7-alpine restart: unless-stopped api: build: . command: uvicorn hermes.api:app --host 0.0.0.0 --port 8000 environment: - APP_ID${APP_ID} - PRIVATE_KEY_PATH/run/secrets/private-key.pem - WEBHOOK_SECRET${WEBHOOK_SECRET} - REDIS_URLredis://redis:6379/0 - LLM_API_KEY${LLM_API_KEY} - LLM_BASE_URL${LLM_BASE_URL} ports: - 8000:8000 secrets: - private_key worker: build: . command: python -m hermes.worker environment: - APP_ID${APP_ID} - PRIVATE_KEY_PATH/run/secrets/private-key.pem - WEBHOOK_SECRET${WEBHOOK_SECRET} - REDIS_URLredis://redis:6379/0 - LLM_API_KEY${LLM_API_KEY} - LLM_BASE_URL${LLM_BASE_URL} secrets: - private_key secrets: private_key: file: ./private-key.pem这里有个容易犯错的点不要把私钥直接写进环境变量或镜像里用 Docker 的 secrets 机制挂载会更安全。另外 API 和 Worker 必须连同一个 Redis 实例否则 Webhook 进来了Worker 却看不到任务。4.4 用测试 PR 验证全链路部署完成后我建议用一个真实的测试 PR 走一遍全流程不要上来就接生产仓库。操作也简单建一个分支改一个文件提交信息写feat: test hermes然后创建 PR 指向主干。接着按顺序观察几件事GitHub 仓库的 Webhooks 页面里Recent Deliveries 应该显示这次 PR 事件已送达Hermes API 容器日志里出现review task enqueued说明事件被正确接收并入库Worker 日志里出现AI review finished, findings2之类的输出说明模型调用完成打开 PR 页面能看到 bot 账号下的 review 摘要和按行评论。只要这四步都通了主链路就算跑起来了。接下来就是观察真实 PR 的评审效果慢慢调整规则和提示词。5. 真实上线后的故障排查记录5.1 症状一PR 建了Hermes 大门不出二门不迈我第一次接生产仓库时同事发了 PRHermes 一点反应都没有。当时没有直接看日志而是先去排查 Webhook 是否送达在仓库的 Settings → Webhooks → Recent Deliveries 里发现请求根本没到达服务器——指向的是我本地调试地址当然打不通。换成生产地址后发现请求到了但 API 返回 500日志里提示 Webhook 签名校验失败。原因是环境变量里的 Secret 和 GitHub App 配置里的不一致这种低级错误在配置多的时候很容易发生我建议把 Secret 统一放在一个.env文件里维护避免每次手敲出错。再往后还会遇到一种情况Webhook 返回 200事件也显示成功但 Worker 完全没有任务。这时去 GitHub App 配置页复盘发现我只订阅了push事件pull_request根本没勾。如果你的 bot 也静默建议按这个顺序排查Webhook 是否送达 → 签名是否通过 → 事件类型是否订阅 → 任务是否入队 → Worker 是否消费。一层一层来别上来就怀疑代码写错了。5.2 症状二评审结果写不回 GitHub有一段时间静态规则跑得很欢但评论始终写不上去API 报403 Resource not accessible by integration。这基本是权限问题GitHub App 的 Permissions 里Pull requests 只给了 Read没法提交 review。去后台改成 Read Write然后重新保存安装设置已安装的仓库会自动更新权限。还有一类 403 来自 Installation Token 过期。GitHub App 换来的 Installation Token 有效期一小时如果 Worker 启动时缓存了 Token超过一小时还在用GitHub 就会无情地返回 401。解决方法是每次调用 API 前检查 Token 的剩余有效期少于五分钟就重新换取。这个坑的隐蔽之处在于它不总是一开始就报错而是跑到一半开始随机失败日志和时间段完全对不上。5.3 症状三大 PR 让队列积压到报警某个周五一个超大 PR 进来diff 超过 2500 行我眼睁睁看着 Redis 队列从 0 涨到 300 多Worker 一直在跑但 PR 评论就是出不来。一个模型请求要跑几十秒而任务还在不断涌进来整个 Pipeline 瘫痪了。这次问题的直接原因是把整个大 diff 一次性丢给了模型生成时间和上下文长度几乎成正比。后来我做了三个调整第一单次请求最多只带 200 行 diff 上下文超出就按文件拆分第二给每个模型请求设置 45 秒超时超时了只跳过这个文件不影响整个 PR第三把 Worker 并发从 1 提到 4同时保留同一仓库串行的锁避免评论互相打架。这三板斧下来P95 评审时间从 98 秒降到了 22 秒左右队列再也没积压过。5.4 症状四外部 fork 的 PR 全部被静默开源项目少不了外部贡献者但 fork 出来的 PRHermes 全部不评论。最开始我以为是权限问题排查半天最后在日志里看到一行自己写的代码pr.base.repo ! pr.head.repo, skip fork PR。原来是我早期为了省事把所有 fork PR 直接跳过了典型的本想免责结果把功能砍了。想清楚之后处理方式没那么复杂fork PR 的 diff 可以通过 base 仓库的refs/pull/{number}/head读取评论依然写在 base 仓库的 PR 上Permission 沿用 base 仓库的 Pull requests Write 就行。唯一要多留个心眼的是外部代码不可信静态规则对这一类 PR 要更严格模型评审时也要在提示词里注明外部贡献重点关注安全性。6. 两个月的运行数据与调优建议6.1 两个月的数据到底怎么样接入 Hermes 两个月后我们做了一次统计。当然这个数据只代表我所在团队的实际情况不一定对所有团队适用但可以参考指标接入前接入后PR 平均首响时间6 小时左右3 分钟内 bot 出评论PR 合入前平均评审轮次2.3 次1.6 次线上故障中属于 review 漏检的比例每月约 2 次两个月 1 次新人上手写 PR 后的低级错误率偏高明显下降最有价值的变化不是时间缩短而是工程师的注意力被解放了。以前大家打开 PR 要花十分钟扫低级问题现在这些已经由机器人处理完人只需要看逻辑、看业务、看测试覆盖。很多次同事在评论里只留了一句逻辑 OKbot 说的那个点改一下说明机器已经承担了大部分例行工作。6.2 误报率与狼来了效应的平衡机器人最怕的不是漏报是误报太多。一旦团队开始无视机器人评论就是狼来了再准的规则也白搭。我采用的措施是默认只把 blocker 和 warning 写回 PRnit 级别全部只进日志每条 bot 意见必须带上具体的代码行证据没有证据的意见直接丢弃每周把 bot 的输出导出来让技术负责人逐条标记可采纳/可忽略如果某条规则的采纳率长期低于 40%就禁用掉别心软。另外提醒一点团队里的初级工程师对机器人意见往往不假思索地接受这其实是种隐患。我后来在 bot 评论模板里固定加了一行提示如果本条建议与业务场景冲突以人工评审为准。这句话能很大程度避免新人把机器人的建议当作圣旨。6.3 可以继续往前走的几个方向现在 Hermes 在我这边已经稳定运行接下来我想做几件事。第一短期内在 branch protection 里把机器人设成 required reviewer但前提是它连续一个月的误报率低于 5%第二扩展规则库把我们团队自己积累的 review 检查清单逐步翻译成可执行的规则第三给 PR 评论加上交互命令比如/hermes re-review、/hermes ignore让开发者能主动控制机器人的行为第四等仓库积累了足够多的历史评审数据后尝试基于自己团队的数据微调评审模型这是我目前最期待的长期方向。如果你也想在团队里落地类似的机器人我最后的一条建议是先让它当坐班实习生别让它当可以拍板的评审官。每一步调优都用数据说话等到团队真的信任它了再把评审门槛交到它手里。
返回列表