
1. Hermes 是什么从“被动等评审”到“机器人先看一遍”先说我自己的真实场景。我在 GitHub 上维护着两三个开源项目平时 Issues 和 PR 都不算少。最头疼的不是写代码而是“切换上下文”——刚写完一个功能还没来得及深呼吸就有 PR 等着看。小 PR 还好几分钟能过遇到那种改了上千行、横跨五六个文件的大 PR光是把 diff 从头到尾捋一遍就得很久更别提还要逐行想“这里为什么这么写”“那里有没有边界漏了”。一两个 PR 还能撑住一旦 PR 排着队进来脑子完全不够用。后来我开始尝试把第一轮审查交给自动化机器人也就是给 PR 配一个代号叫 Hermes 的审查助手。它做的事情并不复杂PR 创建或更新后自动拉取 diff调用大模型对代码做一轮静态逻辑层面的检查然后以机器人身份在 PR 里留下行级评论或汇总意见。这样我回到电脑前时PR 已经被“先看了一遍”我只需要在机器人给出的结论上做二次确认把精力放在它拿不准的、需要业务上下文的地方。如果你也维护开源项目或者在团队里负责代码评审可以试试这个思路。这个方案落地后我的体感变化是PR 等待首次反馈的时间从“按小时计算”缩到了“按分钟计算”低级问题拼写错误、明显未使用的变量、缺少空值判断、风格不统一在人工介入前就被筛掉了我的审查时间大概省了 40% 左右。Hermes 不是要取代人工评审它的定位更像团队里的“见习评审员”——先帮你把明显的问题挑出来再把需要人类判断的争议点放上桌面。2. 整体设计思路选型与架构拆解2.1 为什么用 GitHub App 而不是个人 Token 或 OAuth App在设计 Hermes 的第一步我就要定下来它该以什么身份访问 GitHub。最省事的方式是拿一个个人访问令牌Personal Access Token但这样做有三个问题一是权限范围太宽个人 Token 往往有整个账号的仓库权限一旦泄露风险很大二是这个机器人是以你的名义评论PR 里显示的评论头像和名字都是你容易让人误解三是没法把它分发给团队其他人使用别人想接手也很麻烦。GitHub App 是更正规的做法。创建 App 时可以按仓库粒度授予权限比如只给 pull_requests 的读写权限不给代码、配置、密钥等敏感区域的权限甚至可以限定到某个仓库。GitHub App 的身份是独立的 bot 账号在 PR 里显示的是机器人自己的头像职责边界非常清晰。它使用 installation token 访问仓库token 本身有过期时间而且可以通过 GitHub 后台随时取消授权安全上比长期 Token 可控很多。我最终选择的是“GitHub App Webhook 事件驱动”的架构。Webhook 的好处是实时性和低空转成本PR 一有动作GitHub 就把事件推送到 Hermes 服务不需要定时轮询 API。轮询不仅会有延迟还会频繁吃掉 API 配额对开源仓库尤其不友好。Hermes 服务收到 Webhook 后再根据事件类型决定是否需要处理这种按需计算的方式让资源占用降得很低。2.2 最小权限清单与事件订阅很多人第一次配置 GitHub App 时容易犯一个错把权限能勾的全勾上结果打开后台发现 App 几乎可以读写仓库的一切。这个做法风险很大万一 Hermes 的服务器被攻击攻击者就等于拿到了仓库的“万能钥匙”。我在部署 Hermes 时遵循的是最小权限原则具体只需要这么几项权限项需要的级别用途Pull requestsRead Write读取 PR 内容和 diff发布审查评论MetadataRead默认必需获取仓库基本信息Webhooks不需要我们不通过 API 管理 WebhookContents只读可选读取非 PR 相关文件以供上下文参考Checks只写可选将来如果需要写入 CI 检查状态事件订阅方面我只需要三类pull_request 事件重点处理 opened、synchronize、ready_for_review、reopenedpull_request_review_comment 事件可选用于响应评论区里的 Hermes 指令ping 事件GitHub 在创建 Webhook 时用来验证连通性只订阅必要的事件能减少无效请求也让 Hermes 的日志更干净。如果后续要支持“评论 /retry 就重新审查”这类交互再补上对应的 issue_comment 事件即可。2.3 触发策略不是每个 PR 都值得马上审查技术选型确定以后我第一个踩的坑是没有“筛选”导致 Hermes 每收到一个事件就跑一次完整审查。后来我发现在真实项目里很多 PR 并不适合立刻走全量审查比如 Draft PR草稿作者还在频繁改代码审了也是白审比如只改了 README 或者文档的 PR完全没必要调用大模型再比如超过 1500 行的巨型 PR一次全量审查不但成本高模型还容易漏掉关键上下文。我现在的触发策略是这样的Hermes 先看事件的属性只有同时满足以下条件才进入审查流程。PR 状态不是 draftPR 中至少有改动文件且排除了纯文档类文件.md、.txt、图片资源等变更文件数量在配置范围内默认 130 个总行数变更增删总和在配置范围内默认 101500 行如果 PR 标题或描述中包含[skip review]或[no-hermes]直接跳过这个策略让我的服务器少跑了很多冤枉任务同时也不再收到“为什么改个错别字也要被机器人评论”的抱怨。触发条件既可以从 PR 元数据里读取也可以在配置文件中动态调整算是投入产出比最高的一个设计。3. 核心流程拆解从 Webhook 到行级评论3.1 构建 GitHub App 凭证与安装令牌在讲具体流程之前需要先把凭证机制捋清楚。GitHub App 的鉴权是两层结构第一层是用应用的私钥签出一个 JWTJSON Web Token用来证明“我是这个 App”第二层是用 JWT 换取位于特定仓库或组织的 installation token这个 token 才能去调用 API 读取和写入仓库内容。Installation token 默认一小时过期。这个流程如果自己撸一遍会有点绕我直接分享一个已经验证过的 Python 参考实现核心逻辑都写在注释里import time import jwt import requests APP_ID 你的 App ID INSTALLATION_ID 你的 Installation ID PRIVATE_KEY_PATH ./hermes.private-key.pem with open(PRIVATE_KEY_PATH, r) as f: private_key f.read() # 1. 生成 JWT有效期最长 10 分钟 now int(time.time()) payload { iat: now, exp: now 10 * 60, iss: APP_ID, } jwt_token jwt.encode(payload, private_key, algorithmRS256) # 2. 用 JWT 换取 installation token headers { Authorization: fBearer {jwt_token}, Accept: application/vnd.githubjson, } resp requests.post( fhttps://api.github.com/app/installations/{INSTALLATION_ID}/access_tokens, headersheaders, ) resp.raise_for_status() installation_token resp.json()[token] print(installation_token)这里的 JWT 签名算法是 RS256私钥在创建 GitHub App 时生成并且只能下载一次一定要妥善保存。从安全角度讲私钥不应该直接放进代码仓库而是作为环境变量或挂在 Docker 的 secret 中传入。拿到 installation token 之后就可以把它当作普通的 Bearer Token 去调用 GitHub REST API 了。3.2 拉取并解析 PR Diff行号匹配是关键拿到令牌后第一步是获取 PR 的 diff。GitHub 官方提供了GET /repos/{owner}/{repo}/pulls/{pull_number}这个接口配合Accept: application/vnd.github.v3.diff这个自定义请求头返回的就是一个标准的 unified diff 文本。我一般会在触发 Webhook 时把pull_request.diff_url字段记录下来直接从这个地址拉取就行。解析 diff 时最容易踩的坑是行号匹配。PR 评论需要精确的“文件路径 行号”而行号在原文件和目标文件里往往不一致。以这段 diff 为例 -10,7 10,8 def calculate_total(items): total 0 for item in items: - total item.price # 增加折扣处理 total item.price * item.discount if item.discount else item.price return totaldiff 中第一行 -10,7 10,8 的含义是原文件从第 10 行开始共 7 行新文件也从第 10 行开始但共 8 行。如果 Hermes 要给新代码“增加折扣处理”这一行评论行号必须对应新文件的第 11 行因为 diff 中行从新文件的第 10 行开始第一行是total 0第二行才是新增行而不是原文件的第 11 行。我处理这个问题的做法是把 diff 按文件拆开逐行解析每个 hunk 的起始位置然后维护一份“新增行号 - 实际文件行号”的映射表。这样 Hermes 在生成评论时把它映射到真实位置GitHub API 才能正确地把评论挂到代码行上。如果直接去评论一个 diff 里不存在的行号API 会返回 422 错误而且这个问题在日志里特别不直观。3.3 构造审查上下文与 LLM 调用整个 Hermes 里最影响效果的环节是“给模型看什么”。一开始我图省事只把裸 diff 扔给模型结果模型的评论几乎全是废话比如“请确保这个改动没有破坏现有功能”——这种话放谁身上都觉得是废话。后来我仔细琢磨了上下文应该包含什么调整后的组合是PR 标题和描述告诉模型这个 PR 想干什么每个被改动文件的 diff 片段与改动点直接相关的上下文代码片段一般取改动行前后 20 行项目约定文件的内容摘要比如 CONTRIBUTING.md 或 .editorconfig 的规则Prompt 模板也不需要整得多复杂我现在的做法是在系统提示词里写清楚几条硬性规则你是一名资深代码审查专家。请基于用户提供的 PR 变更重点检查以下内容 1. 明显的逻辑错误与边界条件遗漏 2. 可能导致运行时异常的问题例如空指针、越界、未捕获异常 3. 并发安全与数据一致性问题 4. 与常见代码风格规范不一致的地方。 对每个问题请给出严重级别critical / warning / info、问题位置文件路径行号、问题描述、修改建议。 如果某行代码质量良好不要评论。不要输出空泛的赞美或套话。关于模型选型Hermes 的设计本身对底层模型是解耦的。我最早用的是 OpenAI 的接口后来也测试过 DeepSeek 等开源或半开源的模型。关键指标是代码理解能力和结构化输出稳定性。DeepSeek 这类模型在代码任务上的表现不错并且可以通过 HTTP 接口快速对接社区里也有人直接把 Hermes 和这类模型搭配部署性价比更高。不过要注意不同模型的 API 格式不完全一样建议在 Hermes 里封装一层模型适配器让上层流程不依赖具体厂商。调用过程中的实用配置我也分享几个。温度Temperature建议调到 0.2 或更低代码审查需要确定性优先不需要模型“发挥创意”。最大输出 token 要设置足够高因为审查意见往往会包含多段代码建议。超时时间至少设到 60 秒以上大 PR 的推理时间普遍偏长宁可等一会也不要因为超时丢掉结果。3.4 提交 Review 评论行级评论与汇总意见的结合模型给出的审查结果是结构化数据Hermes 要把它转成 GitHub 的 PR Review 对象。GitHub 的 Pull Request Review API 允许我们在一个 review 里同时提交多个评论threads每个评论可以精确到文件的行号。和逐条调用 issue comment 接口相比review 对象在 UI 上更像一次正式的代码评审作者也更容易一次性看到所有意见。我的提交策略是critical 级别的问题用行级评论挂在具体代码行旁边warning 和 info 级别的问题汇总在 review 的 body 里避免评论数量太多把 PR 页面刷得满屏都是。如果 critical 数量大于 0review 事件用 REQUEST_CHANGES否则用 COMMENT 事件并把“未发现明确阻塞性问题仍建议人工确认设计合理性”这类结论写在 body 末尾。幂等性处理也很重要。同一段代码如果作者反复 pushHermes 会在每次 synchronize 事件时重新跑一次。如果不做去重作者会收到好几条重复评论体验很差。我的做法是把每次审查结果按“PR 编号 提交 SHA”做存储在展示和提交评论前检查当前 SHA 是否已经评论过。GitHub 评论本身的in_reply_to机制也可以用来做增量更新但实现复杂度会高不少建议先从整轮去重做起。4. 部署与落地Docker 与 GitHub Actions 两条路线4.1 自托管 FastAPI 服务用 Docker Compose 管理Hermes 的第一个版本我跑在本机进程里后来要 7x24 小时常驻就直接容器化了。我用的框架是 FastAPI因为它天然支持异步处理 Webhook 请求配合uvicorn跑起来非常轻。Webhook 接收端只需要做两件事验证请求来源然后把事件塞进任务队列。Docker Compose 的配置大概长这样version: 3.8 services: hermes: image: hermes-agent:latest restart: unless-stopped ports: - 8438:8438 environment: - APP_ID${APP_ID} - INSTALLATION_ID${INSTALLATION_ID} - PRIVATE_KEY${PRIVATE_KEY} - LLM_API_KEY${LLM_API_KEY} - LLM_BASE_URL${LLM_BASE_URL} - LLM_MODEL${LLM_MODEL} volumes: - ./data:/app/data需要特别注意PRIVATE_KEY在 compose 文件里用的是环境变量引用实际值要放在.env文件里并且.env必须加入.gitignore。如果你在服务器上部署建议在系统层面做一层密钥管理比如 Docker secret 或者直接引用环境变量文件而不是把私钥明文写死在镜像里。服务本身需要能被 GitHub 访问到也就是要有一个公网可访问的 HTTPS 回调地址。这里不展开讲具体怎么获取域名或反向代理实际部署时只需要确保 Webhook URL 能稳定接收 GitHub 的推送即可。GitHub Webhook 对响应时间有要求如果 Hermes 在接收事件的同时执行完整审查流程很容易超过 10 秒导致 GitHub 认为投递失败重试。我的做法是接收端把事件写入本地 SQLite 或 Redis 队列后立刻返回 200后台再由 worker 异步处理审查和评论。4.2 不想自建服务用 GitHub Actions 就能托管 Hermes自托管方案适合有服务器、想完全掌控数据的人。如果你不希望维护一套常驻服务可以把 Hermes 的核心逻辑封装成 GitHub Actions 的 action在 workflow 里监听 pull_request 事件任务跑完进程就结束不需要额外开销。这种方案的好处是零服务器成本GitHub 本身承载了全部算力缺点也明显GitHub Actions 一次 job 有运行时长限制超大 PR 或者模型响应慢的情况容易卡在边界另外 actions 里的日志和事件数据都在 GitHub 侧留存隐私性弱一些。一个基于 actions 的触发示例是这样的on: pull_request: types: [opened, synchronize, ready_for_review] jobs: hermes-review: runs-on: ubuntu-latest permissions: pull-requests: write contents: read steps: - name: Checkout uses: actions/checkoutv4 - name: Run Hermes code review uses: your-org/hermes-actionv1 with: github-token: ${{ secrets.GITHUB_TOKEN }} model-api-key: ${{ secrets.LLM_API_KEY }}需要注意GitHub Actions 自动提供的GITHUB_TOKEN默认权限是只读需要像示例中那样显式声明pull-requests: write否则评论发不出去。另外在 Actions 环境下跑 HermesWebhook 和 installation token 的流程都被 GitHub 帮你省掉了直接用GITHUB_TOKEN调 API 即可逻辑会比自托管简单。4.3 数据隐私与成本控制我最初决定自托管 Hermes一个重要考量是数据隐私。代码是一个企业最敏感的资产之一如果审查内容要送到外部模型服务就必须在团队内部说清楚“哪些代码会被发送、发送给谁、保留多久”。如果你用的是 OpenAI、DeepSeek 这类云端 API建议在项目层面设置规则把tests/、generated/、含敏感配置的路径排除在发送范围之外至少不要在 prompt 里完整塞入密钥文件。成本方面我总结出三个实用的省钱思路。第一只审查变更行而不是整个文件。让模型只看到 diff 和附近少量上下文和把整个文件拆开全部喂给模型相比token 消耗能差出一个量级。第二设置每小时/每仓库的调用上限。比如 Hermes 每小时最多处理 30 个 PR超出就自动排队并通知管理员防止某次大量推送导致费用暴涨。第三用好缓存。同一个 PR 如果 commit SHA 没变Hermes 直接从缓存读取上次审查结果不重复调用模型。5. 常见问题排查与避坑指南5.1 常见问题速查表在本地调试和真实部署 Hermes 的过程中我遇到最多的问题基本都可以归成下面几类。我整理了一个速查表按经验频率排序现象可能原因排查方法Webhook 收不到任何事件GitHub 回调地址不可达、Secret 不匹配在 GitHub App 后台查看“Recent Deliveries”确认投递记录和响应码评论发布失败返回 422行号不在 diff 范围内拉取 PR diff校验评论行号是否属于变更行返回 401 或 403Token 无效或权限不足确认 installation token 未过期App 权限列表是否包含 pull_requests: write模型响应慢导致超时单次调用超时时间过短、PR 太大把超时调到 60s 以上排查是否需要限制 PR 行数同一 PR 重复收到多条评论缺少幂等去重逻辑在提交评论前检查当前 commit SHA 是否已处理过评论挂了但作者看不到评论提交到了错误的仓库确认 Hermes 使用的 installation token 对应的仓库与 PR 仓库一致大 PR 审查费用过高上下文没有做裁剪检查是否把整个文件都塞进了模型改成只发送 diff 和邻近代码后台日志有大量重试接收端处理超时接收端和审查端拆分接收后立刻返回 2005.2 我踩过的三个值得记录的大坑第一个坑发生在给 Hermes 配权限的时候。早期版本为了省事我申请了 OAuth App 的授权结果它拥有用户账号下几乎所有仓库的权限而且无法精确控制到某个仓库。后来换成了 GitHub App 并在权限列表里只勾选了必要的项权限边界立刻清晰了。建议一开始就用 GitHub App 起步不要为了“快速运行”绕过这一步。第二个坑是没有给 PR 评论做幂等。第一次测试时我连续修改了三次代码测试效果结果 Hermes 三次都完整评论了一遍PR 评论区变成了同一个模型的刷屏现场。后来加了“PR 编号 commit SHA”的唯一性检查同一轮提交只允许生成一次评论问题才算是彻底解决。第三个坑是没有限制超大 PR。有一次团队合并了一个非常庞大的重构分支PR 改动超过了 3000 行Hermes 在调用模型时直接超时而且那次任务的 token 消耗比过去一周加起来都多。现在我在代码里加了硬性上限大 PR 不进入模型审查流程只返回一条提示让维护者决定是否拆小后重试。这个取舍在真实项目里非常必要。5.3 安全与合规提醒再补充几条安全和合规层面的提醒。首先GitHub App 的私钥是最高机密一旦泄露别人就能以 Hermes 的身份读取和评论仓库代码。我建议私钥只在启动时注入内存不在日志里打印。其次自定义 Webhook 的 Secret 校验一定要做。GitHub 在推送事件时会在 header 里带上签名Hermes 需要用配置的 Secret 对请求体做 HMAC-SHA256 校验否则任何人都可以伪造事件调用你的服务。第三如果你部署自托管 Hermes务必把仓库的.env、.pem文件加入.gitignore同时给服务器设置好访问控制避免其他服务直接把私钥目录扫走。6. 把 Hermes 接进团队流程之后的一些实际体会把 Hermes 跑通并在多个仓库里稳定运行了几个月之后我最大的体会是自动化代码评审真正让你省时间的部分不是它帮你“找到”了多少 bug而是它帮你“挡掉了”大量低质量的注意力消耗。过去我打开一个 PR要先花十几秒进入审查状态然后逐行看代码。现在这个状态切换的时间被压缩了因为 Hermes 已经给出了初步结论我只需要带着它给出的问题列表去验证而不必从零开始。在团队协作层面Hermes 还带来一个有意思的变化因为机器人会实时给 PR 评论AI 生成的审查意见倒逼开发者写更清晰的 PR 描述和更小粒度的提交。以前大家提交 PR 时往往只丢一句“fix bug”现在都会主动写明改动背景、影响范围、测试情况因为模型“看不懂”含混的描述时也会在审查意见里指出“缺少上下文”。从流程角度看这是一个比较好的正循环。如果你打算在自己的项目里也搭一套 Hermes一个小建议是前期不要追求大而全的审查规则先把“空指针/空值风险、明显逻辑漏洞、风格严重不一致”这三类问题跑通让团队感受到辅助价值再逐步扩展。要让自动化审查被团队接受关键不是模型的评论多犀利而是少说废话、不打扰、可执行。相信我只要评论质量稳住了大家会主动帮你想下一步该让 Hermes 再多看点什么。