代码审查智能体设计实践:用Hermes重构PR评审流程

代码审查智能体设计实践:用Hermes重构PR评审流程
1. 我为什么给PR流程配了一个“审查智能体”——从人工评审的痛点说起先说一个我这两周一直在做的事把一套名为 Hermes 的自动化代码评审服务接进了团队的 GitHub 仓库。所有新开的 PRPull Request都会先经过它再由人来复核。目前它已经稳定处理了团队内部几十个 PR产出的评论有废话但也有好几次真的捕获到了遗漏的空指针和日志信息泄露。你可能会问GitHub 本身有 Review 机制团队也有人工评审为什么还要再跑一个 Hermes 出来最直接的原因就一条代码评审越来越难靠“人肉”做完。1.1 代码评审中最耗神的其实不是“看代码”很多人以为评审 PR 最费时间的是阅读 diff、找逻辑漏洞。真正做过一段时间维护工作的人应该会有共鸣最耗神的其实是评审前的“上下文重建”以及评审之后的“沟通往返”。一个后端服务仓库日均 PR 数量达到十几个以后每位负责评审的同学每天光“进入状态”就要花掉大量时间。看到一个新的 PR你至少需要先搞清楚这个 PR 想解决什么问题它改了哪几条业务链路它有没有改动公共模块如果公共模块被动了影响范围是什么如果恰逢 PR 里还有大量重构产生的 diff那情况更难办。等你把无关紧要的格式变更、变量重命名、文件挪动看完注意力已经被消耗得差不多真正核心的业务逻辑反而没有力气去细想。还有个团队里更常见的问题很多人习惯把 PR 堆到下午再一次看或者趁 CI 跑测试的时间快速扫一遍。这种情况下评审质量一定不稳定——状态好时能揪出明显问题状态不好时基本就是“看着没问题就 Approve”。这不是人的问题而是流程里缺少一个能够承担“预审”角色的环节。1.2 Hermes的定位先过滤再深读Hermes 的工作思路很简单它不做“取代人类评审”这种不切实际的承诺而是把所有 PR 评论变成两层第一层自动过滤。凡是环境配置错误、明显的不安全写法、资源未释放、调试代码被提交、依赖版本不一致这类“一眼能看出”的问题Hermes 直接给出提示把碎片化问题挡在人工介入之前。第二层辅助深读。它对 PR 涉及的代码做一次结构化梳理输出一份摘要这个 PR 的意图是什么、改了哪些关键点、哪些文件风险最高、有没有潜在边界条件需要考虑。人工评审拿到这份摘要后不需要再从头猜可以直接跳到有争议的地方去判断取舍。打个比方这就跟你去医院看病一样。过去你直接挂专家号专家要从零开始听你描述病情、翻检查单、排除无关项。现在可以先由一个全科助手把基础筛查做完把可疑项标出来专家再针对性地判断治疗方向。最终拍板的一定是专家但助手让专家的时间花在了刀刃上。这个定位听起来并不玄但实际做起来涉及的东西很杂GitHub Webhook 接入、commit 状态跟踪、评审上下文构建、结果去重、评论落点定位……任何一个环节设计不到位Hermes 就会从“助手”变成“噪音源”。下面我把整条链路的设计和实现过程拆开讲能让你在搭建类似自动化评审服务时少走弯路。2. 从Webhook到评论落地Hermes读取PR并产出一条评审意见的完整链路刚开始搭 Hermes 的时候我下意识以为重点在“如何写评审 Prompt”上。做了一段时间才发现真正的难点在 GitHub 侧的工程对接你连一个 PR 都还没看到就已经要和 webhook、API 鉴权、diff 上下文、评论位置、去重机制缠斗半天。所以先把这条链路完整地捋一遍只有知道数据怎么流动后面谈配置和调优才有基础。2.1 PR事件如何被Hermes捕获GitHub 提供了一整套 webhook 事件机制。当仓库里发生 PR 相关动作时GitHub 会向你在 App 里配置的服务器地址发送一个 JSON 请求。Hermes 监听的事件主要有这几类pull_request.opened新 PR 创建这是最典型的触发时机。pull_request.synchronizePR 的分支有新 commit 推送评审结果需要更新。pull_request.ready_for_reviewPR 从 Draft 状态转成正式可评审状态。pull_request_review_comment.create有人在 PR 里回复评论某些情况下可以作为 Hermes 自我修正的触发信号。当某个 PR 被打开时GitHub 发来的 event payload 大体是下面这个样子{ action: opened, number: 42, pull_request: { html_url: https://github.com/example/repo/pull/42, title: feat: 重构用户中心缓存逻辑, state: open, merged: false, base: { ref: main, sha: e1f3b2a... }, head: { ref: feature/user-cache-refactor, sha: f9c9e21... }, draft: false }, repository: { full_name: example/repo } }看到actionopened之后Hermes 不能马上拿着这个 payload 里的信息去分析代码因为这个 payload 只相当于一个“门铃”告诉你有人按门铃了。真正的内容比如 PR 的标题、描述、变更的文件列表、每一段 diff都需要通过 GitHub REST API 去拉取。Hermes 接收到事件后要做的第一件事是解析出两个关键字段base.sha和head.sha。前者代表 PR 目标分支最新 commit后者代表 PR 来源分支最新 commit。GitHub 有一个专门接口可以拿到两个 commit 之间的完整 diffGET /repos/{owner}/{repo}/compare/{basehead...}实际上我们会取base.sha...head.sha的区间代码侧的写法大致是这样import requests headers { Authorization: fBearer {token}, Accept: application/vnd.github.v3.diff, } compare_url ( fhttps://api.github.com/repos/{owner}/{repo}/compare/ f{base_sha}...{head_sha} ) resp requests.get(compare_url, headersheaders) diff_text resp.text这里有两处容易出错。第一处如果 base 分支在你打开 PR 之后又有了新 commit那么使用当前 base 分支最新 commit 去 compare得到的 diff 会包含大量不属于当前 PR 的变更评审会变得混乱。正确做法是拿到 PR opening 时 base 对应的瞬间快照版本也就是 Pull Request 页面里显示的“base sha”而不是实时去查 base 分支的 head。第二处diff 可能非常大。一次涉及几百个文件的大 PR整个 diff 文本可能达到几 MB。如果直接全部丢给大模型去“阅读”哪怕语境窗口放得下响应时间和成本也非常惊人。所以 Hermes 在实际处理时会有节点筛选和文件截断逻辑这个在下文讲上下文构建时展开。2.2 构建评审上下文diff并不是唯一需要看的东西拿到完整的 diff 文本之后直接把它扔给 LLM让它“评论一下”这是很多初版方案最容易踩的坑。纯看 diff 会产生两个问题第一diff 是碎片化的。比如一个 PR 把UserService里的方法签名从getUserById(Long id)改成了getUserById(String userId)单看这个 diff 你会觉得只是参数类型变化但如果调用方的不同分支里还在传Long类型或者这个接口是给外部系统用的就必须结合调用链去判断影响。只看 diff 看不到这些上下文。第二PR 描述里往往藏着真正的设计意图。很多时候代码本身没有“语法错误”它是朝着一个错误的方向在实现。比如描述里写着“为了支持多租户隔离”但代码里你看到的是关闭了所有鉴权校验这是一定要拦下来的。仅凭代码 diffHermes 只能发现实现层面的 bug发现不了方向性错误。所以在把数据喂给模型之前Hermes 会构建一个评审上下文包里面包含上下文信息来源为什么需要PR 标题和描述Pull Request 本体理解变更目的识别实现与意图的偏差变更文件清单及数量GitHub API快速判断 PR 规模与风险集中点核心文件的代码片段文件 raw 内容理解函数整体结构而非只看被修改的行潜在关联的 IssuePR 描述中的fixes #xxx关键字把代码变更对回到业务诉求历史评审意见Hermes 自身的本地存储避免重复提出相同问题实现跨 PR 学习团队约束规则规则库文件识别不符合团队约定的写法实际在实现时我不会真的把上面所有信息一股脑都拿去喂给大语言模型那样成本太高。最简单高效的做法是分两个阶段第一阶段Hermes 先基于 PR 标题、描述、文件清单和完整 diff生成一个结构化的“摘要与风险初评”第二阶段根据第一阶段给出的高风险文件列表再去逐一抓取相关文件的上下文。可以把这个设计理解为“先通读目录再精读重点章节”而不是要求一个人从第一页到最后一页逐字背诵整本书。2.3 评审结果如何回到GitHub评论、Review与Check RunHermes 分析完一个 PR 后有三种途径可以把结果回写到 GitHub 上分别对应不同的使用场景我后来的实现里全都用到了。第一种是「PR 评论」也就是直接在 PR 的对话流里发一条总评。适合给出整体摘要、变更影响面总结、以及需要人工重点确认的问题清单。它的优点是人一眼就能看到缺点是如果每条意见都发总评很容易刷屏时间长了大家就把 Hermes 当成一个“话痨机器人”。第二种是「代码行级评论」GitHub 允许通过 API 在某个具体代码行上挂评论。例如你发现某个文件第 80 行有个空指针隐患可以直接在那一行下评论。这种模式体验最好缺点是实现复杂。GitHub 的 Reviews API 要求评论必须关联到具体的 commit而且必须定位到 diff 上下文中的位置。如果你的 diff 是按行计算的错位一个字符评论就挂不上。所以 Hermes 的行级评论并不是对每个问题都强行走。服务器端有一个映射逻辑先尝试把评论定位到diff_hunk中的某一行定位失败时就降级为在摘要评论里附带文件路径和行号尽量不丢失信息。第三种是「Check Run」也就是在 PR 的 Checks 选项卡里增加一条记录。如果你的仓库配置了分支保护规则要求所有 Check 必须通过才能合并那么 Hermes 可以把“存在严重问题”这个状态嵌进去阻塞合入。一般情况下我不建议默认阻塞团队成员对自动化评审的信任度还没有达到那个程度过早阻塞容易引发对抗情绪。单条评论包含什么内容也有讲究。Hermes 早期生成的评论动辄几百字后来发现大段文字根本没人看。现在的输出结构是一句话问题描述、影响的文件与行号、建议修改方向、严重级别标签。举个例子**风险提示中等** src/main/java/com/example/service/UserService.java:120 userObj 在调用 updateUser() 之前可能为 null。 如果 getUser() 返回空值这里会发生空指针异常。 建议在调用前增加判空逻辑或使用 Java Optional 封装。这种评论干净、准确、不掺杂没用的表达人工复核时很省力。3. 把Hermes放进真实仓库权限、触发规则与最小配置这一节讲实际部署时最容易踩坑的部分。负责过机器人账号或者 CI 服务的人都知道GitHub 的权限模型设计得很细也正因为细稍不注意就会把权限放大得离谱给仓库留下安全隐患。3.1 机器人账号与权限边界设置Hermes 一定不要使用个人访问令牌在 GitHub 上操作。个人访问令牌挂在某个真实用户名下一旦令牌泄露等于把你的个人账号权限给了别人。可靠的做法是创建一个独立的 GitHub App以机器人的身份运行。GitHub App 的好处是它的权限是针对仓库的细粒度授权可以只给它读取代码、写入评论的权限而不给它修改代码、管理用户的权限。一个比较收敛的权限配置长这样权限项权限值用途说明Pull requestsRead/WRITE读取 PR 信息、写入评论和审查意见ChecksRead/WRITE写入 Check Run 状态ContentsRead拉取文件内容和 diffIssuesRead读取 PR 关联的 IssueMetadataRead获取仓库基础信息与事件推送Commit statusesRead了解 CI 当前是否通过需要注意GitHub 在拉取私有仓库代码时机器人需要走 GitHub App 的 Installation Token。这个 Token 有效期非常短通常 1 小时不能像普通 Token 一样写死在配置里。需要实现一个用私钥换 Token 的过程流程是按 GitHub 要求的 JWT 签名方式生成 JWT再调用POST /app/installations/{installation_id}/access_tokens换取临时安装令牌。很多新手版 Hermes 卡住都是因为这一步返回 401。常见原因是本地系统时间不准确导致 JWT 的iat和exp时间校验失败。解决方式也简单先确保服务器时间已同步再把 JWT 的过期时间控制在 10 分钟以内。3.2 什么样的PR需要触发过滤规则与算力成本控制如果仓库里每个动作都触发一次 LLM 评审一天下来账单会很难看还会产生大量没有必要出现的机器人评论。所以在 Hermes 前面加一层过滤逻辑非常关键。我的过滤规则设计如下分支路径过滤一旦发现 PR 的 head 分支名以docs/或dependabot/开头就直接跳过。纯 Markdown 文档变更交给模型去评既浪费资源又没有意义。dependabot[bot]提交的依赖升级 PR 通常只动package.json和锁文件其变更内容也不需要通过 LLM 去推理。变更规模过滤如果 PR 改动超过一个阈值比如 500 个文件Hermes 会拒绝评审只留一条提示信息让维护者手动拆分或安排多人评审。大 PR 本身就是坏味道让 Bot“硬评”只会加剧问题。标题标记过滤如果 PR 标题里带有[skip ci]或[skip review]这类约定标记Hermes 会尊重它。这给予了开发人员临时跳过评审的入口毕竟有些 PR 是处于草稿阶段的中间提交不需要每次都评价。并发控制团队并行打开 PR 的数量超过一定值时Hermes 启用队列避免一次并发调用把模型服务打死。此外还有一层本地缓存的过滤如果某个 PR 我上一轮已经评过了目前没有新的 commit pushHermes 不会因为有人编辑了 PR 描述就重新评一次。因为描述修改通常不影响代码质量结论没必要重复烧钱。代码 diff 改变时也就是收到synchronize事件时才需要触发重新评审。3.3 小范围试运行如何让团队慢慢适应机器人的存在我在搭任何工具时最不推荐的做法是一上来就全仓库强制开启也不推荐直接将其设置为合并阻塞条件。团队成员对新事物的接受需要过程尤其是一个会“批评”别人代码的机器人。如果第一天它就在大家伙 PR 里挑出一堆刺别人很容易直接把它举报关掉。更温和的做法是用标签作为开关- 默认情况下 Hermes 不在任何 PR 上运行。 - 只有当 PR 被贴上 review-me 标签时Hermes 才会开始评审。 - 评审完成后Hermes 会移除 review-me 标签并打上 reviewed 标签。这样团队里愿意尝鲜的同学可以先在自己的 PR 上贴标签试试看到底有没有用。如果靠谱再逐步扩大触发范围最后才考虑接入分支保护规则。有人可能会担心“为什么我不直接无条件全开”。因为在信任还未建立的阶段工具产出偶尔不准确时你要面对的对话会变成“你搞的这个 bot 怎么这么蠢”而不是“它这几个建议挺有用”。有了标签开关用户可以自主选择即使出现误报它的受众也是主动邀请它的那部分人反馈心态完全不同。Hermes 正式上线后我在仓库的CONTRIBUTING.md里加了一段说明内容大致是Hermes 是代码评审助手它的评论只代表机器对现有代码的静态理解。如果认为某条评论是误报可以在评论下回复hermes-bot ignoreHermes 后续不会在本 PR 中重复这一意见。这个“ignore”机制是团队能够逐渐信任它的关键算是给了一个“说不”的入口。4. 与Actions、CodeQL和人工Review的分工不重复造轮子自动化代码评审这个方向并不新。GitHub 生态里早就有大量工具在做这件事CodeQL 做漏洞扫描ESLint 检查 JavaScript 规范SonarQube 做圈复杂度统计Dependabot 负责依赖安全更新。那 Hermes 这类基于大模型的评审服务和它们有什么关系这是很多人在规划时第一个产生的疑问。4.1 静态检查解决“确定性问题”LLM负责“模糊判断”传统静态检查工具的本质是基于规则或语义图模式的确定性匹配。以 CodeQL 为例它会把代码转换成一个关系数据库用 QL 语言去查询潜在的漏洞模式。如果代码里写的是executeQuery(sql)且参数是用户输入CodeQL 能很确定地告诉你这里有 SQL 注入风险。这类工具适合解决“符合或不符合特定模式”的问题。但它们处理不了开放性问题。比如这段算法是否在所有边界条件下都正确这个接口改名后调用链上是否所有语义都对齐了这位开发者的实现是不是与 PR 描述的意图一致这段代码在团队现有架构里是否引入了不必要的耦合这些问题没有固定的查询规则可以套用需要对“代码写的什么”和“作者想干什么”做语义理解。Hermes 的存在意义并不是要替代 ESLint 或 CodeQL。恰恰相反一个成熟的 Hermes 应当把静态检查已经能报出来的问题视为“公理”这些不需要 LLM 重复指出应当由独立 Runner 直接产生注释LLM 只负责分析静态检查器“看得见但看不懂”的部分以及它们完全看不见的设计层问题。我在实际实现里做了一个非常关键的分工问题类型谁负责示例语法错误、编译失败CI 编译器代码无法构建明显安全漏洞CodeQL / 依赖扫描SQL 注入、硬编码密钥编码风格和简单质量问题ESLint / Checkstyle未使用变量、格式问题逻辑缺陷与边界条件Hermes 辅助人工Review未判空、并发操作无锁、状态未恢复产品意图与实现的偏差Hermes 辅助人工Review描述说要做 A代码做的是 B架构耦合与设计取舍人工Review把新功能塞进通用模块是否合理这样设置之后Hermes 的评论里几乎不会再出现“这里少了个分号”这类低级信息能够把有限的算力和读者的注意力都留给真正的语义问题。4.2 为什么不直接把这个智能体写成GitHub Action动手初版时我也认真考虑过直接写成一个 GitHub Action。毕竟 Action 的部署成本最低仓库里放一个.github/workflows/hermes.yml服务器都不需要GitHub 自己托管运行器触发后直接在 Actions 里跑一个容器即可。但对 Hermes 这类“带状态、需要和外部模型服务交互、需要跨多次事件记忆”的工作负载来说Action 模式有几个明显的问题。第一Action 本质上是一个无状态的短暂任务。每次触发都会从零开始想要实现“上次 warning 有没有被修复”这类跟踪Action 只能去查询 GitHub 历史评论。问题在于查询本身还有 API 限流和一致性延迟状态管理写起来非常别扭。第二Action 访问外部大模型服务的稳定性不如常驻服务。GitHub 托管的 Runner IP 段访问外部接口会被限流或加验证如果你的内网模型服务并不公网暴露Action 基本连不上。即便调用的是公网 APIRunner 冷启动、依赖下载这些步骤也要额外消耗时间。第三无法做精细的并发调度和本地缓存。Hermes 如果做成常驻服务对所有仓库提交的评审请求在内存里就是一组任务队列。能提前对相同的 PR 去重、对相同文件的相同片段做 embedding 缓存这些优化在 Action 模式里根本没法实现。所以最合适的架构是Hermes 作为一个独立的 Web 服务部署在服务器上事件通过 GitHub webhook 推给它服务内部处理完后再通过 GitHub API 将评论写回。整个服务就像一个网关心跳只在有事件进来时才工作平时安静地待着。4.3 Hermes的评论必须以辅助材料身份存在团队里最重要、也最容易忽略的一项配置是Hermes 的评论内容永远不要直接作为合并评判标准。在设计上Hermes 的评论全都采用“建议”语气并且结尾附注一条固定说明“以上为 Hermes 生成的自动评审建议仅供人工复核参考。”这么做的原因很实际LLM 生成内容天然存在不确定性哪怕做到万分之一误报一旦评论内容被当作“机器权威”直接用来拒绝开发者的 PR工具的公信力就会崩塌。在这点上我更倾向于让 Hermes 的评论扮演“让人类 Reviewer 更轻松”的辅助者角色。可能某天团队已经完全信任它可以在分支保护规则中加一条“Hermes 的 check 若为失败则不允许合并”但在自动评审尚未被团队普遍接受时把它当成一个“话多且细心的初级工程师”就好采纳与否仍然由人决定。5. 踩坑记录重复评论、冲突与“看起来对实则错”的修复任何自动评审工具做出来后真正消耗时间的不是写评审逻辑而是处理各类“现场事故”。5.1 多轮push后旧评论残留成噪声Hermes 第一次上线时遇到的最突出问题是评论重复。一个 PR 通常要经历好几轮 commit 修改每次新的 commit push 都会触发重新评审。假如第一轮评审时Hermes 在UserService.java发现了一个问题并在代码行上评论了开发者也看到了这个问题然后在第二轮 commit 里修复了。按理说 Hermes 应该闭嘴。但现实是Hermes 无法自动感知“开发者的 commit 是否修复了它之前提过的问题”。如果它的状态管理不到位第二次评审时会把上一轮的评论原样再发一遍。就算开发者已经修复旧的行级评论也不会消失它依然挂在旧 commit 对应的代码行上让人误以为当前代码还有问题。要想正确处理这类问题光靠 GitHub 自带的“outdated”标记是不靠谱的因为它仅代表评论所在的行在最新的 diff 里不存在了并不能判断问题是否真的被修复。Hermes 的处理方式分两步第一步在任何代码行评论发送前查询该 PR 下已经发布的评论判断同一文件同一行是否已存在 Hermes 发起的问题标签。如果已存在直接不重复发只更新旧评论的状态。第二步当收到synchronize事件时Hermes 会在内部重新生成一遍完整的问题列表并把结论与旧列表做对照。之前出现但当前不存在的问题Hermes 会在评论里追加一条内部状态“该问题已在新版本中消失”而不是保留原有的报错式评论继续误导人。后来我发现一个更通用的经验所有自动评审工具都应该把“状态是否仍然成立”当作一等公民。评论要能关闭、能恢复、能轮转而不是只能新增。这个问题不解决工具在时间维度上的可信度就永远是负的。5.2 PR被插队发生冲突后评审上下文失效项目热词里有一条“git pr被插队导致冲突怎么处理”在做自动评审时也遇到同款问题只是处理者不是人而是机器。场景是这样的开发者开了一个 PR当时 Hermes 基于当时的 base 分支快照做了完整分析一切正常结论是“可以安全合入”。但几个小时后另一个更大的 PR 先被合进了主干把当前 PR 涉及到的公共模块冲突了。这时候原来的评审结论已经完全失效——因为当前 PR 的 head 分支可能还没有合并最新的主干代码它的 diff 已经不是将要被合入的那份内容了。如果此时 Hermes 没有意识到 base 分支已经前移它给出的建议就建立在过期的时间点上可能基于旧行号给出的评论位置完全错位也可能分析的是旧版本代码而开发者的本地已经改成了另一种写法。解决这个问题的关键要素是“merge-base 检测”。PR 的base.sha一旦变化Hermes 必定要重新拉取 compare 数据重新计算一次 review。但也不能在 base 分支每次变动时都盲目重跑。实现时 Hermes 会持续监听pull_request的base ref变更事件一旦它从 GitHub 侧感知到 base sha 有变化立刻进行一次强制重新分析。在自动评审逻辑中Hermes 一定会把“冲突状态”作为一种前置输入条件参与到 Prompt 中注意该 PR 当前与目标分支存在代码冲突冲突文件为 - src/main/java/com/example/core/CacheManager.java - src/main/java/com/example/controller/OrderController.java 请优先评审冲突区域因为冲突区域很可能隐藏合并语义错误。这样处理会比把冲突丢给开发者自己去解决更有价值。冲突本身 Git 能处理但冲突解决时引起的业务逻辑丢失普通工具发现不了Hermes 正好可以针对这部分做深度审。5.3 “看着对但因误报”和“看起来对实则错”的双向挑战误报是自动评审工具最容易遭人吐槽的问题但真正调试过之后我发现更难解决的是“看起来对实则错”。误报的例子经常是这样的Hermes 看到一段加了双重检查锁定的代码基于训练数据中“双重检查锁定在某些 Java 版本中容易出错”的常见记忆直接给出警告说这里可能有线程安全问题。但真实情况是这段代码的变量已经被声明为volatile且团队已经做过充分并发测试。这就是没有结合完整上下文导致的错误判断。Hermes 只看到了片段而不是整个同步策略。另一个方向则更隐蔽。例如在评审一个支付相关的 PR 时代码里写了一个对账接口的调用条件if (order.getStatus().equals(OrderStatus.PAID)) { reconciliationService.sync(order); }表面看没什么问题。但 Hermes 结合 PR 描述“我们希望所有已支付超过 24 小时的订单才被重新对账”后发现代码里根本没有增加“超过 24 小时”的判断。这个 bug 不是语法错误也不是空指针而是实现与业务需求的语义不一致。只有当模型尝试理解“为什么这么写”时才能发现问题。处理“看起来对实则错”没有银弹Hermes 的策略是主动降低“肯定性判断”的频率只有当 PR 描述、关联 Issue、代码上下文三者同时明朗时Hermes 才会说“这似乎与预期行为不一致”。其他情况下Hermes 更倾向于使用提问句式“这个条件判断是否需要覆盖order null的场景方便确认一下预期行为吗”这种提问式评论给开发者的感觉不是被机器挑刺而像多了一个可以随时拉来讨论的同事。也正因为这种语气Hermes 在团队里的接受度比我预想中高了不少。6. 让Hermes更懂你们团队规则注入与人工反馈闭环一个通用代码评审智能体只能成为通用工具真正让它变得有价值的是它是否理解你所在团队特有的业务约束和编码习惯。同样的 PR扔给通用模型可能只会给“大而全”的泛泛建议而团队真正需要的是“这里为什么不能直接用 Transactional因为我们的压测环境不允许嵌套事务”这种贴合自身技术栈的提示。6.1 把团队规约变成可查询的约束文本Hermes 的规则来源分两块。第一块来自公开的通用最佳实践第二块来自我手动维护的团队规则文件。一开始团队规则文件里写的是这样的条目1. 所有对外接口必须显式参数校验禁止仅依赖数据库约束。 2. 新增依赖时必须在 PR 描述中说明引入原因和替代方案调研结论。 3. 禁止在 try-catch 块中吞掉异常后返回 null如需兜底必须打印完整堆栈。 4. Redis 缓存更新必须与数据库写操作保持同一事务边界。 5. 所有与外部系统交互的网络调用必须设置超时时长。Hermes 在启动阶段会加载这个规则文件并按照固定模板组装成一个“团队约束”片段在每次发起评审请求时注入到系统 Prompt 中。有了这些规则Hermes 的评测标准就不仅是“良好的代码通常应该这样”而是“这个团队要求必须这样”。从技术实现上规则文本内容不需要是纯自然语言Markdown 即可。实际跑下来感觉规则条目控制在 30 条以内效果最好。太多的话语言模型会“消化不良”有些规则会被稀释在长文本里太多细碎的规则也不适合放在系统 Prompt 中比如“私有方法命名必须加下划线”这类应该交给静态检查解决。6.2 减少评论噪音提高信息密度的三条经验如果 Hermes 开着的默认行为是“每发现一条可疑点就评论一次”代码注释容易泛滥。根据一个“让评论有含金量”的期望我会要求 Hermes 在评论前先做一轮聚合筛选相似度聚合十条内容相近的警告在摘要里合并成一条并列出影响的全部位置。严重度分级Hermes 只对高严重度问题发单行评论中低严重度问题的位置只记录到最后的摘要报告中不单独打断开发者。沉默是金如果 Hermes 分析结束之后没有找到超过阈值的问题它什么都不发布连“LGTM”这句话也不发。要让人类相信“机器人只在有问题的时候出现”而不是像打卡机器一样在每个 PR 底下都留个爪印。做完这三件事PR 评论列表里的信息密度明显提升了。后来大家看到 Hermes 评论时的态度也从“这破机器人又说废话了”变成了“Hermes 新提了个问题我看一下”。6.3 Reviewer 的反馈如何转化为Hermes的长期记忆前面说过要让 Hermes 支持“ignore”反馈但这只是最低层次的机制。更进一步的想法是让它从人类的评审意见中学习。当人工 Reviewer 针对 Hermes 的某条评论回复“误报”并说明原因时Hermes 会把这轮对话写入一个“训练样本候选池”。当时机成熟积累了足够多优质反馈后可以通过微调模型或优化 few-shot 示例来把知识固化下来。这一层在初期不必做太重因为它依赖 Langfuse 之类的追踪平台要消耗额外的工程成本。但对长期演进来说这是唯一能摆脱“每换一套代码库都要从头调规则”的路径。我自己目前的做法更轻量让 Hermes 把“人工驳回最多的评论类型”记录下来并按周汇总。汇总结果会发到团队的周会文档里人工看一眼就知道它经常在哪里误判并在规则文件里针对性地补齐排除条件。事实证明这个轻量反馈闭环已经能解决绝大部分重复误报。7. 现阶段的实际工作流与后续扩展想法最后聊一下现在团队里 PR 流程实际长什么样。曾经的流程是开发者 push 代码 → 创建 PR → CI 跑测试 → 团队指定 Reviewer → Reviewer 手动逐行查看 → 给出评论 → 开发者修改 → 再次 Review。如果遇到 Reviewer 忙或者大型改动这段流程可能拖很久。引入 Hermes 后变成开发者 push 代码 → 创建 PR → CI 跑测试的同时Hermes 自动启动预审 → 2 分钟左右给出摘要和风险提示 → 人工 Reviewer 收到通知时已经把摘要看过一遍能直接带着 Hermes 的标注进入代码细节 → 人工把精力集中在有争议处进行判断。这样说起来似乎只是加了一步但带来的改变很直观人工 Review 的“首屏时间”从十五分钟级别缩小到三分钟内。哪怕 Hermes 的结论偶尔不准它也至少帮人类完成了一部分“阅读代码并回忆上下文”的工作。另一个被低估的价值在于它可以随时待命。多年前端/后端的例行 PR很多发生在深夜或其他人时区。之前这些 PR 会一直等到对应维护者上线才能获得首个反馈。Hermes 不睡觉不摸鱼不因疲劳而降低标准它对所有 PR 的处理质量和耗时基本一致。7.1 我的使用建议别让它做“一个人干完全部事”的幻想对那些想复制这套方案的人我个人的核心建议是把它看作一套“AI 配对程序员”而不是“AI 评审法官”。确保 Hermes 开评前先取到 PR 描述和关联 Issue没有上下文的评审会让团队反感。确保它每次评论前都判断一下“这条记录是否值得单独出现在行级评论里”而不是把所有内容一股脑全部输出。给它一个能够被人叫停的入口无论是 ignore 标签还是特殊指令否则工具就会成为流程里的束缚。如果你正在计划做类似的东西建议不要重复造轮子。可以在 GitHub 生态中寻找流程引擎的基础能力把主要精力花在调整自己的评审维度、团队规则、输出格式上。Hermes 这个名字只是一个代号核心价值在流程设计里不在命名上。7.2 后续可以继续深挖的方向我下一步想做的扩展有两个方向。第一个方向是接入“提交阶段”的提前介入。现在 Hermes 是在 PR 级别评审已经能及时发现问题。但如果在开发者执行git push之前就触发一次本地评审相当于在代码还没出你电脑时先过一遍基础检查成本更低、反馈更快。Git 的pre-push钩子可以做这件事难点在于如何不打断开发者的流式工作节奏。第二个方向是跨 PR 的历史记忆。现在 Hermes 在一个 PR 内能记住问题和修复状态但在多个 PR 之间它没有记忆。如果能在每次评审结束时把“这个模块的常见问题”沉淀下来下一次改动同一模块时Hermes 可以提前提醒“你上次提交时在缓存清理逻辑里经常漏删 key这次请重点检查”。要做到这点需要一个长期向量存储库以及维护得足够干净的历史评审记录。说白了Hermes 这样的工具很像刚加入团队的实习生。你让它独立负责一个重要模块它大概率会让你失望。但如果安排好规则、边界、反馈机制它就能勤勤恳恳地做掉大量重复繁琐的准备工作把团队整体的评审节奏带快。这也是我目前对自动化代码评审最真实的体感。

最新新闻

日新闻

周新闻

月新闻