news 2026/9/8 18:54:52

Hermes自动化代码评审:基于GitHub PR的智能审查实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hermes自动化代码评审:基于GitHub PR的智能审查实践

作为长期在团队里负责代码评审的人,我太清楚 PR 审查有多磨人了。小到缩进错误、命名不规范,大到并发安全、潜在性能瓶颈,全靠人肉去翻 diff,费时费力不说,眼睛一花还容易漏掉关键问题。后来我自己搭了一个叫 Hermes 的自动化代码评审智能体,专门接在 GitHub 的 PR 流程里。每次有新 PR 或新提交,它会自动拉取变更内容,跑一遍静态检查、自定义规则和 AI 辅助分析,然后把结论以评审评论和 Check Run 状态的形式回写到 PR 页面上。这篇文章就是 Hermes 这个项目的完整复盘,从设计思路到部署细节,再到我实际踩过的坑,都会摊开讲。适合正在被 Code Review 淹没的研发团队、刚接触自动化评审工具的个人开发者,以及想自己搭一套 PR 机器人的人参考。

1. 项目整体设计:Hermes 怎么接入 GitHub PR 审查

1.1 为什么需要自动化代码评审

先说一个真实场景:我们团队有段时间每个 PR 平均改动 300 行以上,涉及 10 多个文件。两个后端维护者每天要评审至少 5 个这样的 PR,光看代码就要花掉大半天,留给设计讨论和业务开发的时间少得可怜。

人工评审还有一个更隐蔽的问题:每个人的关注点不一样。有人只盯命名,有人只看算法,有人关心边界条件,但很少有人在短时间内把所有维度都覆盖到。结果就是,同一个 PR 被多个不同的人反复看,依然会有低级错误漏过去。自动化代码评审不是要替代人,而是先把那些“一眼就能看到”的问题筛掉,让人把精力集中在需要判断力和上下文的地方。

我当时给 Hermes 定的目标很朴素:能在 2 分钟内给出一份有序的评审意见,命中率尽量高,误报能忍,但绝不能打扰人。后来实践证明,只要规则设计得当,这个目标完全可行。

1.2 Hermes 的整体工作流

Hermes 的正常工作流其实不复杂,核心就是“事件驱动”四个字。GitHub 上发生的 PR 相关动作(比如打开、同步、重新请求审查)会以 Webhook 的形式推送到 Hermes 服务端,Hermes 做完校验后拉取数据,分析,写回结果。

步骤动作说明
1接收 WebhookGitHub 推送pull_request事件到 Hermes
2验签用 Webhook Secret 校验请求合法性
3拉取上下文获取 PR 元数据、diff、提交记录
4执行审查按配置规则跑静态检查、自定义规则、AI 分析
5产出结果汇总问题,生成评论文案
6回写创建 Review Comments / Review 摘要 / Check Run
7幂等处理记录本次审查指纹,避免重复评论

这个流程的顺序非常重要。很多人一上来就想着怎么分析代码,忽略了入口的验签和出口的幂等,结果不是被伪造请求打崩,就是同一批评论在 PR 上刷了一屏又一屏。我后面会专门说这些问题。

1.3 为什么选择 GitHub App 而不是个人 Token

实现一个 PR 机器人,通常有两种接入方式:用 GitHub App,或者用个人访问 Token。我在 Hermes 第一版里用的是个人 Token,图省事,后来马上后悔了。

个人 Token 的问题是权限太粗。如果 Token 给了repo权限,等于这个 Token 可以访问所有仓库,一旦泄露,风险非常大。而且个人 Token 没有安装级隔离,审查逻辑无法区分“这个 PR 是我该管的”还是“别的仓库的”。我后来全部切成了 GitHub App。

GitHub App 的好处是权限按安装范围控制,可以只给指定仓库授权,而且每个 App 有独立的私钥和 Webhook Secret,安全性高得多。虽然创建 App 比生成 Token 多几步操作,但长期维护下来非常值得。我个人建议,只要你的自动化评审服务要给团队用,就直接用 GitHub App,不要犹豫。

2. 核心实现:Hermes 审查引擎与规则体系

2.1 获取 PR 元数据与 Diff 内容

Hermes 接受到 Webhook 之后,第一步是从事件负载里拿到基础信息:仓库名、PR 编号、发送者账号、动作为opened还是synchronize。然后调用 GitHub REST API 获取详细数据。

