十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

基于Hermes智能体的GitHub PR自动代码评审实践 代码评审是研发流程里最耗时的一件事做了这么多年开发我越来越觉得“写代码”反而不是瓶颈“review代码”才是。每次PR堆积起来团队成员要么抽不出空细看要么看一遍只回一句“LGTM”真正有问题的逻辑漏洞、越权接口、硬编码密钥反而被放过去了。我最近把公司内部的GitHub PR审查流程接到了Hermes智能体框架上让AI先做一轮自动代码评审再把结构化结论回写到PR评论里。跑了将近三个月能拦住的问题比想象中多很多这里把整套思路、踩过的坑、以及可直接复用的配置和代码都整理出来。这个方案适合谁只要你的团队在用GitHub做代码托管PR是常规协作方式同时又希望在不引入重型商业产品的前提下用低成本的方式把代码评审质量提上来这篇文章基本都能帮上忙。我会尽量绕开抽象的概念直接讲清楚Hermes是怎么和GitHub打交道的、审查规则怎么定、输出格式怎么控制以及上线之后那些文档里查不到的坑。1. 项目整体思路为什么要把PR审查交给智能体1.1 自动化PR审查到底在解决什么问题先说痛点。一个中等规模的研发团队每天产生的PR少则十几条多则几十条。代码评审存在的问题通常不是“没有人看”而是“看不过来、看不细、看不全”。我观察了很久常见情况有三种。第一种是评审积压。核心开发者的时间被大量PR阻塞等有人来看的时候分支已经落后主干很远解决冲突的成本反而更高。第二种是评审流于形式。点开PR扫一眼diff看到改动不多就回个LGTM这种评审对质量几乎没有正向作用。第三种是标准不一致。有人在意命名有人只关心功能还有人专门抓安全每个人的关注点不同导致同样的代码在不同PR里得到截然不同的评价。自动化PR审查不是要替代人类评审者而是先解决“有没有人看”和“能不能看全”的问题。让智能体把每个PR都完整过一遍把明显的问题挑出来把值得讨论的点列清楚人类开发者再基于这份初检结果做判断。这样评审密度上去了标准也统一了。1.2 为什么选Hermes而不是裸调大模型接口一开始我也想过直接用大模型API写一个脚本拿到PR的diff之后塞进prompt让模型输出评论。这种方式简单但实际用起来有几个很别扭的地方。单纯裸调API意味着每个大步骤都得自己在代码里写if-else。比如拿到PR变更是先看元信息还是先看文件列表diff太大要怎么分段模型输出的是不是合法JSON评论要发到哪个接口这些逻辑散落在脚本各处后面维护起来特别痛苦。Hermes这类智能体框架解决的是执行链路的问题。它把任务拆解、工具调用、结果汇总这些动作统一起来。我可以给它定义好工具告诉它“你有权限调用GitHub API你的目标是完成一次PR审查”它会自己规划先做什么、后做什么遇到问题还会多调用几次工具确认信息。这比传统脚本灵活很多也比维护一堆胶水代码省心。1.3 一次PR审查的完整闭环我做的这套流程核心闭环可以概括成五步。PR事件触发之后先拿到PR的基本信息和变更文件列表接着过滤掉不需要审查的文件比如锁文件、生成代码、第三方目录再把每个文件的diff按照一定策略分片逐个塞给Hermes分析Hermes输出结构化审查结果后脚本负责把结果转换成GitHub review comments最后把汇总结论写入PR的review中包括摘要、问题数量、和建议优先级。整个过程从触发到评论出现通常控制在三分钟以内。这个闭环看起来很常规但把每个环节都做好细节其实非常多。后面我会逐个拆解。2. 核心细节解析Hermes怎么“看懂”一次代码变更2.1 数据入口GitHub API中的PR相关信息要让Hermes学会审查PR第一步是搞清楚数据从哪来。GitHub提供了完整的REST API审查流程主要用到的接口有三个。第一个是拉取PR元信息对应GET /repos/{owner}/{repo}/pulls/{pull_number}里面包含PR的标题、描述、作者、基础分支、目标分支、当前head commit的SHA等信息。第二个是拉取PR的文件变更列表对应GET /repos/{owner}/{repo}/pulls/{pull_number}/files返回内容里每个文件都带filename、status、additions、deletions和patch字段其中patch就是diff片段。第三个是提交评审意见对应POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews通过它可以把审查评论以review的形式回写到PR页面上。还有一个容易被忽略的接口是GET /repos/{owner}/{repo}/pulls/{pull_number}/comments用于获取已存在的review comment。为什么需要它因为如果机器人重复运行同一套审查逻辑可能对同一行代码重复评论造成了严重的噪音。正确的做法是先查一遍已有评论把已报过的问题缓存起来避免重复打扰。2.2 增量审查为什么只审diff不审全仓库这个问题的答案其实很简单上下文窗口不够而且全仓库审查根本没有必要。一个大型项目的完整代码可能有几百万行任何大模型都不可能全量塞进上下文。但一次PR通常只改动几个文件、几百行代码真正需要关注的也就是这些变更。增量审查的核心思想是只看本次变更引入的问题不去纠缠历史遗留代码。文件过滤是增量审查的前置步骤。我总结了一套比较实用的规则。package-lock.json、yarn.lock、go.sum这类锁文件必须跳过它们体积大、格式机械审查价值极低*.pb.go、*.g.dart、dist/、build/这类生成代码需要跳过否则会刷出大量无效评论纯格式化或缩进调整的大范围diff如果不涉及逻辑修改可以把模型注意力集中在真正有语义变化的hunk上。文件过滤白名单每个团队可以根据项目特性调整但基本原则是先画红线不是所有变更都值得让模型逐行分析把预算花在刀刃上才能保证响应速度和结果质量。2.3 审查提示词与输出规范的设计这一步是整个项目的灵魂。我给Hermes设定了一套固定的审查角色描述同时把输出格式约束成结构化JSON方便后面脚本解析。系统提示词大概长这样你是一名拥有10年经验的资深代码审查专家正在参与一个GitHub PR的评审工作。 请基于以下代码diff进行分析重点关注 1. 明显的Bug风险与逻辑错误 2. 安全漏洞SQL注入、XSS、路径穿越、硬编码密钥、缺失鉴权等 3. 性能问题不必要循环、全表扫描、潜在死锁等 4. 可读性与维护性命名、重复代码、过长函数、明显反模式。 输出要求 - 仅输出JSON不要输出任何额外解释。 - 每条issue需要包含severity、line、message、suggestion四个字段。 - severity只能是error、warning、suggestion三种。然后每次执行审查时把文件和diff片段拼接进去。模型必须严格输出JSON的好处是后续代码可以直接json.loads不需要做文本解析。我在提示词里还会加一句“如果没有问题issues返回空数组”避免模型强行凑数。实际跑下来JSON格式偶尔还是会出问题比如模型在JSON前后加了一段markdown代码块标记或者注释里带了特殊字符导致JSON解析失败。针对这种情况我在代码里加了一道清洗逻辑把前后无关字符剥掉再做解析。2.4 智能体的任务拆解与工具调用前面提到Hermes是智能体框架这里具体说一下它是怎么工作的。Hermes采用类似ReAct的执行模式模型接收任务描述后先生成下一步行动意图然后调用对应工具拿到工具返回结果后再继续推理如此反复直到任务结束。我给它注册了三个工具拉取PR文件列表、获取文件diff内容、发送review评论。它收到“审查这个PR”的指令后会先调用工具查文件列表再逐个获取diff然后生成审查结论最后调用发送评论的工具。这套机制比固定写死脚本强在哪里假设PR的文件数量很多Hermes会自己决定先看哪些文件、哪些可以跳过如果某个文件的diff很大它会自己规划分批读取如果中间发现某个接口返回了错误它还能尝试重试。这些决策如果在传统脚本里实现需要写大量分支逻辑在智能体框架里只需要定义好工具和规则剩下交给模型自主决策。当然自主决策也带来不确定性。所以我在工具层做了约束不允许Hermes直接对所有文件发起评论必须先输出审查意见由外层脚本做二次校验确保格式合法、行号有效才真正提交。3. 实操过程从零搭建一套Hermes PR审查机器人3.1 环境准备与Hermes安装我把这套方案跑在Linux服务器上Python 3.10以上的环境。Hermes智能体框架的核心依赖并不复杂安装过程很直接。pip install hermes-agent如果你是在全新环境里跑建议先用python -m venv .venv建一个虚拟环境再把依赖装进去。我踩过的一个坑是Python版本太低导致部分依赖编译失败最后全换成Python 3.11才稳定下来。安装完成之后需要准备一个配置文件用来设定模型后端和API Key。Hermes本身支持多种模型接入方式我这边用的是OpenAI兼容接口配置项大概是这样的model: provider: openai-compatible base_url: https://your-llm-endpoint.example.com/v1 api_key: sk-xxxx model_name: llm-model-name temperature: 0.2这里有个细节需要注意审查类场景建议把temperature调低我设的是0.2到0.3之间。温度越低输出越稳定越不容易出现幻觉式的“硬找问题”。代码审查不是创意写作稳定性永远优先。3.2 GitHub Token权限配置Hermes要访问GitHub API需要配置一个GitHub Token。我推荐使用Fine-grained Personal Access Token而不是老的经典Token。Fine-grained Token可以精确限定到某个仓库或者某个组织最小权限原则在这里同样适用。我创建的Token主要勾选以下几个权限权限项说明pull requests: read读取PR元信息和diffpull requests: write提交review评论contents: read读取仓库内容部分场景需要checks: write可选如需提交检查结果Token创建好之后绝对不要写死在代码里或提交到仓库。我用环境变量托管脚本运行时从环境读取export GITHUB_TOKENghp_xxx export GITHUB_REPOyour-org/your-repo如果是在服务器上长期运行建议配合systemd或docker环境变量管理Token权限回收也方便。团队协作时用GitHub App的方式会更正规但个人项目或小团队用Token已经足够。3.3 核心脚本实现拉取PR、生成审查、回传评论这里直接给出一版我用的核心脚本结构包含了PR信息拉取、diff获取、Hermes审查、评论回传的完整链路。因为涉及具体框架的版本差异我标注了通用接口实际使用时按你的Hermes版本调整即可。import json import os import re import requests from hermes import HermesAgent GITHUB_TOKEN os.environ[GITHUB_TOKEN] GITHUB_REPO os.environ[GITHUB_REPO] PR_NUMBER int(os.environ[PR_NUMBER]) PR_HEAD_SHA os.environ[PR_HEAD_SHA] HEADERS { Authorization: ftoken {GITHUB_TOKEN}, Accept: application/vnd.github.v3json, } # 需要跳过的文件 SKIP_FILES_PATTERNS [ package-lock.json, yarn.lock, go.sum, *.pb.go, *.g.dart, dist/, build/, ] def get_pr_files(): 拉取PR的文件变更列表分页处理。 files [] url fhttps://api.github.com/repos/{GITHUB_REPO}/pulls/{PR_NUMBER}/files page 1 while True: resp requests.get( url, headersHEADERS, params{per_page: 100, page: page}, ) resp.raise_for_status() page_data resp.json() if not page_data: break files.extend(page_data) if len(page_data) 100: break page 1 return files def should_skip(filename): 判断文件是否需要跳过审查。 for pattern in SKIP_FILES_PATTERNS: if pattern in filename or filename.endswith(pattern.replace(*, )): return True return False def clean_json_response(text): 清理模型输出中的Markdown代码块标记和前后噪音。 text text.strip() text re.sub(r^(?:json)?, , text).strip() text re.sub(r$, , text).strip() return text def review_with_hermes(agent, filename, patch_content): 用Hermes审查一个文件的diff片段返回结构化审查结果。 system_prompt 你是一名拥有10年经验的资深代码审查专家正在参与某个GitHub PR的评审。 请基于给定的代码diff进行分析重点关注Bug风险、安全漏洞、性能问题、可读性与维护性。 仅输出JSON不要输出额外解释。 格式{summary:...,issues:[]} 每条issue包含severity/line/message/suggestion四个字段。 severity只能是error、warning、suggestion。 没有问题时issues返回空数组。 task f文件{filename}\n以下是该文件的diff内容\n{patch_content} resp_text agent.run(system_prompt, task) resp_text clean_json_response(resp_text) try: result json.loads(resp_text) except json.JSONDecodeError: # 解析失败时返回默认结构保证主流程不中断 return {summary: 解析审查结果失败请人工确认该文件, issues: []} # 过滤掉无效行号或非法severity的issue valid_issues [] for issue in result.get(issues, []): if issue.get(severity) in (error, warning, suggestion): valid_issues.append(issue) return {summary: result.get(summary, ), issues: valid_issues} def post_review_comments(comments): 把审查评论回传到PR review中。 url fhttps://api.github.com/repos/{GITHUB_REPO}/pulls/{PR_NUMBER}/reviews payload { commit_id: PR_HEAD_SHA, event: COMMENT, body: Hermes 自动代码评审结果, comments: comments, } resp requests.post(url, headersHEADERS, jsonpayload) resp.raise_for_status() def main(): agent HermesAgent() files get_pr_files() review_comments [] total_issues 0 for item in files: filename item[filename] patch item.get(patch, ) if should_skip(filename): continue if not patch: continue # 分片逻辑单个文件diff过大时按行数拆成多段审查 patch_lines patch.splitlines() chunk_size 200 for i in range(0, len(patch_lines), chunk_size): chunk \n.join(patch_lines[i:i chunk_size]) result review_with_hermes(agent, filename, chunk) for issue in result[issues]: review_comments.append({ path: filename, line: issue[line], side: RIGHT, body: f[{issue[severity]}] {issue[message]}\n\n建议{issue[suggestion]}, }) total_issues 1 if review_comments: post_review_comments(review_comments[:50]) print(f审查完成共发现 {total_issues} 个问题已提交评论) if __name__ __main__: main()这段脚本的核心值得说几句。分片策略很关键如果某个文件的diff超过一定行数直接全量塞给模型容易超出上下文限制也容易让模型丢失对前面内容的理解。我按200行一个片段拆分每段独立审查最后汇总评论。评论数量做了上限控制单次最多提交50条防止PR页面被刷屏。还有一个细节post_review_comments里的line字段在GitHub API中对应修改后文件的具体行号side标记为RIGHT表示是变更后的代码。这个参数如果写错评论会定位失败这个问题我在4.2里还会再讲。3.4 Webhook与定时轮询两种接入方式怎么选脚本写好了怎么触发它两种主流方案GitHub Webhook和定时轮询。Webhook的方案是在GitHub仓库的Settings里配置一个Webhook地址选择pull_request事件。当PR打开、更新、合入时GitHub会向该地址推送事件。服务器上跑一个简单的Flask应用接收事件符合条件时启动审查脚本。from flask import Flask, request app Flask(__name__) app.route(/webhook, methods[POST]) def webhook(): payload request.get_json() action payload.get(action) if action in (opened, synchronize, ready_for_review): pr payload[pull_request] if not pr.get(draft): os.environ[PR_NUMBER] str(pr[number]) os.environ[PR_HEAD_SHA] pr[head][sha] # 触发审查主流程 import subprocess subprocess.Popen([python, main.py]) return ok, 200定时轮询的方案更简单用crontab或者其他定时任务工具每隔几分钟扫描一次仓库里所有open状态的PR调用GitHub API检查head SHA是否有变化有变化就执行审查。两种方式各有取舍。Webhook实时性高PR一推送马上就能审查但对服务器有公网访问要求还需要处理签名校验和失败重试。定时轮询对公网要求低、部署简单但会有几分钟延迟。考虑到我这边是内部服务器公网入口本来就有所以最终选了Webhook方案。如果你没有公网建议从定时轮询开始先把流程跑通再考虑实时性。3.5 真实PR审查效果记录举一个真实发生过的例子。某个后端服务PR新增了一个接口改动了两百多行代码。三位人工评审者看了一遍其中一人提了变量命名问题另外两人LGTM。Hermes在这个PR里发现了一个被忽略的严重问题新接口使用了字符串拼接来构造SQL查询参数直接拼进查询语句明显存在注入风险。它还注意到这个接口缺少权限注解任何登录用户都能调用管理员接口。这个案例让我很受触动。不是因为它比人聪明而是因为它真的会把每一行都过一遍不会因为“这个PR是熟人的”、“改动看起来不大”而放松警惕。当然Hermes也会误报比如把一些团队特有成体系的写法当成反模式。这就是后面要说的调优问题。4. 常见问题与排查技巧实录4.1 API限流与网络超时GitHub的REST API不认证情况下限流很严格认证后普通请求的限额是每小时5000次。听起来很多但如果审查脚本写得不够优雅每个PR拉几次文件列表、每次评论都发单独请求还是可能撞上限制。我遇到过最夸张的一次一个超大PR有1500多个变更文件脚本逐文件拉diff直接触发了限流整个流程崩掉。后来做了两个优化。第一是给请求加上条件判断跳过明显不需要的文件。第二是请求失败时加上指数退避重试逻辑遇到403或429错误就睡一会儿再试。核心逻辑是给requests调用包一个带重试的封装器5秒起步最多重试5次。实测下来限流问题基本没有再出现过。网络超时则是另一个高频问题。尤其大模型生成长文本时如果服务商接口不稳定偶尔会出现连接超时。这时不要急着把整个流程判定为失败先重试一次重试仍然失败再跳过该文件并在日志里留下记录方便事后排查。4.2 大模型输出格式不稳定JSON解析失败是我前期最头疼的问题。明明提示词里说了多次“仅输出JSON”模型偶尔还是会在结果前后加上json这种代码块标记或者在某个issue里漏掉字段。我的排查思路是三层兜底。第一层在脚本里做文本清洗把代码块标记剥掉。第二层解析失败后用默认结构返回保证单文件审查失败不会影响整个PR流程。第三层对每条issue做字段校验缺失关键字段的日记入日志人工抽查。这三层兜底加上去之后审查流程的稳定性明显提升。我的切身体会是大模型输出这件事永远不要指望100%稳定代码层面必须做好防御。审查场景出错也尽量不要让用户看到一堆报错堆栈更合理的方式是把它降级成一条提示消息这个文件未能自动审查请人工关注。4.3 误报漏报调优误报太多是自动化代码评审推广不开的主要原因。开发人员每天收到几十条机器人评论其中一半还没什么价值很快就有人把机器人拉黑。我经历了一个从“什么都管”到“管关键问题”的调整过程。最初版提示词里写的是“检查所有潜在问题”结果模型连“变量名可读性不足”“建议提取常量”这种风格建议都报噪音极大。后来我把提示词改成先分优先级明确只有error级别的问题才必须报告warning级别要求与具体Bug风险相关suggestion级别默认可以不报。同时也加了负面清单在系统提示词里写明不要建议格式化调整、不要建议重构历史遗留代码、不要因为代码风格与个人偏好不同而发建议。调完这一版噪音评论下降了六成以上。漏报是另一个方向的问题。模型受限于上下文确实会漏掉跨文件的联动问题。比如一个PR同时改了前端调用和后端接口定义模型单独看每个文件时都正常但组合在一起才发现参数不一致。这种问题靠单文件diff很难解决我的做法是增加一个PR级别的总体审查环节把PR描述、关键文件摘要、改动摘要合并成一份总览让Hermes做一次跨文件的整体判断。4.4 团队协作中的细节坑自动审查机器人上线后真正的风险往往不是技术问题而是使用方式。第一个坑是评论轰炸。没有限制评论数量之前一个大PR可能被刷出上百条评论开发者打开PR页面会崩溃。后来我加了评论上限50条并且按severity排序优先展示error级别问题把warning和suggestion折叠到汇总信息里。GitHub的review部分可以直接折叠提交开发者可以在“Files changed”里单独看某一条体验好很多。第二个坑是机器人和人工评审的职责边界。有的开发者看到机器人已经审过一轮自己就不再细看直接在review里通过。这是我极力反对的。自动化评审的价值是“初筛”不是“终审”。所以我在评论顶部明确写着“Hermes自动审查结果仅作参考需人工确认后再合入。”同时也把机器人的event设置为COMMENT而不是APPROVE避免它影响合入权限。第三个坑是并发问题。Webhook事件触发频繁时多个审查进程可能同时对同一个PR跑逻辑产生重复评论。解决方法是加一个基于PR编号的分布式锁或者用一个简单任务队列串行处理。我的做法是全部审查任务先写入SQLite队列再由单个worker消费从源头避免了并发冲突。4.5 一些“看起来很怪”但确实发生过的周边问题排查过程中我发现很多耗时依旧的问题并不在审查脚本本身而是来自周边的环境因素。比如GitHub API偶尔返回超时或连接重置特别是网络状况不佳时代码走到了异常分支整个审查流程没有日志就直接退出了。后来我在入口处加了一层全局异常捕获任何异常都会写日志并把PR标记为“审查异常”这样至少能知道不是“审查通过”。再比如有一个PR是前端工程师改Flutter依赖配置Hermes检查后认为没太大问题但CI却在failed to apply plugin dev.flutter.flutter-gradle-plugin这个错误上挂了。这件事给我提了个醒自动审查不能替代CI审查关注的是代码质量和变更的合理性构建环境的问题应当由CI负责不要指望模型把构建日志也检查一遍。还有一个有代表性的情况同事用Docker部署Hermes时发现模型返回内容正常但评论始终没有发出去。查到最后是容器环境少了PR_HEAD_SHA这个环境变量。GitHub的review接口需要commit_id参数如果传空了接口直接返回422。类似的环境变量缺失问题排查时一定先看配置再怀疑代码。5. 写在最后实际跑了三个月的个人体会这套Hermes PR自动化审查方案从搭建到现在最大感悟是工具本身不难难的是持续调优。我几乎每周都会根据开发者反馈调整一次提示词或者增删一批过滤规则。自动审查本质上是在跟团队的真实代码风格做磨合它不是装完就能一劳永逸的东西。还有一个小技巧想分享给你审查机器人上线初期先让它只读评论不开喷。等它的结论质量稳定了再逐步放大到warning级别最后才是error级别的自动化提醒。千万别一上来就把所有问题全开着否则团队会被噪音淹没一声“关掉吧”就可能让整个项目夭折。如果你也想在团队里跑一套类似的流程我建议先选一个非核心的仓库做试点跑两周积累一批真实审查案例再决定要不要扩大到全部仓库。自动化审查这件事慢就是快。
返回列表