
可能不少团队都有过这种经历PR 一多reviewer 根本看不过来低级错误反复出现——比如把 API Key 打到配置文件里、改动了一处公共函数却忘了同步调用方、格式化风格不统一还要人在评论区来回来回拉扯。我自己被这类问题折磨了大半年最后下决心把 Hermes 接到了 GitHub 的 PR 流程里做成了一个自动化代码评审机器人。这套 Hermes GitHub PR 审查方案不打算替代人类 reviewer而是把耗时、重复、容易遗漏的检查前置到 PR 阶段让人的精力只花在真正需要判断的地方。整套方案核心就三件事用 GitHub Webhook 监听 PR 事件用 Hermes 这套 Agent 框架来编排审查流程再把结果以 Check Run 和 PR 评论的形式回传到仓库。它适合那些已经用 GitHub 托管代码、有分支保护规则、并且想把一部分代码质量拦截自动化的小团队和个人项目。接下来我把整体设计、核心细节、可复现的配置和踩过的坑一次写清楚希望能给你一条能照着走的路。1. Hermes 与 GitHub PR 审查的结合点先搞清楚自动化的边界1.1 Hermes 是什么Agent 框架和普通脚本有什么不同Hermes 是一个开源的智能体框架简单说就是给你一个可以编排模型决策 工具调用 外部系统交互的底座。你不需要自己从头维护一套任务状态机只需要定义 Skill技能、Workflow工作流和 Tool工具Hermes 会按声明好的流程去执行在需要判断的节点把上下文交给模型再把模型返回的意图映射到具体动作上。一开始我也犯过一个思路错误看到 GitHub Actions 能跑脚本就想着直接在 workflow 里写一个 review 脚本把所有逻辑堆在同一个文件里。但真做起来会发现代码评审这件事并不是纯线性的。同样是检查这个 PR每个仓库的规范不一样每个目录的关注点不一样同一个改动在不同上下文里的风险等级也不一样。写死的脚本只能做确定性的检查而 Hermes 这类 Agent 框架的价值在于它把规则和决策拆开了规则负责收集事实模型负责基于事实做判断判断结果再触发下一批动作。这样我们既能把确定性步骤交给工具也能在关键节点接入模型做语义理解。另一个实际好处是可复用。Hermes 的 Skill 可以单独维护和组合比如我定义了一个python-lint-reviewskill另一个secret-scanskill还有一个llm-semantic-reviewskill。新仓库接入时不需要重写一套脚本只要在配置里声明需要加载哪些 skill。这一点在团队仓库越来越多之后价值会非常明显。1.2 自动审查到底审什么分层设定检查范围自动化代码评审最忌讳的就是一把抓。我见过一些方案试图让 AI 一次性看完整个 diff然后输出一堆感觉这里有问题的话结果 reviewer 反而更累。正确做法是把检查分层每一层解决一类问题。第一层是确定性检查。包括语法检查、代码格式化、静态 lint、密钥泄露扫描、依赖版本检查、资源文件变更提示等。这些规则明确、结果稳定完全不需要 AI 参与交给 ESLint、PyLint、Gitleaks 这样的工具跑一遍拿到结果直接结构化。这一层解决的问题是代码是不是干净。第二层是语义级 AI 评审。让大模型看 diff找逻辑漏洞、并发隐患、边界条件处理不到位、公共函数改动后调用方是否受影响等问题。这一层解决的是代码是不是真的正确。但 AI 评审最大的毛病是幻觉和误报所以它不能单独作为唯一的判断来源必须配合第一层的工具结果一起喂给模型让模型基于工具输出做筛选和归纳而不是凭空想象。第三层是动态验证。比如针对特定仓库做 smoke test或者跑一个最小用例集。这一步可以不在每次 PR 都全量执行而是根据改动文件路径做条件触发。比如改了 API 路由就触发接口冒烟测试改了消息队列相关的代码就触发一次发布订阅链路的验证。三层叠在一起审查覆盖面才完整而且每一层的失败都能给出明确的结论来源不会出现AI 说有问题但说不清为什么的情况。1.3 为什么选择 Hermes 而不是直接写 CI 脚本这里要客观一点GitHub Actions 本身完全能做自动化检查我也不是用它替代 Actions。真正让我决定引入 Hermes 的场景是团队对 PR 评审的诉求超出了跑一遍脚本。举个例子我们有一个规则是如果修改了core/目录下的接口定义必须同步更新 API 文档并且在 PR 描述里说明对下游的影响。这个规则在 Actions 里写起来很别扭你得比对文件清单解析 PR 描述文本甚至还得理解改动的语义。而用 Hermes 的话我可以定义成一个 Skill让模型先去读 diff判断改动是否影响接口契约再检查 PR 描述里有没有相应说明最后决定是打success还是failure。换句话说Actions 更适合当某条件满足时执行某命令而 Hermes 更适合基于上下文做判断再决定下一步做什么。当然Hermes 也带来了额外的部署和维护成本所以我不建议所有检查都往里塞。CI 里继续跑构建、跑单元测试、跑 CD 部署这些都是合适的。Hermes 只负责代码评审这一条链路两者通过 Check Run 状态对接最终都汇总到 PR 页面上。2. 核心细节拆解架构、事件驱动与评价标准2.1 整体架构Webhook 入口、任务队列、审查引擎、结果回写整个自动评审服务我拆成了四个部分各司其职。最前面是 Webhook 入口用来接收 GitHub 发过来的事件推送。我用的 FastAPI 写了一个轻量服务监听/webhook路径拿到请求后先校验签名确认来源是 GitHub再把事件信息解析成统一结构丢进任务队列。为什么一定要加任务队列而不是在 Webhook 里直接处理因为 GitHub 的 Webhook 请求有十秒超时限制而代码评审涉及拉取 diff、跑静态工具、调用模型接口动辄几十秒甚至更久。如果同步处理GitHub 端早就超时重试了。队列用 Redis RQ 就够把仓库名、PR 编号、commit SHA、事件类型打包成一个任务由独立的 worker 进程去消费。审查引擎是核心也就是 Hermes 加载 Skill 后真正干活的地方。它拿到任务后会执行一系列步骤调用 GitHub API 获取 PR 的 diff根据仓库配置选择要跑的静态检查工具把工具结果和 diff 一起交给大模型做语义评审最后把全部结果汇总成一份审查报告。回写层负责把报告写回 GitHub。这里我统一走 GitHub App 的安装令牌通过 REST API 创建或更新 Check Run必要的时候往 PR 里发评论。把回写单独分层是为了让应用逻辑和 GitHub 接口解耦后续如果迁移到其他代码托管平台只需要换这一层。2.2 PR 事件到底听哪几个opened、synchronize、reopened、labeledGitHub 的 PR 事件类型很多但真正需要监听的就那么几个。第一个是opened用户刚提 PR 时触发这是我们最基础的入口。第二个是synchronizePR 分支有新提交或强制推送时触发这是后续迭代审核的关键。第三个是reopenedPR 从关闭状态重新打开时触发。第四个是labeled属于可选事件我用来实现手动触发复查——给 PR 打上review-again标签机器人就会重新跑一遍。这里的坑是很多人只监听opened结果 PR 更新之后就再也不会被自动审查保护形同虚设。反过来监听太多事件也有问题比如edited事件PR 标题或者描述一变就触发实际意义不大还会造成大量无意义请求。我最终的监听清单是opened、synchronize、reopened、labeled并且用配置文件里的白名单做二次过滤。还有一个值得单独说的场景draft PR。如果仓库经常有人提草稿 PR机器人去做完整审查其实是浪费资源。我的处理方式是opened事件到达时先判断 PR 的draft字段如果是草稿直接跳过审查等到ready_for_review事件出现再触发。注意ready_for_review并不是opened要单独注册。这个小细节能给你省下大量无谓的模型调用费用。2.3 审查结果的标准设计状态、严重级别、行级评论与总结摘要审查结果的设计直接决定了这个机器人好不好用。我自己定义了一套规范GitHub 端的状态和内部严重级别分开管理。Check Run 的结论状态我按这么几种来用success表示所有检查通过没有阻断问题failure表示存在必须处理的阻断项neutral表示机器人都看过了但没啥强意见比如只发现一些风格建议cancelled用在超时或者任务异常中断的情况。这里要特别强调neutral是个好工具它不会卡合并又能告诉 review 页面机器人来过且看过没发现大问题避免所有提交都显示一个灰色的 pending。严重级别我分成 blocker、warning、nit 三档。blocker 理解为必须修复才能合并比如密钥泄露、明显的数据竞争、会崩线的 NPEwarning 是需要关注但不一定马上改比如性能隐患、异常吞掉、边界条件缺失nit 是锦上添花的建议比如命名更清晰、注释补一句。回传形式我采用总结评论 行级评论的组合。总结评论放在 PR issue 评论区内容是全局结论、问题统计、链接列表。行级评论只对 blocker 和部分 warning 使用并且严格只落在 diff 中出现的新增或修改行上。线上协作时如果每条 nit 都做成行级评论PR 页面会被刷爆reviewer 真正关心的问题反而不明显。这套分级规范我们用了两个月反馈最好的一点是打开 PR 之后不用再刷评论区看机器人总结就够了。3. 实操过程与核心实现3.1 第一步注册 GitHub App 并配置最小权限如果你打算自己搭而不是用现成的 SaaS 服务建议从注册一个 GitHub App 开始。访问 GitHub 的 Settings → Developer settings → GitHub Apps点 New GitHub App填应用名称、Homepage URL、Webhook URL 和 Webhook Secret。Webhook Secret 自己生成一串随机字符串GitHub 用它给请求体签名服务端必须校验。权限这里是最容易搞错的地方。很多人直接用 Personal Access Token然后发现根本创建不了 Check Run因为 Check Run 相关 API 要求以 GitHub App 身份访问。我最终申请的权限如下权限名称访问级别用途Pull requestsRead and write读取 PR 信息、创建行级评论ChecksRead and write创建和更新 Check RunContentsRead-only读取仓库代码与 diff 内容MetadataRead-onlyGitHub App 必需的基础元数据权限注册完成后会生成 App ID还会让下载私钥文件。私钥别提交到代码仓库里后面部署时通过环境变量注入。最后要把 App 安装到目标仓库或组织安装成功后记下 Installation ID后面获取访问令牌要用。为什么坚持用 GitHub App 而不是直接用机器人账号加 Token主要是权限边界。GitHub App 的授权范围是细粒度的可以限定到具体仓库还能通过安装授权机制自动轮换令牌。个人 Token 一旦泄露影响面太大而且 GitHub 也不允许个人 Token 调用部分 Checks API。这一步虽然配置成本略高但从安全和功能角度都值得。3.2 第二步在 Hermes 里定义审查 Skill一个最小可用的 YAMLHermes 的 Skill 定义我建议用 YAML结构清楚也容易做版本管理。一个最小可用的审查 Skill大概长这样name: github-pr-review description: 对 GitHub Pull Request 执行分层代码评审 trigger: event: pull_request types: [opened, synchronize, reopened] filters: draft: false input: repo: ${{ event.repository.full_name }} pr_number: ${{ event.pull_request.number }} commit_sha: ${{ event.pull_request.head.sha }} steps: - id: fetch_diff tool: github.fetch_pull_request_diff params: repo: ${{ input.repo }} pr_number: ${{ input.pr_number }} - id: run_static_checks tool: runner.execute_local_tools params: diff: ${{ steps.fetch_diff.output }} tools: [eslint, gitleaks, tfsec] - id: semantic_review tool: model.chat_completion params: model: deepseek-chat prompt_template: prompts/pr_review.md context: diff: ${{ steps.fetch_diff.output }} static_results: ${{ steps.run_static_checks.output }} - id: write_results tool: github.write_check_run params: repo: ${{ input.repo }} commit_sha: ${{ input.commit_sha }} conclusion: ${{ steps.semantic_review.output.conclusion }} summary: ${{ steps.semantic_review.output.summary }} annotations: ${{ steps.semantic_review.output.annotations }}这个 YAML 的核心思路是把每一步都声明成可独立运行的节点。fetch_diff负责拉取 diffrun_static_checks把 diff 交给本地的 lint 工具执行semantic_review把 diff 和静态结果合并喂给模型最后write_results把结论写回 GitHub。如果某个步骤失败Hermes 会按配置决定是终止整个 Skill 还是走 fallback 分支。开发时有个小技巧先手动跑一次fetch_diff拿到真实 diff 存成文件后续调 prompt 和模型时用这个固定输入能显著减少调试时间。不然每次都从 GitHub 拉数据还要处理签名校验、网络波动很容易分不清是代码问题还是环境问题。3.3 第三步接入模型接口与提示词工程模型接口我用的是大模型统一 API 格式DeepSeek 等厂商都兼容 OpenAI 的接口协议差别只在base_url和 api key。接入的时候把这两个参数通过环境变量注入Hermes 里做一个统一的chat_completion工具封装上层完全不感知具体厂商。真正决定审查质量的是提示词。我的提示词模板长期迭代了很多版有几个核心原则。第一明确角色和任务告诉模型它是什么身份、要做什么、不需要做什么。第二只给它足够的信息把 diff 和静态检查结果作为事实输入第三强制输出 JSON规定字段和格式方便后续解析第四给 few-shot 例子让模型明白什么算 blocker什么算 nit。我用的 prompt 模板核心部分大致是这样的你是一名资深代码评审工程师。以下是某个 Pull Request 的 diff以及静态检查工具的输出。 请只针对新增或修改的行提出真实存在的问题。 不要表扬代码不要泛泛而谈不要输出推测性意见。 请输出 JSON 数组每个元素包含字段 - severity: blocker / warning / nit - line: 问题所在 diff 行号 - message: 一句话说明问题 - suggestion: 具体修改建议 示例 [ { severity: blocker, line: 42, message: 在事务提交前直接返回导致数据库连接未释放, suggestion: 将 return 移出 with 块或使用 try-finally 确保释放 } ]这里最容易被低估的是输出格式约束。早期版本我用自然语言描述模型经常输出大段分析程序没法解析。后来强制它输出 JSON 数组再在代码里做格式校验不合法就重试一次。重试还不行就放弃 AI 评审只回传静态检查结果至少保证流程不挂死。另外一个经验模型很容易把上下文里已有的代码片段重新复述一遍当意见。比如这里的user_id可能为空如果传入的 diff 里本来就有判空逻辑模型还会说一遍。处理办法是在提示词里加一句如果代码已处理该情况不要重复提问。这条简单指令能让误报率下降一半以上。3.4 第四步结果回写策略Check Run 与评论的分工结果回写是决定体验的一步。Check Run 负责状态同步评论负责给人看的扩展信息。我先说 Check Run。创建和更新都走 Checks API需要以 GitHub App 身份拿 token。代码层面相当于这样import requests headers {Authorization: fBearer {token}, Accept: application/vnd.githubjson} payload { name: Hermes Code Review, head_sha: commit_sha, status: completed, conclusion: conclusion, output: { title: Hermes 自动代码评审, summary: summary_text, annotations: annotations, }, } requests.post( fhttps://api.github.com/repos/{repo}/check-runs, headersheaders, jsonpayload, )注意annotations字段是数组每条对应一个行级问题。GitHub 对单个 Check Run 的 annotations 数量有限制如果问题太多我只会把 blocker 和部分 warning 放进去其余写进 summary 里避免超过接口限制。然后是 PR 评论。总结评论我已经在前面讲过这里重点说两个容易踩的细节。第一个是幂等性同一个 PR 如果反复 push机器人每次都产生新评论评论区会炸。我的做法是先用 Search API 查一下有没有已经存在的 Hermes Review Report 评论如果有就 update 掉那条没有才新建。第二个细节是行级评论必须基于 diff hunk 的 position不是文件绝对行号。GitHub API 的position参数指的是 diff 中相对于 hunk 起点的偏移量如果传错接口会直接报错。这个坑我踩了整整一个下午最后是把 diff 解析成 patch 后再逐个 hunk 去匹配目标行号才从根本上解决。3.5 部署形态Docker 和进程管理整套服务我用 Docker 部署分两个容器一个是 Webhook 入口服务一个是 Worker 任务处理服务。两边共用同一份代码和配置只是启动命令不同。环境变量我用.env文件管理主要包括 GitHub App 的APP_ID、私钥路径、Redis 地址、模型接口的base_url和api_key。docker-compose 配置简写如下services: webhook: build: . command: uvicorn app.main:app --host 0.0.0.0 --port 8000 env_file: .env ports: - 8000:8000 worker: build: . command: rq worker hermes-review-queue env_file: .env depends_on: - redis redis: image: redis:7-alpine日志一定要打全。Webhook 收到事件打一行任务开始打一行diff 拉取完成打一行模型调用开始和结束打一行回写结果打一行。后期排查问题时这些日志比什么调试工具都管用。我还加了一个健康检查接口返回当前服务状态和队列积压量挂在监控系统里一旦任务堆积能第一时间发现。4. 常见问题与排查技巧实录4.1 问题一仓库收到了 Webhook服务里却没有日志这个现象最容易让人懵。排查时先看 GitHub App 的 Recent Deliveries里面能看到每次请求的响应码。如果显示 500说明服务端报错去查应用日志如果显示超时说明处理太慢必须异步化。如果根本没有请求记录大概率是 Webhook 地址不可达或者 App 没有正确安装到仓库。还有一个很容易漏的Webhook 的 Content-Type 设置。GitHub 能发application/json或application/x-www-form-urlencoded我建议选 JSON解析方便也方便校验签名。签名校验千万别跳过网上随便一搜就能搜到公网 IP 扫描批量发假 Webhook 的事情不校验等于把仓库暴露给伪造请求。4.2 问题二每次 push 都会产生一批重复评论这个问题的根源在于synchronize事件的触发次数远比你想象的多。有人 force push 一次GitHub 可能连续推几个事件过来。我的解决方案是双重幂等一方面在任务队列里以repo pr_number commit_sha作为唯一任务 ID同一个 commit 只允许一个审查任务另一方面在回写评论时用现有评论查找的方式更新而不是每次都新建。如果你发现即使这样还是会重复多半是查找评论时用的关键字不准确。比如评论里包含的动态 Summary 内容每次不同导致 Search API 查不到旧评论。我在总结评论开头固定加一行!-- hermes-review-key --标记查找时只找这段标记稳定且高效。查不到旧评论就新建查到了就更新正文和结论。4.3 问题三模型一本正经地胡说八道这是所有 AI 评审项目都绕不开的痛点。我见过它把完全正确的并发写法判成死锁也见过它对一个明显的空指针风险视而不见。后来我总结出一个原则模型只能当筛子不能当法官。也就是说模型的任务是从静态工具输出和 diff 中筛选出可能重要的问题再给出解释但到达什么严重级别这件事要结合工具证据来判断。具体操作上我在提示词里要求模型每条意见都必须引用具体代码证据没有证据的意见直接丢弃。同时在回写之前程序还会做一次过滤如果某条意见指向的代码行在 diff 中并不存在就自动降级或者不展示。这样处理后机器人给出的意见基本都能追溯到具体位置即使不准确人也能快速定位不会觉得满屏废话。4.4 问题四Check Run 一直 pending把合并门禁卡死了如果你的仓库开了分支保护并且把 Hermes 的 Check Run 设为 required status check那机器人卡住就意味着谁也合并不了。我遇到过几次模型接口超时任务长时间不结束Check Run 一直 pending团队所有人都在等。这个问题必须提前治理。做法有两条。第一所有任务设置整体超时时间比如 10 分钟一旦超时就把 Check Run 标记为cancelled避免永远 pending。第二在分支保护设置里区分 required 和 optional。我的建议是把静态检查和密钥扫描设为 required把 AI 语义评审标记为 optional这样模型抽风时不会阻塞发布但静态检查出问题则必须修。安全性和协作流畅度都能照顾到。4.5 问题五大模型接口超时导致整个流程失败模型接口超时是家常便饭网络抖动、服务商限流、输入过长都可能触发。我的处理分三个层次。第一层是重试对瞬时错误做三次指数退避重试每次间隔 1 秒、2 秒、4 秒。第二层是分块diff 超过一定长度时按文件拆分分别送模型再把结果合并。第三层是降级如果最终还失败就只回传静态检查结果并给 Check Run 标记neutral在 summary 里注明AI 评审暂不可用。还有一个常被忽略的是 prompt 长度控制。GitHub 上动辄几千行的大 diff 全量塞给模型并不现实而且模型对超长输入的注意力会下降。我后来明确规定只对 ratio 超过 30% 的变更文件做深度 AI 评审其他文件只跑静态检查。这个调整既省 token又明显提升了评审意见的准确率。这套系统上线跑了两个多月我最大体会不是它替我拦下了多少 bug而是团队对 review 这件事的态度变了。以前大家打开 PR 就头疼评论区经常纠缠在风格问题上现在机器人先把流水线式的检查全部做掉人类 reviewer 只需要盯着真正需要判断的点去讨论。如果你也想做类似的改造我的建议是第一版别贪多先跑通静态检查加一个简单的模型总结形成习惯之后再逐步加规则。这套系统的上限其实不取决于模型多聪明而取决于你把代码规范拆得多清楚机器只是把你脑袋里那套标准执行得更稳定、更及时罢了。