import httpx GITHUB_API = "https://api.github.com" HEADERS = { "Authorization": f"Bearer {HERMES_TOKEN}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", } async def fetch_pr_details(owner: str, repo: str, pr_number: int): async with httpx.AsyncClient(headers=HEADERS) as client: resp = await client.get(f"{GITHUB_API}/repos/{owner}/{repo}/pulls/{pr_number}") resp.raise_for_status() return resp.json() async def fetch_pr_files(owner: str, repo: str, pr_number: int): async with httpx.AsyncClient(headers=HEADERS) as client: resp = await client.get( f"{GITHUB_API}/repos/{owner}/{repo}/pulls/{pr_number}/files", params={"per_page": 100}, ) resp.raise_for_status() return resp.json()

这里有两个注意点。第一,pulls/{pr_number}/files接口默认最多能返回 3000 个文件,超过会被截断,需要处理分页和超长 diff 的情况,我一般会设置一个 max 阈值,超过 200 个文件就直接跳过逐行审查,只做摘要分析,防止把服务拖垮。第二,GitHub API 有速率限制,频繁请求时推荐使用X-GitHub-Api-Version头,并且开启条件请求(If-None-Match),缓存 ETag,能省下大量配额。

2.2 静态检查与自定义规则引擎

拿到 diff 之后,Hermes 会进入规则引擎。规则引擎的设计思路是“配置优先、代码辅助”,尽量让不懂 Python 的团队成员也能自己加规则。

我定义了一套 YAML 规则格式,核心要素有:规则名、匹配目标、正则或脚本、严重级别、建议文案。比如禁止在 commit message 里出现临时标记:

rules: - name: no_debug_puts target: added_lines pattern: '^\s*(puts|print|console\.log)\b' severity: warning message: "请删除调试输出语句,改用 logger 记录关键信息。" - name: no_todo_in_diff target: added_lines pattern: 'TODO' severity: info message: "新增代码里出现 TODO,请确认是否需要在当前 PR 内处理。"

匹配目标我实现了三种:added_lines(只审查新增行)、changed_lines(所有变更行)、file_name(文件名匹配)。为什么要区分added_lineschanged_lines?因为很多团队只看新增代码是否有问题,改动删除行不适合套用新增规则。如果对整段 diff 跑正则,很容易因为删掉的代码也包含危险内容而产生误报。我一开始没区分,导致误报率高到没人信这工具,后来改成只匹配新增行,效果立刻好很多。

规则引擎的执行顺序也很重要。我会先跑低成本的文本正则,再跑文件级别的检查,最后才跑 AI 分析。因为 AI 分析耗时高、费用贵,能用正则解决的绝不动用大模型。

2.3 AI 辅助评审:上下文理解与评论生成

纯正则做不了语义层面的判断,比如“这个函数并发访问没有加锁”“这个数组越界了”。所以 Hermes 在规则引擎之上加了一个 AI 辅助模块,把 diff 内容、相关文件路径、语言类型拼成 Prompt,送到大模型做一次快速初审。

我用的 Prompt 模板大致长这样:

你是一名资深代码评审工程师,请审查以下 Pull Request 的变更内容。 只关注可能导致 Bug、安全隐患、性能问题或可维护性问题的点。 按以下格式输出: [severity] file:line - 问题描述 如果没有问题,输出:NO_ISSUES 变更内容: {file_diff}

这里有一个安全细节必须先处理:不要把整个仓库的代码或敏感的密钥配置直接塞进 Prompt。Hermes 在拼装上下文前会先做一次敏感信息过滤,凡是匹配到AKIABEGIN RSA PRIVATE KEYpassword=等关键词的内容,都会被脱敏替换为[REDACTED]。因为你不知道模型服务方会不会记录请求数据,至少不能主动把秘钥送过去。

AI 输出的结果会被结构化解析,再和规则引擎的结果合并。合并的原则是:规则引擎的结果优先展示,AI 结果降级为“建议”级别,防止模型幻觉造成强误导。我试过直接让 AI 全权做主,结果它一本正经地挑出几个“潜在空指针风险”,实际上都是没问题的代码,团队信赖度瞬间归零。所以 AI 只能当辅助,不能当裁判。

2.4 审查结果回写与状态检查

审查完成后,怎么把结果写回 GitHub,直接决定了同事们的使用体验。Hermes 支持两种回写方式:Review Comments(行级评论)和 Review 摘要(整体评论)。

如果问题足够精确,可以定位到具体文件的某一行,那就用 Review Comments。GitHub 的 API 允许在positionline上评论,但要注意新版本 API 对行号的解释和旧的position模式有区别。我建议优先使用subject_type=line这种新参数,否则行号对不上会很尴尬。

