用Hermes智能体实现GitHub PR自动化审查的完整实践

发布时间:2026/9/7 22:16:10
用Hermes智能体实现GitHub PR自动化审查的完整实践 1. 为什么我会把 Hermes 交给 GitHub PR 去审1.1 手动审查的低效与不可持续代码评审这件事在很多团队里是典型的“又爱又恨”。爱的是它确实能在合入之前兜住一批问题恨的是它太耗人而且越是体量小的 PR评审成本占比越高。我这边维护着一个十来人的 GitHub 仓库日常每天少说五六个 PR 推进来。每个人点开 diff 逐行看再回到讨论区写评论时不时还要切到历史提交里查上下文一轮下来半天时间就搭进去了。更现实的问题是评审延迟直接推高了合入等待时间而等待时间一长开发者就会被迫在分支上累积更多提交PR 变得越来越大评审难度进一步上升形成一个很难打破的恶性循环。这个问题靠“加大人工投入”根本解决不了因为团队规模就那么大。真正让我下定决心折腾自动化是因为连续出了几次低级错误漏审一次是.env.example里混进了真实 Token一次是异常处理写了个裸except把错误全吞了。这些都属于看一眼就能发现的问题可人在疲劳状态下就是会看不见。我开始意识到需要有一个“先读过一遍”的东西把机械性的、模式化的工作吃掉让人的注意力只留给真正需要判断的设计和业务问题。1.2 Hermes 智能体在代码评审里能做哪些事Hermes 在我眼里不是某个单一的 AI 产品而是一类以模型为大脑、以工作流为手脚的智能体实现。用它的思路做 GitHub PR 审查和传统的静态扫描工具完全是两个层次。传统 Lint 只能根据正则和语法树找固定模式改个写法它就哑火了而 Hermes 能真正“读”上下文某个函数加了参数之后调用方是否都同步改了某段配置改了字段名之后测试里面有没有引用旧字段某个 SQL 改动是不是存在注入风险。这些判断依赖语义理解不是简单规则能覆盖的。它最擅长的是以下几类事情模式化问题排查硬编码密钥、越权接口、空指针路径、明显的事务缺失。变更完整性检查改了一个结构体或接口牵连的调用点是否都跟上了。规范一致性命名风格、错误处理方式、日志上下文是否符合仓库既有习惯。上下文汇总把一个大 PR 的改动整理成有层次的摘要降低人工 review 者的阅读负担。我反复跟团队强调一点Hermes 不是来替代 review 者的它是前置筛选和批量预审。真正复杂的设计评审、业务方向把关、技术债权衡还是得人来拍板。这套链路跑起来之后人工 review 者的角色从“逐行挑毛病”变成了“确认机器意见并做设计判断”这是质的区别。1.3 这套方案适合谁如果你是以下几种情况我觉得 Hermes 做 PR 审查的思路值得认真试一试团队负责人想把评审标准统一起来让不同经验水平的成员都执行同一套底线规则。开源仓库维护者PR 来自世界各地审查者时间有限自动化预审能大幅降低维持项目质量的门槛。独立开发者哪怕没有协作者PR 审查也能作为第二双眼睛自己审自己的分支遗漏。对 LLM 应用感兴趣的工程师PR 审查是个绝佳的落地场景链路短、反馈明确、效果可量化。我接下来的内容会从环境搭建和 GitHub 接入开始逐步讲到规则配置、真实 PR 演示、CI 自动化集成以及我在实际运行中踩过的坑和边界判断。2. 先把 Hermes 智能体跑起来环境准备与接入 GitHub2.1 环境选型本地运行还是 Docker安装 Hermes 之前先想清楚以什么形态运行它。我的经验是优先看你的使用频率。如果只是偶尔在本地仓库上手动跑一下审查直接用 Python 虚拟环境安装就行如果要作为常态化的 CI 服务那 Docker 部署更合适方便在服务器或自托管 runner 上保持一致环境。以 Python 生态为例我习惯先用 conda 建一个独立环境避免和系统 Python 打架conda create -n hermes python3.11 -y conda activate hermes pip install hermes-agent没有 conda 的话venv 也一样区别只是环境管理方式python3 -m venv ~/hermes-env source ~/hermes-env/bin/activate pip install hermes-agentDocker 路线对应启动一个常驻服务docker pull hermes-agent/server:latest docker run -d --name hermes-server \ -p 8080:8080 \ -v ~/.hermes:/data \ hermes-agent/server:latest安装完成后命令行入口一般叫hermes先验证一下hermes --version hermes --help注意Hermes Studio 这类管理界面并不是必需品。如果只做 PR 审查纯 CLI 加配置文件就完全够了。管理界面适合需要多人共用、可视化编排复杂技能的团队场景。2.2 大模型入口选择云端 API 还是本地模型Hermes 作为智能体核心推理能力来自大模型。这一步的选择直接影响数据安全、响应速度和成本值得单独讲清楚。我同时配置过云端和本地两条路线互相补充。云端模型比如 DeepSeek 这类 OpenAI 兼容接口优势是配置简单、延迟低、上下文窗口大长 PR 也能一次性读完。代价是代码内容要出内网敏感项目得慎重。本地模型通过 Ollama 或 vLLM 加载开源权重Hermes 系列本身就有对应开源模型也有 Qwen、Llama 系可选优势是数据完全不出内网劣势是显存要够加载一个大一点的模型至少需要 16G 以上显存审查长 PR 时上下文很容易被撑爆。我的选择是日常非敏感仓库用在线接口敏感项目走内网 Ollama 服务。如果你拿不准可以先从云端接口起步跑通了再考虑迁移。配置上一般是一个 YAML 文件类似下面这样llm: provider: openai_compatible base_url: https://api.deepseek.com/v1 model: deepseek-chat api_key_env: LLM_API_KEY github: token_env: GITHUB_TOKEN repo: my-org/cool-repo密钥一律走环境变量注入千万不要写死在配置文件里。2.3 GitHub 凭证最小权限原则下的机器人账号要让 Hermes 读取 PR 和发布审查评论必须有 GitHub 凭证。我自己踩过一个教训最开始图省事直接用了自己的 Personal Access Token结果所有审查评论都以我的头像发出不仅刷屏了同事的 inbox还让别人误以为是我亲自写的意见。后来改用机器人身份的 Fine-grained PAT才算是正经的自动化服务形态。三种接入方式对比如下方式权限范围可见身份适合场景个人 PAT经典范围大难收口个人账号本地临时调试Fine-grained PAT可按仓库、按权限收口机器人账号个人或小团队自动化GitHub App独立应用身份权限细分到仓库可轮换密钥应用机器人开源项目、团队常态集成我的推荐组合是建一个专门做机器人的 GitHub 账号然后给它签发 Fine-grained PAT只授权需要的那几个仓库。权限项也不用开全按最小需求勾选Contents: ReadPull requests: Read WriteChecks: Read WriteMetadata: Read这里 Pull requests 权限建议开 Read Write因为完全只读虽然能分析但没法把审查意见贴回 PR 上Write 权限用于创建 review 评论和提交状态。配置到环境变量export GITHUB_TOKENgithub_pat_xxx如果使用.env文件管理记得把.env加进.gitignore这个文件一旦进仓库就等于裸奔。2.4 网络访问的稳定性和备选方案实际部署中有个绕不开的现实问题GitHub 的连接稳定性在不同网络环境下差异很大。如果你在拉取依赖、下载 Actions 或访问 API 时遇到间歇性超时建议从几个方向缓解尽量让 Hermes 跑在与 GitHub 连接质量好的网络环境中比如云服务器或自托管 runner。依赖安装阶段可以配置可信任的软件包镜像源来加速 Python 包和模型权重的拉取比如 conda 的 channel 配置里在 defaults 之外追加国内高校提供的同行镜像。对于 Actions 频繁拉取第三方 action 的场景考虑使用企业内网的自托管 runner外部网络抖动对主流程的影响会小很多。这些都属于工程部署的常规优化不涉及任何特殊手段。3. Hermes 看懂一个 PR 的底层链路3.1 从 GitHub API 拿到完整上下文理解 Hermes 的审查机制关键是搞清楚它“看”的到底是什么。一个 PR 在 GitHub 上不是一个孤立的 diff它是一组相互关联的数据标题和描述、变更文件列表、每个文件的补丁、提交历史、关联 Issue、既有评论、CI 状态等等。Hermes 做的第一件事就是通过 GitHub REST API 把这些信息全部拉下来。核心接口就那么几个GET /repos/{owner}/{repo}/pulls/{pull_number} GET /repos/{owner}/{repo}/pulls/{pull_number}/files GET /repos/{owner}/{repo}/pulls/{pull_number}/comments GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews GET /repos/{owner}/{repo}/issues/{pull_number}/comments其中拉文件列表的接口最关键每条记录里包含文件名、状态、添加/删除行数以及完整的patch字段这个就是代码审查的主体素材。我建议把 PR 描述和关联 Issue 也一起喂给模型否则只给 diff 的话模型不知道这次改动的业务目的意见容易跑偏。3.2 上下文切片与 token 预算管理直接给一个大语言模型塞整个 PR 的 diff看起来简单实际上会在一半时出现问题。一个稍微大点的 PRdiff 可能几万行远远超出模型上下文窗口。更麻烦的是如果 1 行业务改动后面跟着 100 行 lock 文件变更模型容易把注意力耗在依赖锁定文件上有价值的代码反而被淹没了。我的做法是对 diff 做三级过滤排除生成文件package-lock.json、yarn.lock、*.min.js、dist/、build/等一律不进模型上下文。按文件大小切片单个文件 diff 过大时按逻辑块或函数级别拆分分批分析。优先关注新增行重点分析开头的行以及它们邻近的上下文删除行一般只做参考。切片之后每个分片单独调用模型得到局部结论最后合并成一份全局报告。这一层逻辑直接决定审查的覆盖率和成本值得花时间调优。3.3 一次完整审查的动作编排Hermes 作为一个智能体核心优势就在于将上面的流程拆解成一系列可编排的动作接收任务review PR #27拉取 PR 元信息与 diff按规则做过滤和切片对每个切片执行pr_reviewer技能输出结构化意见合并意见去重按严重级别排序调用 GitHub API 创建 review把意见以评论形式贴到对应代码行汇总审查摘要写入 PR 的顶部评论或独立 comment其中的pr_reviewer技能本质是一段精心设计的提示词加工具调用的组合。它规定了模型的角色、输出格式、每条意见必须包含文件路径和行号、不能出现无根据的猜测等约束。这套机制的好处是审查标准完全可以通过改技能定义来迭代不需要改代码。3.4 一个直观的最小实现示意虽然各发行版的 SDK 不完全一致但核心逻辑都可以用下面的伪代码来理解import os import requests def fetch_pr_diff(repo, pr_number, token): headers { Authorization: fBearer {token}, Accept: application/vnd.github.diff, } url fhttps://api.github.com/repos/{repo}/pulls/{pr_number} return requests.get(url, headersheaders).text def run_hermes_review(repo, pr_number): token os.environ[GITHUB_TOKEN] diff fetch_pr_diff(repo, pr_number, token) # 调用 pr_reviewer 技能传入 diff 和配置规则 review_result hermes_agent.run_skill(pr_reviewer, { repo: repo, pr: pr_number, diff: diff, }) # 结果会有结构化字段summary, comments[], blocking_issues[] return review_result这段代码不是某个具体 SDK 的正式用法但它把链路本质讲清楚了拉取数据、喂给模型、拿回结构化结果。剩下的就是如何把结果写回 GitHub 的问题了。4. 把审查标准做成一套可维护的规则体系4.1 三条铁律先定边界自动化审查最怕的是没有边界的“全面检查”。模型给什么意见都往 PR 上贴结果就是意见数量爆炸人工 review 者根本看不过来自动化反而变成噪音源。我在设计规则时先定了三条铁律只审本次改动不评价历史遗留问题避免老债新算。每条意见可追溯到具体代码位置没有文件路径和行号的意见一律不发。不确定就不硬说模型没把握时标注“需要人工确认”绝不把猜测包装成事实。这三条约束写进pr_reviewer技能的提示词里效果立竿见影。意见数量从一次几十条降到个位数留存的都是真正值得看的。4.2 规则文件的结构和写法我把审查规则设计成 YAML 格式分静态模式和语义模式两层。静态模式由正则和路径匹配组成适合确定性高的问题语义模式靠大模型判断适合需要理解上下文的问题。rules: security: - id: BLOCK-SEC-001 name: hardcoded_secret path_include: [*.py, *.js, *.ts, *.go, *.yaml] patterns: - sk-[A-Za-z0-9]{16,} - AKIA[A-Z0-9]{16} - password\\s*[:]\\s*[\][^\][\] severity: BLOCK message: 检测到疑似硬编码密钥请改用环境变量或 Secret 管理服务。 - id: WARN-SEC-002 name: sql_concat path_include: [*.py] patterns: - execute\\s*\\(\\s*f[\] - cursor\\.execute\\(.*[\]\\s*\\ severity: WARN message: 检测到 SQL 拼接建议改用参数化查询避免注入风险。 python: - id: WARN-PY-001 name: bare_except path_include: [*.py] patterns: - except\\s*: severity: WARN message: 裸 except 会吞掉异常类型和堆栈建议收窄到具体异常。 - id: NIT-PY-002 name: mutable_default_arg path_include: [*.py] patterns: - def\\s\\w\\s*\\([^)]*\\[\\s*\\] - def\\s\\w\\s*\\([^)]*\\{\\s*\\} severity: NIT message: 可变对象作为默认参数是 Python 经典陷阱建议改 None 并在函数内初始化。语义模式则是给模型的一组“审查指令”比如检查新增接口是否处理了空值和异常分支。检查被修改函数的调用方是否都同步更新。检查数据库字段变更是否带了迁移文件。检查权限相关逻辑是否存在越权路径。静态规则负责兜底语义规则负责理解。两者组合才能既稳又准。4.3 严重级别BLOCK、WARN 和 NIT规则体系的精髓在于分级。我把所有意见分成三档级别含义自动化动作人工处理方式BLOCK必须修复才能合入的问题状态设为失败PR 不能 merge确认修复后重新触发审查WARN建议修复但不强制仅评论提醒视情况处理NIT风格、可读性小建议仅评论提醒默认可忽略有兴趣就改BLOCK 级别的东西我会控制得很严只放真正的安全和功能性红线比如硬编码密钥、未处理的致命异常、明显的 SQL 注入、权限绕过。如果 BLOCK 划得过宽比如把命名风格也设为 BLOCK那自动化就会频繁卡住合并流程团队立刻就会反感这套系统。4.4 针对 Flutter 项目的规则扩展热搜里有一票 Flutter 和 Gradle 相关的问题我也顺带说明在 Flutter 仓库上的规则扩展。Flutter 项目的 PR 审查有自己的特点纯 Python 和前端仓库的规则不适用。需要额外关注pubspec.yaml和pubspec.lock是否同步提交否则依赖版本在不同机器上不一致。Android 侧 Gradle 插件版本与 Flutter 版本是否匹配。原生平台代码Kotlin/Swift的权限声明是否合理。新增插件是否设置了最低版本避免低版本系统崩溃。这些判断靠正则只能覆盖一小部分核心还是靠语义规则让模型带着 Flutter 项目的知识去看 diff。效果比通用审查好很多。5. 在真实仓库上跑一轮 PR 审查5.1 示例仓库与 PR 背景为了把流程讲透我拿一个真实的场景举例。假设仓库是my-org/cool-repo技术栈是 FastAPI SQLAlchemy。PR #27 的目标是“新增用户注册接口”涉及的改动文件app/services/user_service.py新增register_user函数app/repositories/user_repository.py新增create_user方法alembic/versions/0012_user_active_flag.py数据库迁移脚本新增is_active字段这是一个典型的、看起来不大的功能 PR但恰恰是这类 PR 最容易漏问题。5.2 触发审查的命令和执行过程在本地跑一轮命令很简单hermes run review PR #27 --repo my-org/cool-repo执行过程中Hermes 会先打印正在拉取的数据和正在分析的切片这部分日志建议平时不要关掉排查问题非常有帮助。我观察到的典型输出流程[1/4] Fetching PR metadata: my-org/cool-repo#27 [2/4] Fetching changed files: 3 files, 214 insertions, 38 deletions [3/4] Slicing diff: 2 code slices 1 migration slice [4/4] Running pr_reviewer skill... Review completed. 3 blocking, 2 warning, 1 nit.5.3 输出结果解读几秒到几十秒后Hermes 在 PR 上贴出了审查意见。我把实际产出的核心部分整理如下摘要PR 整体结构清晰迁移脚本和模型字段定义一致。发现 3 个需处理的问题1 个安全相关密码复杂度校验缺失、1 个数据一致性新用户默认值依赖应用层而非数据库层、1 个异常路径邮箱重复时的错误被吞掉。具体意见示例BLOCKuser_service.py#L45create_user中直接对email字段使用查询后再插入存在并发注册重复邮箱的风险。建议给users.email加数据库唯一约束同时捕获IntegrityError返回友好错误。BLOCKuser_service.py#L52密码复杂度校验缺失。当前代码只检查了非空建议增加长度和字符类型组合校验。WARNuser_repository.py#L38commit失败时session.rollback()没有在except块中调用事务状态可能残留。NITalembic/0012_user_active_flag.py#L12列名is_active与库内已有is_enabled命名风格不一致建议统一。说实话看到结果的瞬间我就明白这套链路的价值了。这三个问题如果让一个人去 review 这个 PR至少要花二十分钟而且大概率会在时间紧迫的情况下漏掉第一个并发问题。5.4 人工复核的处理流程Hermes 贴出意见后我的处理流程是这样的先读摘要再逐一确认代码位置。对于合理意见直接在 PR 上回复“/fix”让开发者在本地修复后推送新提交对于模型判断有误的情况选择“dismiss”并说明原因这个 dismissal 行为也会沉淀进后续审查的学习参考。这里要特别强调别开“自动合入”这个功能。哪怕模型给出的 BLOCK 意见看起来完全正确也应该经过人工确认后修复。自动审查是帮你节省时间不是替你做决策。6. 接入 CI让审查自动跑在每个 PR 上6.1 GitHub Actions 工作流写法本地手动跑只是入门真正让这套体系发挥价值的是把 Hermes 接进 GitHub Actions让每个新 PR 或新提交推送后自动触发审查。我用的 workflow 文件如下name: hermes-pr-review on: pull_request: types: [opened, synchronize, ready_for_review] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Install Hermes uses: hermes/setup-hermesv1 - name: Run PR review env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LLM_API_KEY: ${{ secrets.LLM_API_KEY }} run: | hermes run review current PR \ --repo ${{ github.repository }} \ --pr ${{ github.event.pull_request.number }}触发条件里我特意加上了synchronize意味着开发者推了修复提交后审查会自动重跑不用手动再触发。这是使用体验上的关键细节。6.2 与分支保护的状态检查联动Actions 跑起来后要在仓库的 Branch protection 规则里把这次审查设置为Required status check。具体操作是进入Settings - Branches - Add classic branch protection rule然后在Require status checks to pass before merging里勾选Hermes Review就是 workflow job 中定义的 status 名称。设好之后只要 Hermes 审查发现 BLOCK 级别问题就会把 status check 置为失败PR 的 Merge 按钮直接变灰。这个机制比任何口头约束都有效。开发者想合入就必须先把 BLOCK 修掉让审查重新跑过。6.3 避免重复评论和重复消耗接入 CI 之后我很快踩了第二个坑每次 push 都会触发一次审查Hermes 会把同样的意见重新贴一遍同一行代码挂着五六条相同的评论看起来非常乱。解决思路是幂等化在评论内容里带一个固定格式的标记比如!-- hermes-review-id: e7a3f19c --下次跑之前先拉取已有评论如果对应代码行已经存在相同 review-id 的意见就直接跳过。实现逻辑可以放在技能层也可以放在触发脚本层。我建议在技能层做因为这样无论从 CLI 还是从 CI 触发行为都一致。6.4 一个真实踩过的坑Flutter/Gradle 插件版本不匹配搜索热词里出现了一条失败信息failed to apply plugin dev.flutter.flutter-gradle-plugin. error: your pr ...。这个报错我恰好遇到过。当时团队的 Flutter 仓库里PR 把自己的 Flutter 版本升级了但 Gradle 插件版本没有同步适配CI 构建直接失败。最要命的不是报错本身而是我们一开始把 Hermes 审查和构建 job 放在同一个 workflow 里构建失败导致审查 job 也被跳过问题根本没被发现。后来我把审查 job 和构建 job 完全解耦各自独立运行。Hermes 审查不依赖构建是否通过构建失败反而会被 Hermes 作为分析素材它会对比 PR 分支和基础分支的 Flutter/Gradle 版本在评论里明确提示版本不一致导致的构建风险。这样即使构建 job 挂了审查 job 依然能输出有价值的分析供人工处理。经验总结不要把影响分析的功能和影响构建的状态耦合在同一个 job 里。审查是旁路系统它的价值恰恰在于主链路失败时还能提供独立判断。6.5 自动化程度的渐进策略我的建议是分三个阶段推进第一阶段Hermes 只贴评论不设置 status check让大家习惯机器提出意见第二阶段开启 WARN 级别以下的自动审查BLOCK 暂时人工确认第三阶段把 BLOCK 的 status check 接入分支保护形成强约束。一次性全量开通团队容易产生抵触循序渐进反而接受度高。7. 实践了这么久我摸清的边界和留着的底线7.1 幻觉和误报机器判断是概率不是事实LLM 审查最大的短板就是会有幻觉。它可能把正常代码误判成 bug也可能看漏真正的问题。我有一次遇到很典型的案例模型把max_length255误认为会导致数据库迁移不一致实际上这个值和迁移脚本里的定义完全一致。这种误报如果不约束会逐渐消耗掉团队的信任。我的缓解手段有两条提示词里强制加“不确定时标注需人工确认不要直接断言”。静态规则发现的确定性问题优先级高于模型语义判断两者冲突时以静态判断为准。另外模型看小 diff 很准但看跨模块的大改动时容易抓不住全局。这种场景我一般直接把 PR 分成逻辑子任务分别审查结论质量会有明显提升。7.2 安全与隐私边界代码不过墙自动化审查绕不开数据出境问题。仓库代码是一个组织的核心资产如果团队用的是云端大模型接口所有 diff 都会被发送到第三方服务。对于非敏感的开源项目问题不大但对于商业闭源项目我就强烈建议用本地模型。我在内网用 Ollama 加载开源权重跑 Hermes效果虽然比云端最强模型略低但胜在数据完全可控。另外一个容易忽略的点不要把用于自动化审查的密钥发给模型。PR 里如果出现了疑似 TokenHermes 会把它识别出来写进审查评论——但如果这个 Analyze 提示词本身把 Token 拼进了发给模型的上下文那等于把真实密钥泄漏给了第三方。我的处理方式是在切片环节就做脱敏正则先把疑似密钥替换成***才允许进入模型上下文。7.3 人工 review 的不可替代位置自动化审查帮我省下了大量时间但我心里很清楚它的能力边界。架构合理性、技术债的全局权衡、业务规则的隐含语义、产品体验层面的判断这些它做不了至少现在做不了。它最大的价值在于把 review 者的精力从“翻 diff 找表面问题”中解放出来让人能专注在真正的判断上。我会在 workflow 里保留一个强制的人工同意按钮即使所有 BLOCK 都清空了关键 PR 也必须有人点 Approve 才能合入。自动化负责效率和底线人负责质量和方向这个分工我用了一段时间目前看是团队接受度最高的状态。7.4 再往后这套体系能长出什么踩过几次坑、调整过几轮规则之后我发现 PR 审查只是入口。现在团队已经把 Hermes 的同一套能力用到了更多场景自动生成发布会说明、辅助提取变更影响范围、检查依赖许可合规等。它的底层逻辑是一样的连接数据源让智能体按规则分析和产出人工审阅确认。如果你也想走这条路建议从 PR 审查这个场景开始链路最短、反馈最直接、收益最好量化。跑顺了之后你会发现整个研发流程的“自动可做的事”远比想象中多。

相关新闻