"你这个PR为什么没有写测试?""这个函数为什么不直接返回?""这里是不是应该有缓存?"——如果你也像我一样,每天要被这类review意见轰炸,同时又觉得自己看别人代码时根本没时间细读,那这篇东西应该是你的菜。我用Hermes做了一套GitHub PR自动化代码评审,不是那种挂个机器人卖萌的玩具,而是一条真正能跑在仓库事件流里的智能体链路:从PR打开的那一刻起自动拉取diff、做语义级分析、按规则过滤噪音、在关键行上回评论,全部无人值守跑完。这套东西在我这边已经稳定运行了三个多月,累计评审了几百个PR,接下来我会把选择理由、链路设计、规则打磨、踩坑记录和团队落地方式完整拆开讲,适合正在评估AI代码评审方案、或者准备自己搭一套的技术负责人和爬过不少坑的开发者。
1. 为什么我最终选了Hermes做自动评审
1.1 人工review在快节奏迭代里藏不住的问题
先说个大实话:大部分团队的Code Review是在表演,不是在评审。代码写的越来越快,PR越来越小,reviewer却越来越忙。我之前统计过自己团队的数据,一个PR平均要等11个小时才有第一条人工评论,等到全部讨论结束合并,基本要跨两个工作日。这期间开发者在干什么?要么切到下一个任务让上下文在脑子里断掉,要么就在那干等。
人工评审还有两个致命伤。第一是reviewer注意力是随机的,看到哪算哪,最关键的架构问题往往没人说,反而揪着缩进和命名不放;第二是人会碍于情面不把问题说透,尤其刚入职的新人或者协作方写的东西,有经验的同事心里觉得不行,嘴巴上却说"整体没问题"。这些都是制度问题,靠团队文化短期根本解不了。既然人不可靠,我就把目光转向自动化。
我试过传统静态扫描工具,ESLint、SonarQube、CodeQL这类的确能拦掉低级错误,但它们的本质是"已知模式的匹配器",发现不了跨文件、跨函数的语义问题。举个例子,一个接口的参数从单个ID变成了整个对象,静态工具只会报类型不匹配,不会去追调用方是否还有隐式的顺序依赖。而这类问题恰恰是review里最有价值的部分。
1.2 Hermes的定位:不是又一个Lint,而是一个编外Reviewer
选择Hermes的核心原因,是它的定位不在一行代码的规范上,而在"看得懂上下文"。它的工作方式是接收PR的完整上下文——标题、描述、文件变更、diff、关联issue,然后像一个人一样做判断:这段逻辑是否重复、这个接口变更有没有漏改调用方、异常处理是不是吞掉了关键错误。这些事情本质是语义理解,不是正则匹配,所以必须由大模型驱动的agent来完成。
另一个让我下决心的点,是Hermes的设计允许自托管部署。代码评审这件事,绕不开仓库权限和代码资产安全,很多团队不敢把内部代码送到商业SaaS的服务器上做分析。Hermes以智能体的形式跑在自己的基础设施里,代码只经过自己的服务端和所接的模型服务,访问边界完全可控。对于把代码安全当红线的团队来说,这一点比其他功能都重要。再加上它的规则系统是可编程的,你可以把团队的评审规范直接写进配置,而不是依赖线上prompt不可见、调整还要排期的黑盒产品。
1.3 和GitHub Copilot等现成方案的直观对比
做方案对比的时候,GitHub官方路线肯定是绕不开的。Copilot现在确实能在PR页面上给出自动review建议,开箱即用非常方便,但对我的场景有两个硬伤:一是它跑在GitHub的云环境里,仓库代码默认会被处理,合规那边直接一票否决;二是它的评审风格偏"通用助手",你很难告诉它"我们团队的异常处理规范是什么、禁止评价哪些东西、什么级别的意见必须block合并"。
我把三者的差异整理成了下面这张表,看完应该就很清楚了:
| 对比维度 | 传统静态扫描 | GitHub Copilot原生PR建议 | Hermes自托管PR审查Agent |
|---|---|---|---|
| 检查能力 | 模式匹配,只能查已知规则 | 语义级,能理解上下文 | 语义级,且支持自定义规则 |
| 部署位置 | 自己CI或云端 | GitHub云环境 | 完全自托管 |
| 代码出域 | 看配置,但规则基本是共识标准 | 代码会进入GitHub环境处理 | 代码只留在自己服务边界内 |
| 规则定制 | 需要写死正则/AST规则 | 有限,靠系统prompt | 自由编程,团队规范直接落地 |
| 评审姿势 | 输出清单 | PR页评论 | 行级评论+整体总结+状态检查 |
| 噪音控制 | 很差,误报多 | 普通 | 可配置置信度、分级、豁免规则 |
这张表列完,选型其实已经结束了。剩下的问题只有一个:Hermes这套链路到底怎么搭起来跑顺。下面就是整个架构的拆解。
2. 从PR创建到评审落地的完整链路
2.1 触发方式:GitHub App的Webhook是正路
整套链路的第一环,是"怎么知道一个PR被创建了"。Hermes支持两种接入方式,一种是用GitHub App注册到仓库,另一种是用个人访问令牌(PAT)去调用REST API。我强烈建议用GitHub App,原因有二:权限颗粒度完全不同,App可以只申请Pull requests和Checks两个权限,而PAT在大多数情况下被团队管理员限制得很死,甚至拿不到读代码的权限;App的身份是独立的"机器人",它发的review评论会带上Hermes的标识,不会被误认成某个同事说的、也不会有"为什么小张给了一个和自己代码风格相反的意见"这种尴尬。
事件监听我关注四个类型:pull_request里的opened、synchronize和ready_for_review,外加pull_request_review里的submitted。opened管新建PR,synchronize管代码更新,ready_for_review管从草稿转正式,submitted用于监听人类reviewer已提交意见,Hermes可以基于别人的评论补充技术验证,这算一个进阶玩法。事件进来之后,Hermes先去查一遍这个PR的元数据,判断它是否命中需要评审的条件,比如来自fork的PR要不要评、draft状态的PR要不要评,通常这两个默认都是不评的。
2.2 上下文组装:从diff到模型能理解的评审材料
事件拿到手只是起点,真正的难点是把"PR长什么样"完整告诉Hermes。我不建议直接把原生diff丢给模型就完事,因为GitHub的diff默认不带完整上下文,模型容易看着"删除了一行"就去评论,实际上删掉的那个函数在文件后面还有大段定义,它根本看不见。正确的做法是走GitHub的Contents API,把涉及变更的每个文件的最新版本内容抓下来,然后在本地做一次diff计算,把每个hunk前后各扩展20到30行上下文作为补充材料。
组装评审材料时,我按下面这个顺序打包给Hermes:
- 仓库的编程语言、核心框架、工程目录结构说明
- PR标题、描述、关联issue的标题和关键内容
- 变更文件的完整列表,标注哪些是新增、哪些是修改、哪些是删除
- 每个文件的高亮diff段,带扩展上下文
- 本次PR的变更统计,比如新增多少行、删除多少行,便于模型判断改动规模
这个打包过程还有个必须处理的点:忽略文件。依赖锁定文件、构建产物、自动生成的代码、vendor目录,这些一旦进到diff里,轻则浪费token,重则让模型完全走偏。我维护了一份默认忽略规则,同时在仓库根目录支持.hermesignore自定义,效果类似.gitignore,但专门服务评审上下文。
2.3 评审动作:行级评论和整体总结分开做
材料组装完,Hermes就会开始第一轮"文件级评审"。为什么不是一次性把所有文件都丢给模型让它一口气评完?因为实际测试下来,文件数量一多,模型对前面文件内容的记忆会急速衰减,后面的评论质量明显下降,还会出现把A文件的变量名写到B文件评论里的诡异情况。分文件处理之后,再把每个文件产生的评审意见汇总起来做一次整体总结,准确率高得多。
行级评论通过POST /repos/{owner}/{repo}/pulls/{pull_number}/comments这个接口创建,每个评论必须带上path和line参数,也就是定位到具体的文件和代码行。Hermes会把评审意见里的每个观点先转成结构化数据,包含文件、起始行、结束行、严重级别、问题类型、原始评论文本,然后逐条去调评论接口。除了行级评论,Hermes还会在PR末尾发一段整体总结,分成三块:本次PR核心变更概述、发现的关键风险点、给人类reviewer的建议关注范围。最后,通过Checks API创建一个check run,如果有BLOCKING级别的问题,状态设为failure,否则设为success,让分支保护规则可以基于这个状态决定是否允许合并。
2.4 增量评审与重复评论的兜底策略
评审不是PR创建时跑一次就完了。开发者看到AI评论后大概率会改代码,push新commit又触发synchronize事件,如果每次都对整个PR重新评一遍,会出现两个问题:一是历史评论位置已经不在最新代码上,行号全部错位;二是同一个问题被反复评论,开发者会直接爆炸。
我的做法是维护一张pr_review_state表,记录每个PR的每个文件每次评审时模型的输出摘要,包括问题指纹和对应的diff hash。新版diff进来后,先计算和上次评审的差异,只把新增或发生变化的diff段交给Hermes做增量评审。对于相同问题的判断,我给每条评审意见生成一个基于代码片段和问题类型的语义指纹,如果在历史评论里找到了相同指纹,就不再发重复评论,而是给已有的评论点一个👍或者追加"该问题在当前版本仍然存在"的轻量回复。这套机制跑起来之后,重复评论率从最初的35%降到了不到5%。
3. 让AI不乱喷的评审规则设计
3.1 先给Hermes定一个"人设",而不是放养它
很多人在让大模型做代码评审时,prompt就写一句"请审查这个PR,找出问题",结果模型进入"谄媚模式",每条评论都像面试里夸赞候选人一样,全是空话。我这边第一课就是:你必须给Hermes定义一个明确的评审人设和边界。
我们在规则系统里定义的角色是"一个在这个仓库工作了两年半的资深开发者,熟悉团队的代码规范和历史架构决策,只关注值得花人力去改的问题"。这里的关键词是"只关注值得改的"——不是把模型训练成一个找茬机器,而是过滤掉那些改了也没意义的东西。这段人设会作为系统级指令,在所有评审任务前固定注入,效果立竿见影,模型输出的垃圾评论量直接少了一半。
3.2 噪音评论的硬过滤规则,写在prompt之外
光靠prompt约束模型不可靠,模型本质是概率输出,今天就遵守规则,明天一个措辞变化就可能放飞。我在Hermes的规则层里加了一套不依赖模型自觉的硬过滤规则,任何评审意见在发送前都必须过这一层。这些规则用YAML声明,类似下面这样:
noise_filter: deny_phrases: - "建议优化" - "建议重构" - "性能可能会受影响" - "代码风格建议" min_line_count: 8 max_comment_length: 500 require_actionable: true allow_praise: false ignore_files: - "*.lock" - "package-lock.json" - "dist/**" ignore_patterns: - "\\b(TODO|FIXME|HACK)\\b"逐条解释下这些规则的意图。deny_phrases把那些常见的"AI空话"直接掐死,只要评论里出现这些短语就不发,防止模型输出"建议优化性能"这种说了等于没说的话。require_actionable是强制要求评论必须包含可执行的修改建议,且建议不能是"请优化"这种,而最好是能指明改哪个函数、加什么判断、参数怎么调整。allow_praise设为false,不允许模型发"这段代码写得很清晰"之类的废话,AI的认可对开发者来说没有价值,反而会让真正的问题被淹没。ignore_patterns里的TODO和FIXME是故意放过的,团队成员已经用注释标记了这些问题,AI再评论一遍纯属噪音。
3.3 严重级别:让AI的意见有明确的分量
评审意见最怕的就是全部一个等级,开发者不知道哪条必须改、哪条可以忽略。Hermes把意见分成三个级别,并且每个级别在评论里都有显式标签:BLOCKING(阻塞合并)、SHOULD-FIX(应该修改)、NIT(吹毛求疵级别)。这个分级不是让模型随便定的,我在指令里给了明确标准:
BLOCKING只允许用在三类问题上:会导致线上故障的缺陷、明显的安全漏洞、以及会让本次PR功能无法按预期工作的错误。搞笑的是,我刚上线这套分级的时候,Hermes会把所有意见都标成BLOCKING,整个PR被它锁得没法合并。后来在指令里加了"如果你无法用一句话说明这个问题为什么会导致线上故障,那它就不是BLOCKING",效果才正常。NIT级别则要求模型主动控制数量,一个PR里NIT意见不超过三条,因为NIT的本质是"不改也行",发多了只会让作者心烦。
3.4 一个经过验证的评审指令模板
把规则层和指令层分开之后,我沉淀了一套可以直接抄走的prompt核心模板。这里不涉及任何团队私有信息,你拿去改改就能用:
你现在是仓库{repo}的资深代码评审员。 请基于我提供的PR信息、diff和文件上下文进行评审。 评审时必须遵守以下规则: 1. 只评论你确信的问题。如果没有把握,保持沉默。 2. 每个评论必须包含三部分:问题定位、根因分析、具体修改建议。 3. 修改建议必须指明修改位置(函数名/代码块)和修改方向,禁止"请自行优化"。 4. 如果发现跨文件影响,在评论中明确列出受影响文件。 5. 禁止评论代码风格、缩进、命名等静态工具已覆盖的内容。 6. 禁止输出赞扬性内容。 7. 按BLOCKING/SHOULD-FIX/NIT分级,标准如下: - BLOCKING:可能导致线上故障、安全问题、或者功能无法实现 - SHOULD-FIX:虽然不是致命缺陷,但违背最佳实践或可能导致潜在bug - NIT:非必要修改,每PR最多三条 8. 如果某个问题在历史上已经讨论过并有意保留,不要重复提出。我在多个仓库里用这套模板跑了几个迭代,它最大的作用是给模型定了一个"少说但说准"的基调。实际评审质量不能只看单个PR的感受,还要看后面统计反馈闭环的部分。
4. 实测运行三个月踩过的坑
4.1 解释性注释被当成"坏味道":典型的上下文缺失
上线第一周,最离谱的一次事故是Hermes在一段处理支付回调的复杂逻辑里,把开发者的解释性注释批成了"无用注释,建议删除"。那段注释解释了为什么这里必须延迟50毫秒再处理下一个状态机、以及延迟是为了配合第三方网关的最终一致性。作者看到评论直接炸了,在群里问"这AI是不是不懂业务"。我打开评审日志一看,原因很清楚:组装上下文时只带了diff和文件片段,没有把相邻的历史代码、相关文档带进去,模型完全没看懂那段注释背后的业务含义。
排查链路是这样的:先导出了那条评论对应的请求日志,发现送入模型的上文里只有120行代码diff,注释所在函数的前后几十行根本没有;再进一步查prompt,发现系统指令里没有对"解释性注释"这类常见合法模式做豁免。根因确认之后做了两个修复:一是在指令里显式声明"当代码涉及复杂业务逻辑、并发处理、外部系统对接时,解释性注释是必要且合法的,不得建议删除";二是把模型温度从默认的0.7下调到了0.2,让输出更保守、更聚焦文件内的事实。
4.2 大PR直接吃满Token,评审中途崩溃
跑通之后的第二批问题来自超大PR。有次同事把一次数据库迁移、一个模块重构和二十多个文件的测试改动全塞进了一个PR,总diff超过8万字符。Hermes在组装上下文时把这些全部打包送给了模型,结果请求直接超出上下文窗口被拒绝,整个评审链路静默失败——没有任何评论,也没有check run,PR就那么明晃晃地摆在那里没人评。
排查的时候我先看的日志,发现模型调用接口抛出了max_tokens相关的错误,紧接着又做了个对照测试:手工把那个PR的文件列表摘出来,发现接上文时没有做变体筛选。修复方案分三层:第一层,在上下文组装阶段对大型PR强制启动"分块评审"模式,按文件维度切块,每块之间增加"这是同一个PR的第N块"的衔接信息;第二层,设置单次评审最大token预算,超过预算的PR自动跳过非关键文件的详细分析,只做整体结构和风险扫描;第三层,增加失败重试和告警,模型调用一旦失败,Hermes会在PR里发一条"评审失败:上下文超限,已降级为标准规则扫描",同时给管理员推一条告警,避免静默故障。
4.3 并发触发的API限流与评论风暴
有段时间组里做一次大规模重构,十几个PR几乎在同一时间点被创建。Hermes本来是按PR维度做并发评审的,结果十几个请求一起打到模型服务和GitHub API上,直接触发了官方的限流策略。最尴尬的是,GitHub对评论接口的限流是按仓库和用户维度算的,因为所有评论都是用Hermes这个App的身份发的,一个PR的评论还没发完,另一个PR就开始报secondary rate limit错误。
排查下来,问题出在我没有做任何流量整形。修复方案是在Hermes里加了一个简单的队列调度器:全局一共允许三个PR并发评审,每个PR内部行级评论的创建速率控制在每两秒最多五条,超出的排队等待。同时给GitHub API调用加上了指数退避重试,Retry-After响应头里的值作为基准等待时间。这套调度上线后,再也没出现过批量限流。值得多说一句的是,这种并发问题在测试环境几乎踩不到,因为只有真实的一天十几个PR涌进来才能暴露调度器的脆弱,所以如果你也要搭类似系统,建议上线前用历史PR数据做一次回放压测。
4.4 评论疲劳:每个PR十条AI意见,作者快麻了
有一个阶段Hermes的评论量很大,每个PR平均十条意见,而且大部分是SHOULD-FIX级别的"潜在优化"。表面看AI很勤奋,但当开发者连看十条都发现"改了也行、不改也没事"之后,他会对整个AI评审失去信任,连真正重要的BLOCKING意见也不看了。这就是典型的"评论疲劳",和我们手机上推送通知越来越多却越来越不想看是一个道理。
我的修复策略很简单:把评审的默认目标从"找出所有问题"改成"找出不要让这个PR带着隐患上线的那些问题"。在规则里新增了一条:如果一个问题不能明确地描述出它会导致什么具体故障场景,那就不允许SHOULD-FIX级别以上的评论出现。同时把意见总数做了硬性上限,单个PR最多输出八条意见,超过上限的意见进入一个隐藏的"草稿箱",只有当作者主动说"给我更详细的建议"时才会展示。这个改动之后,PR上的评论数降到了平均四条,但作者评论里明确说有帮助、并照着修改的比例反而提高了近一倍。
4.5 权限边界:App权限开大了一点点,就被安全同事找上门
用GitHub App接入时,我一开始直接用官方示例里的默认权限模板,包含Contents读写、Pull requests读写、Checks读写。上线第二周,安全同事发来一条告警,说这个App有权直接往默认分支推送代码,虽然实际没用,但权限面明显超出"只做评审"的最小需求。我这才认真翻了GitHub官方文档,发现Pull requests的write权限不仅包括发表评审意见,还包括修改、关闭甚至合入PR的能力。
排查链路不复杂,就是反复核对App需要的权限和接口文档里的权限要求。最终我把权限收敛到这个清单:Contents只保留read,需要拉取文件内容做上下文;Pull requests保留read和write,因为要创建行级评论和整体评论必须用写权限;Checks保留read和write,用于创建check run;Issues用read,用于读取关联issue的标题;Metadata自动附带read,这是GitHub App的强制项。收敛完之后,权限面满足了"只能看、只能评、不能合"的目标。这步排查也给我提了个醒:接任何外部工具到代码仓库,权限最小化不是安全团队的额外要求,而是基本盘。
5. 落地团队的协作流程与配置清单
5.1 哪些PR必须走Hermes,哪些可以不评
不是所有PR都值得AI评审。在实际落地中,我把PR分成了三类,分别走不同的策略。第一类是核心服务的主分支变更,强制Hermes评审且BLOCKING意见会阻塞合并;第二类是feature分支合并到develop的常规PR,Hermes照常评审,但BLOCKING只作为建议,最终决策权在人类reviewer手上;第三类是文档更新、依赖版本升级、配置修改这类低风险PR,默认跳过详细评审,只跑一个基本的变更检查。
这个分类直接在Hermes的配置里用路径规则和分支规则表达。比如依赖升级类PR,diff里全是package.json和package-lock.json的变更,Hermes的语义分析几乎没有用武之地,跑了也是浪费token。我用一个简单的路径匹配规则把这些PR全部排除,效果立竿见影,每天消耗的token直接降了四成。这里的思路是:自动化评审不是越多越好,而是要把算力花在风险最高的地方。
5.2 人类reviewer和AI的分工边界
AI评审不是替代人工review,而是把人工从"找茬模式"里解放出来,让它专注在AI做不了的事情上。我团队里现在执行的流程是:PR创建后,Hermes先跑,几分钟内给出评论和整体总结,开发者收到Hermes的提示后当期就改一轮;等代码稳定了再找人类reviewer,人类只review那几处被Hermes标记为高风险的地方,以及Hermes不擅长的架构决策、业务语义、长期演进这类问题。
这样分工给团队带来的最直观变化是,人类reviewer平均看一个PR的时间从二十分钟缩短到不到十分钟。因为相当一部分低级问题、边界遗漏、异常处理的缺失已经在AI阶段被拦住了,人类reviewer不需要重复这些"低水平的辛苦"。但我也给团队定了一条铁律:Hermes说"没问题"不代表真的没问题,它没有通过测试确认行为的能力,在并发、性能、数据一致性这些领域仍然需要人类做判断。AI可以作为第一道过滤网,但绝不能成为"代码质量正确性"的最终裁决者。
5.3 一套可以直接照抄的配置文件模板
走到这一步,我把自己团队目前在生产环境跑的Hermes配置脱敏后整理了一份,你可以直接对着改。整个配置分成三块:接入设置、评审行为、消息通道。
app: mode: github_app app_id: 123456 private_key_file: /etc/hermes/hermes.private-key.pem webhook_secret: ${HERMES_WEBHOOK_SECRET} install_filter: organizations: - your-org repositories: - core-service - web-frontend review: auto_review: enabled: true on_events: [opened, synchronize, ready_for_review] skip_drafts: true skip_fork: true skip_paths: - "*.md" - "**/package-lock.json" - "dist/**" - "vendor/**" model: provider: your_model_provider base_url: http://hermes-model-proxy:8080 model_name: your-review-model temperature: 0.2 max_tokens: 4000 timeout_seconds: 90 severity: blocking_on_check: true max_comments_per_pr: 8 queue: max_concurrent_prs: 3 comment_rate_per_second: 2.5 notifications: admin_webhook: ${ADMIN_WEBHOOK_URL} on_failure: true on_rate_limit: true metrics: storage: sqlite db_path: /var/lib/hermes/hermes.db几个要注意的关键项:model.temperature必须压低,0.2是我调过一轮之后的稳定值,太高会让模型输出发散、过度发挥;queue.comment_rate_per_second是前面提到的限流保护,2.5这个值实测不会触发GitHub的次级限流;max_comments_per_pr是防评论疲劳的硬闸。还有一个容易忽略的skip_fork,强制设为true,因为来自fork的PR通常没有完整上下文,而且提交者不受团队规范约束,AI评审意义不大,反而可能产生误导。
5.4 用数据做反馈闭环,而不是凭感觉调整
最后我要强调的是,评审规则不可能一步到位,必须靠数据迭代。我在Hermes里加了两个关键统计维度:意见采纳率和评审精准度。用户在PR页面上对某条AI评论点"回复"或"确认修改",就视为采纳;反之,如果作者明确回复"这不是问题"或者关闭不予处理,则记录为一次误报。每天自动汇总一个表格,展示不同问题类别(安全、空值、并发、性能、可读性等)的采纳率和误报率。
复盘的时候,魔改方向就非常清晰了:如果某个类别采纳率低于30%,说明规则和指令与该类别不匹配,要么是这个问题判定标准不对,要么是Hermes误把FYI当成了问题,我会针对这类问题做精准的prompt修正,然后在样本集上做回归测试。相反,如果某个类别采纳率很高但评论总数很少,说明这个方向是团队真正感到疼的,我会把相关规则权重提高,让Hermes更积极的挖掘同类问题。三个月下来,整体意见采纳率从最初的45%升到了67%左右,这个数字对AI评审来说已经算是相当能打的了。
如果在搭这套系统的过程中只能选一条经验带走,我想说是:AI review真正难的地方从来不在调用模型,而在于让模型知道什么话值得说、什么话不该说。你能把这条想透,Hermes就能从一个话痨助手变成团队里真正靠谱的那双眼睛。