comment_payload = { "commit_id": latest_commit_sha, "path": file_path, "line": target_line, "side": "RIGHT", "body": f"**Hermes ({severity})** {message}", }

如果问题比较分散,Hermes 会在 PR 页面生成一个总评论,按严重级别分组,只列关键问题。同一时间只有一个总结论,避免刷屏。这一步的实现靠一个简单但有效的机制:每次生成结果前,先检查该 PR 是否有hermes-review标签的评论,如果没有则创建,如果有则更新。这样既保留了历史,又不会让评论区失控。

Check Run 是另一个不可忽视的组件。我给 Hermes 配置了一个名为hermes/review的 check,结论有successneutralfailure。当审查中没有发现问题时,返回 Success,有问题时返回 Neutral 而不是 Failure。因为自动评审的结论如果直接阻断合并,会造成大量误杀;Neutral 状态既能展示结果,又不影响主流程。等到规则足够稳定、误报率足够低之后,再考虑把特定规则调到failure

3. 实操部署:从零搭建 Hermes 审查服务

3.1 环境准备与依赖安装

Hermes 本身是一个 Python 异步服务,我在生产环境用的是 FastAPI + Uvicorn,再加上几个关键依赖。这里列一个最小依赖清单:

fastapi==0.115.6 uvicorn[standard]==0.32.1 httpx==0.28.1 pyyaml==6.0.2 cryptography==44.0.0 PyGithub==2.5.0 openai==1.59.3 # 或使用兼容 OpenAI 协议的 SDK

安装很简单,建议用虚拟环境隔离:

mkdir hermes-review && cd hermes-review python3.11 -m venv .venv source .venv/bin/activate pip install -r requirements.txt

依赖里我特意提cryptography,是因为 GitHub App 的私钥通常返回的是 PEM 格式,Python 里解析这种格式做 JWT 签名时很容易踩坑。后面我会讲一个典型的签名坑。

3.2 创建 GitHub App 并配置权限

这一步是关键,很多人卡在这里。先去 GitHub 主页 -> Settings -> Developer settings -> GitHub Apps,点 New GitHub App。

需要填写的核心字段有:

字段建议值作用
GitHub App namehermes-review-botApp 显示名称
Webhook URLhttps://your-domain.com/hooks/github接收事件回调
Webhook secret随机生成 32 字节字符串签名校验
PermissionsPull requests: Read/Write, Checks: Write读取 PR、写评论、设置检查
Subscribe to eventsPull request, Issue comment响应 PR 更新和评论触发

创建完 App 后会生成一个 App ID,然后生成私钥,下载下来保存好。私钥只能下载一次,丢了就得重新生成。接下来要安装这个 App。在 GitHub Apps 页面找到你的 App,点 Install,选择你允许访问的仓库。这一步之后才能拿到 Installation ID。

你可能问,为什么权限里 Pull requests 要 Read/Write 而不是只 Read?因为我们要创建评论和审查,而创建 Review 属于 Write 权限,只读权限写不了。Checks 权限则是为了写 Check Run。

3.3 本地运行与生产部署

本地调试时,最麻烦的是让 GitHub 能访问到你本地的服务。我个人的做法是部署到一台有公网 IP 的测试服务器上,把服务跑起来,再用 Nginx 挂一个子路径转发到 Uvicorn 的端口。如果你习惯用内网穿透工具也行,但注意要确保 Webhook 地址稳定,否则 DNS 解析偶尔出问题会让 GitHub 回调失败。

生产环境我用的是 Docker,镜像我放在私有仓库里,每次更新就是重新打镜像然后滚动重启。目录结构大致是这样:

hermes-review/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置读取 │ ├── github_client.py # GitHub API 封装 │ ├── rule_engine.py # 规则引擎 │ ├── ai_reviewer.py # AI 辅助审查 │ └── models.py # 数据模型 ├── rules/ │ └── default.yaml # 默认规则文件 ├── prompts/ │ └── review_system.txt # AI 系统提示词 ├── Dockerfile └── requirements.txt

启动命令我写在main.py入口里,用uvicorn启动:

import uvicorn if __name__ == "__main__": uvicorn.run("app.main:app", host="0.0.0.0", port=8765, log_level="info")

你可能会问,端口为什么选 8765?其实没有特殊含义,只要别用 80 或 443 这种常见端口,避免和 Nginx 冲突就行。

3.4 让 Hermes 跑起来:示例规则与验证

配好 App 和环境后,就可以做一次端到端验证。先在测试仓库创建一个本地分支,改一个明显的问题,比如加一行print("debug"),然后提交并推送,创建 PR。动作触发的opened事件会通过 Webhook 发给 Hermes。

Hermes 收到事件后,会走一遍完整流程,然后在 PR 上生成评论。如果一切正常,你会在 PR 页面看到一条类似这样的评论文案:

Hermes (warning)main.py:15- 请删除调试输出语句,改用 logger 记录关键信息。

看到这条评论,就说明链路通了。如果没看到,先去看 Webhook 投递记录,GitHub 的 App 设置页有“Recent Deliveries”,点进去能看到请求状态和响应体,这是排查问题的第一站。我个人有 80% 的部署问题都是在这里找到原因的。

4. 常见问题与排查技巧实录

4.1 Webhook 请求失败或签名校验不过

这个问题的现象是 GitHub 的投递记录里显示响应 400 或 500,服务端日志提示Signature mismatch

GitHub 发送 Webhook 时,会在X-Hub-Signature-256请求头里放一个 HMAC SHA256 签名,用你的 Webhook Secret 对请求体做签名。如果你在代码里读到的原始请求体已经被框架序列化过(比如转成 dict 再转 JSON),签名就会对不上。我做 FastAPI 时踩过一次:直接用了request.json(),然后对字典做字符串拼接,结果签名永远不对。正确做法是读取await request.body()拿原始字节流。

import hashlib import hmac async def verify_webhook_signature(request_body: bytes, signature_header: str): secret = settings.webhook_secret.encode() expected = "sha256=" + hmac.new(secret, request_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature_header)

注意,必须是原始字节流,不能有任何编码转换。另一个坑是,如果你在服务前方加了 Nginx,确保 Nginx 不会把请求体重新编码,否则签名也会失效。我一般在 Nginx 里直接透传,不做 body 的 gzip 解压或改写。

4.2 评论重复或抖动

服务跑起来了,但同一个 PR 每次更新都会生成一长串新评论,旧评论也不删除,评论区非常混乱。这是因为我没有对评审结果做幂等控制。

解决办法是为每次评审生成一个指纹,比如取仓库名、PR 编号、最新 commit SHA、规则版本这四个字段拼接后做 MD5。在提交评论前,查询该 PR 是否已经有相同指纹的评论,如果有就跳过或更新,没有才创建。这样即使 Webhook 重试,也不会重复刷屏。

review_fingerprint = md5(f"{owner}:{repo}:{pr_number}:{commit_sha}:{rule_version}")

一个更细的技巧:不要把指纹写在评论正文里让用户看到,而是用评论的隐藏元数据或评论头部的 HTML 注释来存指纹。例如在评论文案最前面加上<!-- hermes-fp: ... -->,这样界面上看不到,但程序可以通过 API 读取该评论的 body 做匹配。GitHub 的 API 不会过滤 HTML 注释,所以这个方案可行。

4.3 规则误报与调优

误报是自动化评审最伤信誉的问题。一开始我把正则规则写得太宽,比如只要新增的 JS 代码里出现.innerHTML就报警,结果很多逻辑上已经做转义处理的安全代码也被标记为高风险。同事反馈“这工具有点神经质”,后来我就不敢让它直接进 CI 了。

调优的经验是:每条规则都要配置信度等级。Hermes 规则结构里我加了两个字段:confidenceallowlistconfidence表示这条规则判定的可信度,超过 0.8 才会被提升到 warning,否则只作为 info。allowlist支持按文件或按内容豁免,比如:

- name: no_inner_html match: '\.innerHTML\s*=' severity: warning confidence: 0.6 allowlist: - path: "test/" - pattern: "// eslint-disable hermes"

有了allowlist,团队可以在代码里显式声明“这里我知道风险,请忽略”,既保留了规则的提醒能力,又给了开发者裁决权。这是调优过程中最有价值的一个设计。实际跑了两周后,我将误报率从 17% 降到了 3% 以下。

4.4 GitHub API 限流与重试策略

GitHub API 的限流有两条坑:一是核心 API 每个 Token 每小时间隔 5000 次请求,二是即使是企业版也可能触发 Secondary Rate Limit。Hermes 在大量旧评论更新时,很容易在几分钟内打满配额。

我做了三层防护。第一,所有 GET 请求都做缓存,按 URL + ETag 缓存响应,如果返回 304 就直接用缓存,不消耗配额。第二,写操作采用异步队列,所有创建评论、更新评论的任务都先进 Redis 队列,由单独 worker 按顺序消费,避免并发导致 429。第三,对 429 和 5xx 响应做退避重试,重试间隔按指数增长,从 1 秒到 60 秒封顶。

async def github_request_with_retry(client, method, url, **kwargs): max_retries = 5 for attempt in range(max_retries): resp = await client.request(method, url, **kwargs) if resp.status_code in (429, 500, 502, 503): wait_time = min(2 ** attempt + random.uniform(0, 0.5), 60) await asyncio.sleep(wait_time) continue resp.raise_for_status() return resp

如果审查超大 PR,API 调用次数会非常多。我的做法是设置一个大 PR 阈值,比如超过 150 个文件就不再逐行评审,只做整体摘要。这样可以保住核心链路稳定,不会被一个巨型 PR 拖垮。

5. 实际运行效果与团队反馈

Hermes 在内部试运行了 4 周后,我统计了一下数据:累计审查了 126 个 PR,共发现 314 个问题。其中规则引擎占 78%,AI 辅助评审占 22%。最常用命中项前三名是:调试输出残留、空异常捕获、明显无用的代码注释。

团队反馈里对我启发最大的一条是:“希望 Hermes 能告诉我不只是哪里有问题,还要解释一下为什么这是个问题。”于是我在评论模板里加了问题说明链接和例子。比如:

Hermes (error)auth.py:42
未捕获requests.exceptions.ConnectionError会导致程序崩溃。
建议:在重试逻辑中捕获网络异常,并记录日志。
参考:项目 Wiki 中的“错误处理规范”。

通过这种方式,Hermes 从一个只会挑刺的机器人,慢慢变成了一个能带着新人成长的辅助者。我也把规则文档写进了仓库的docs/hermes-rules.md,新人提交 PR 前可以先看看 Hermes 的规则,提前自查一遍,PR 通过率明显提高了。

最后说点个人体感。我把 Hermes 切到主干流程之前,先在几个低风险仓库跑了将近两周,靠它抓出来的问题里,真正提醒到我的反而是一些不起眼的死代码和错误日志格式。自动化代码评审不是要替人做决定,而是把基础检查的活扛下来,让评审者把精力放到真正的设计讨论上。如果你也准备搭一套,我建议第一版规则尽可能少,先跑通链路,再慢慢加规则,这样维护成本会低很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 18:54:49

接入Hermes:用智能体把GitHub PR的代码评审底线兜住

我一直有个执念&#xff1a;代码评审这件事&#xff0c;应该让机器先把该看的看了&#xff0c;人再集中精力看机器看不懂的。所以当 Hermes 这个智能体出现在我视野里的时候&#xff0c;我几乎没有犹豫就把它接到了 GitHub PR 流程里。跑了两个月&#xff0c;几百个 PR 下来&am…

作者头像 李华
网站建设 2026/9/8 18:54:23

工业控制MLCC选型实战:从PLC到伺服驱动的完整指南

1. 从一颗小电容看工业控制的稳定性做工业控制硬件设计六年多&#xff0c;我越来越觉得MLCC&#xff08;多层陶瓷电容&#xff09;是被低估的“小角色”。PLC主控板上几十上百颗MLCC&#xff0c;伺服驱动器母线侧、IGBT吸收回路里的表贴电容&#xff0c;哪个环节选型马虎了&…

作者头像 李华
网站建设 2026/9/8 18:53:14

MuJoCo与dm_control实战:从机械臂仿真到强化学习训练

做具身智能的同行&#xff0c;应该都经历过这么一段纠结&#xff1a;机械臂、人形机器人在真实硬件上调试&#xff0c;又贵又慢&#xff0c;还得提心吊胆怕撞坏。所以大多数项目都会先在仿真器里完成原型验证、运动规划甚至强化学习训练。但仿真器选型这事&#xff0c;真不是一…

作者头像 李华
网站建设 2026/9/8 18:52:32

基于微信小程序的老年人健康监测与预警系统(源码+讲解视频+LW)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/8 18:52:28

深入理解 Rust 编译器错误 E0499:一个变量不能被多次可变借用

深入理解 Rust 编译器错误 E0499&#xff1a;一个变量不能被多次可变借用 【免费下载链接】rust Empowering everyone to build reliable and efficient software. 项目地址: https://gitcode.com/GitHub_Trending/ru/rust 导读 E0499 是 Rust 编译器中一组与「借用检查…

作者头像 李华