资讯动态

AI智能体驱动的自动化代码评审:从PR到质量门禁的工程实践

发布时间:2026/9/8 12:49:48 来源:尧图企业网站定制
1. 为什么我要把 PR 评审交给一个智能体把 PR 评审这件事从人肉盯 diff变成智能体自动过一遍是我今年做得最值的一个技术决策。Hermes 最初只是我写的一个脚本监听 GitHub 的 PR 事件拉取 diff丢给大模型再把评审意见写回评论区。后来它长成了一个带工具调用、有评审规范、能区分严重级别的自动化代码评审智能体团队里每天二十几个 PR 先过它这一关人工评审者只需要看它筛出来的结果。1.1 人工评审的三个真实痛点先交代背景。我在一个 20 人左右的研发团队三个后端小组共用一个仓库平均每天开 20 到 40 个 PR。过去半年我统计过一个 PR 从提交到拿到第一条有效评审意见中位数在 6 小时以上。不是没人看是评审的成本确实太高了上下文切换成本高。评审人自己手里有开发任务切过去看 diff至少需要二十分钟才能完全进入状态看完还得再切回来这一来一回一天的有效工作时间被吃掉一大块。大 PR 的注意力衰减非常明显。超过 500 行的 diff后半部分的问题检出率会显著下降。这不是个人能力问题是注意力资源天然有限前一半看仔细了后一半自然就松懈。低级问题反复出现。忘记判空、密钥打进了代码、异常被静默吞掉、错误信息不含上下文这些东西在每个 PR 里都会反复出现每次都要人肉再抓一遍极其消耗耐心。我一开始想的是堆规则也就是写一堆自定义 lint 规则。但很快发现这条路走不通规则能抓住模式和格式问题却抓不住这个函数在并发场景下会死锁这里少了一个事务补偿这种需要理解业务上下文和调用链路的语义问题。1.2 Hermes 想解决什么问题后来我把思路从写规则换成了养一个智能体。Hermes 这个名字是按信使的意思取的——它不写代码它把代码里的信息高效地传达给人类评审者。Hermes 的实际定位是一个监听 GitHub PR 事件的自动化代码评审智能体。它拿到 PR 的完整上下文标题、描述、diff、涉及文件、历史提交交给具备代码理解能力的大模型做初步评审再把意见按严重级别写回 PR 的行级评论里。它能做到的事情有以下几件在 PR 打开或更新后自动跑一遍评审分钟级出结果按 blocker / major / minor / nit 四个级别对问题分级只把真正值得看的顶到前面行级评论直接定位到具体代码行对已经评论过的问题做指纹去重不会每次 push 都刷屏它不是用来替代人工评审的。它更像是把人工评审里最机械、最耗精力的那部分工作删掉了评审人只需要专注看 Hermes 标出来的高风险区和设计层面的问题。1.3 这套方案适合谁如果你满足下面任意两条就可以考虑做一套类似的方案团队有固定的 GitHub PR 流程但评审经常拖延PR 堆积严重PR 里反复出现低级错误code review 的精力被大量消耗在基础问题上你们已经在用大模型写代码但对让模型帮你看代码这件事还没动手你手里有一台能跑服务的机器或者仓库能挂 GitHub Actions对个人开发者来说这个项目也能简化成自带一个 Action 的评审机器人不用自己写服务。后面我会给出两种运行模式的对比。2. Hermes 的整体设计不是一条 prompt是一个有工具的智能体2.1 事件接入的两种模式先定架构。Hermes 有两种跑法分别适合不同场景。Webhook 常驻服务模式用 GitHub App 注册一个 webhook监听pull_request事件。常驻服务收到事件后拉取上下文、调模型、写回评论。优点是实时性好可以做评审意见回复这类交互缺点是必须部署webhook 地址要公网可达还要维护 token 生命周期。GitHub Actions 模式把评审逻辑打包成一个 Action通过 workflow 的on: pull_request触发。计算环境由 GitHub 托管不用自己部署服务。缺点是跑在 Actions 环境里没法响应评论交互除非再去回调 API而且会消耗 Actions 分钟数。我最后是两套共用的核心评审引擎抽成同一个 Python 包webhook 服务和 Action 都调它。个人项目或小团队用 Action 就够能省一台机器需要做交互式评审意见回复的再上 webhook 服务。维度Webhook 服务GitHub Actions部署成本需要容器或云函数零仓库里放 workflow 即可触发延迟秒级秒到分钟级评论回复交互支持不好做网络要求需要公网可达地址无由 GitHub 托管运行成本服务器费用Actions 分钟配额2.2 核心模块拆解整个 Hermes 拆成五个模块每个模块只干一件事event-adapter接收 webhook 或 Action 的 event payload统一转成内部的 PRContext 对象context-builder从 GitHub API 拉取 PR 标题、描述、提交列表、文件列表、完整 diff做 token 预算控制review-engine组装评审 prompt调用大模型拿回结构化的评审意见result-writer把意见转成 GitHub Review API 的 payload批量写回state-store记录已评审的 commit SHA、已发布的评论指纹防止重复这个拆分方式是我重构三次之后才定下的。最早的版本把拉 diff和写评论全写在主流程里看起来代码很少但项目一旦同时接入 Action 和 webhook 两套触发源就非常痛苦——事件来源不同、token 来源不同唯一的共同点是中间的评审逻辑。把context-builder/review-engine/result-writer拆开之后换触发源只需要换掉event-adapter。2.3 为什么是 Python FastAPI语言选型上我对比过 Node 和 Python。Node 的优势是 GitHub 官方 SDK 生态成熟但异步事件循环的写法对团队大多数人来说并不顺手。Python 的好处在于模型生态天然亲和评审引擎要对接各种大模型的 OpenAI 兼容接口Python 这边几乎是主场同时 FastAPI 处理 webhook 的并发请求非常简单asyncio 下同时拉起几十个 PR 的异步上下文没什么压力。如果你问我的推荐技术栈我的答案是核心评审引擎用 Python触发层用什么语言不重要反正最后都是调同一套 HTTP API。2.4 为什么必须是智能体而不是一次性的 prompt这个点值得多说两句。Hermes 第一版就是一段 prompt把整个 diff 全量塞给模型让它输出 JSON 评论。用了两周就暴露了三个问题token 撑不住一个大 PR 的 diff 有几万行模型上下文直接爆掉只能硬截断评审质量大幅下降缺少全局信息只看 diff 不看文件全貌模型经常对修改后的函数产生误判。比如只看到一个函数从总是返回列表改成可能返回 None就报了一堆 NPE 风险但调用方其实已经做了处理无法按需验证模型说这里会死锁但你没法让它去确认锁的获取顺序改成智能体之后模型不再只依赖一次输入而是拥有了工具获取某个文件的完整内容、在仓库里搜索某个函数定义、读取这个 PR 的历史评论。它可以在 diff 的基础上按需补查上下文这才是评审而不是看补丁。3. 从 GitHub API 拉取 PR 上下文的完整链路3.1 认证方式PAT 与 GitHub App先解决认证问题两条路。PATPersonal Access Token适合个人项目也适合 GitHub Action 内部使用。只需要repo权限生成一个 token 放到环境变量里。限速按你的账号算5000 次/小时个人实验完全够用。GitHub App适合团队和组织场景权限可以按仓库精细授予。用私钥签发 installation tokentoken 有效期 1 小时到期自动轮换。安装级限速比 PAT 稳定出了安全问题还可以单独吊销某个安装的权限。我的建议是第一步先用 PAT 把链路跑通确认评审逻辑可靠之后再换成 GitHub App。不要一上来就搞 App签名、轮换、权限配置这些会消耗大量注意力让你没法专注于核心逻辑。3.2 拉取 PR 元信息与 diff核心就是三个接口GET /repos/{owner}/{repo}/pulls/{pull_number}PR 标题、描述、base/head、状态GET /repos/{owner}/{repo}/pulls/{pull_number}/files文件列表及每个文件的 patchGET /repos/{owner}/{repo}/pulls/{pull_number}/commits提交列表用于拿到最新的 head SHA有一个容易踩的坑是 Accept 头。默认返回的是 JSON 格式patch字段里已经有 diff hunk但部分场景下你需要的是原始 diff——比如传给模型时用纯文本/-格式会比 JSON 转义后的字符串更清晰。这时对 pulls 接口加Accept: application/vnd.github.v3.diff返回体就是纯文本 diff。另一个接口细节files 接口里超大文件的patch字段可能被截断甚至为 null。GitHub 对超大 diff 有读取保护拿到的不一定是完整内容。这个坑在第 6 节我会专门讲处理方式。用 httpx 拉取文件列表的示意代码async def fetch_pr_files(owner, repo, pr_number, token, session): url fhttps://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/files?per_page100 headers { Authorization: fBearer {token}, Accept: application/vnd.github.v3json, } files [] while url: resp await session.get(url, headersheaders) resp.raise_for_status() page resp.json() files.extend(page) url resp.links.get(next, {}).get(url) return files用 requests 也能实现但我建议直接上 httpx因为后面 webhook 服务本身是异步的复用同一个客户端省事很多。3.3 组装模型输入时的 token 预算控制拿到 diff 之后不能直接塞给模型。我整理了一套分层策略过滤不需要评审的文件lock 文件、vendor 目录、生成的 protobuf、纯 rename 文件文件名变内容不变按 token 估算排序每个文件用 tiktoken 估算 diff 大小大的排后面设置上下文预算以 32k 窗口的模型为例给 diff 留 20k剩余留给 prompt 指令和后续的文件内容补查超出预算的文件做截断只保留新增和修改的函数体删掉未改动的大段 importtoken 估算有个经验公式文本长度 / 3是英文文本的平均 token 数代码更密集我一般用len(text) / 2做保守估计更精确的做法是直接用 tiktoken 按目标模型对应的 encoding 算。3.4 同一份上下文喂给模型的三种形态模型对纯文本 diff 的理解效果其实一般。实践下来把上下文组织成三个部分效果最好先给 PR 概况标题、描述、涉及的模块和文件清单再给文件级变更摘要每个文件删了多少行、加了多少行、改了哪个函数最后才给 diff 细节按文件分块每块前面加一句说明例如这是 auth.py 的变更涉及 login 流程顺序很重要。先让模型建立全局认知再看局部补丁很多误判会自然消失。这也是我坚持把context-builder单独抽出来的原因——喂料方式本身就是决定评审质量的关键变量。4. 评审引擎让大模型真的会评审4.1 评审规范先行模型能力再强不给规范它也会乱来。我给 Hermes 写的系统 prompt 核心内容只有四部分你是 Hermes一名资深代码评审工程师。 你的评审边界 1. 正确性并发、边界条件、异常处理、状态流转 2. 安全性注入、密钥泄露、权限绕过 3. 性能无谓的重复计算、N1 查询、大对象复制 4. 可维护性重复代码、过度设计、命名误导 规则 - 只报告你确定存在的问题不确定的一律不报 - 每一条必须给出可落地的修改建议不写空话 - 不要评论代码风格、缩进、命名偏好那是 linter 的事 - 评论必须引用 diff 中真实存在的行 - 输出 JSON 数组不要输出任何解释性文本这里最难落地的一条是只报告确定的问题。大模型的默认倾向是讨好用户你不警告它它就会把我觉得这里可以优化一下这种废话写满整个屏幕。我把不确定就不报放在规则第二位并且在结果后处理时还会再做一次过滤。模型后端我们默认接的是 DeepSeek Hermes任何 OpenAI 兼容接口的模型都可以替换。切换的成本很低无非是改base_url和模型名。4.2 用结构化输出约束评审结果模型输出用 JSON mode / function calling。我们定义的回传结构如下[ { path: src/auth/login.py, line: 87, severity: blocker, title: 用户输入直接拼入 SQL 查询存在注入风险, body: query 参数未做参数化处理建议改为 ..., confidence: 0.96 } ]把path和line单独拎出来是为了让result-writer能直接定位severity用来排序confidence是让模型自己表态低于阈值的意见在写回前直接丢弃。confidence这个字段非常有用。让模型先判断我有多确定比事后用规则过滤可靠得多。我们当时把阈值设为 0.7低于这条线的意见不写回 PR只在日志里留档。4.3 严重级别的硬标准分级不能靠模型拍脑袋我给每一级都定了硬标准级别判定标准示例blocker会引发线上事故、数据丢失、安全问题SQL 注入、密钥硬编码、资源未释放major明显逻辑错误特定场景下会出错边界条件漏判、并发下状态不一致minor代码可运行但存在隐患重复代码、错误处理不完整nit可改可不改的优化变量命名、微小重构模型在判断级别时经常混淆 minor 和 nit这没关系因为最终展示给人类评审者时我们只默认展开 blocker 和 major 两级后面两级折叠到更多建议里。关键原则是别让低价值意见刷屏把真正的问题顶到最前面。4.4 规则引擎与模型结合对抗幻觉模型评审最大的风险是幻觉它可能评论一个 diff 里不存在的行、引用一个不存在的函数、提出一个与代码事实矛盾的修改方案。我在review-engine前面加了一个规则预检层把能被确定性规则捕获的问题先抓掉包括高熵字符串、疑似密钥/Tokeneval、exec、pickle.loads等危险函数调用SQL 字符串拼接TODO/FIXME 残留明显的越权接口缺少鉴权装饰器规则层确定性 100%模型层负责语义理解。两者结合之后规则问题不用浪费模型 token模型则专注规则抓不住的逻辑问题整体准确率明显上升。5. 把评审意见写回 PRReview API 的接入细节5.1 行级评论的定位方式GitHub 的 Review API 有两种提交方式一种是逐条 POST 创建评论另一种是整体提交一个 review 附带多条 comments。Hermes 用的是后者POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews一次性把整个 PR 的评审意见作为一个 review 提交。这样既减少了 API 调用次数也方便人类评审者一眼看清这是一轮机器人评审。payload 长这样{ commit_id: 6dcb09b5b57875f334f61aebed695e2e4193db5e, event: COMMENT, comments: [ { path: src/auth/login.py, side: RIGHT, line: 87, body: query 参数未做参数化处理存在 SQL 注入风险。建议改为参数化查询。 }, { path: src/config.py, side: RIGHT, start_line: 42, line: 45, body: 这段配置加载逻辑在并发场景下可能重复初始化。 } ] }这里要特别注意 GitHub 的评论定位语义。新版 API 用line指定文件新版本的行号如果是针对删除的代码用side: LEFT指定旧版本行号。很多人第一次写都会被网上老教程带偏去用positiondiff hunk 内的位置字段——这个字段已经废弃了新代码一律用lineside。还有一个隐性问题GitHub 只允许在 diff hunk 内含少量上下文行评论。如果模型返回的line不在 hunk 范围内API 会直接报 422 错误。所以result-writer必须做一次行号合法性校验把模型返回的line跟实际 diff 的 hunk 行号集合比对不在范围内的意见降级为在文件末尾追加一条汇总评论。5.2 多行评论与 review 的 event 选择多行评论用start_line指定起始行line指定结束行并且两侧必须同side。如果模型给出的问题跨多个连续行用这种形式比逐行评论体验好很多。另外event字段有三个取值COMMENT/APPROVE/REQUEST_CHANGES。机器人推荐用COMMENT不要用REQUEST_CHANGES。因为机器人的判断不可能 100% 准确如果它REQUEST_CHANGES会直接阻塞合并流程产生不必要的摩擦。如果真想接合并门禁应该用 GitHub Checks API 做结论状态而不是滥用 review 的 approve/reject 语义。5.3 防止重复评论的指纹机制PR 每次 push 都会触发synchronize事件不做去重的话Hermes 会把同一个问题在每一轮都刷一遍。我的方案是在state-store里存两个东西已评审的 commit SHA同一 SHA 不重复跑评论指纹对模型名 文件路径 行号 问题标题做 SHA256写回前查一下有没有发过指纹机制还有一个附加好处当问题在新提交里被修复了Hermes 不会基于旧 diff 再发一遍过期评论因为指纹校验时会发现旧行号对应的代码已经变化模型输出的 context 对不上就会判定为失效意见并丢弃。6. 上线后踩过的坑完整排查链路6.1 事件收到了评论却永远发不出去上线的第一个晚上日志显示 webhook 正常收到pull_request.openedcontext-builder也把 diff 拉回来了模型也输出了意见但result-writer一直在抛 422。排查链路从外到内走了一遍最终定位在行号上。错误信息是 line must be part of the diff。原因模型输出的line是从 patch 文本里读到的相对位置而不是文件新版本的行号。diff 里 hunk 头 -12,6 15,8 表示新文件从第 15 行开始但模型可能把 hunk 内的第 3 行当成了文件第 3 行。修法是在context-builder里提前把每个 hunk 的新旧文件行号映射表算出来喂给模型时直接告诉它这些是合法行号你只能在这里面选从源头消灭越界。这个坑事后看很基础但当时花了我一整个晚上。原因是 GitHub 文档里 diff 行号、hunk 行号、文件行号是三个概念一开始没有仔细区分。经验是写result-writer之前先用几个小 PR 做回归测试把行号计算方式验证一遍。6.2 大 diff 被截断评审出现评价缺失上线第七天一个重构 PR 有 3000 多行变更Hermes 只评论了两个文件。查日志发现context-builder里 files 接口返回的patch字段是 null导致整个文件被跳过。GitHub 对超大文件和超大 diff 有读取保护patch字段可能被省略。修法是分层拉取先通过 files 接口拿文件清单和变更统计对patch为 null 的文件改用 raw contents 接口分别拉取 base 和 head 两个版本的文件内容自己算 diff文件实在太大比如超过 5000 行就直接放弃展示完整 diff只把变更的函数签名和文件摘要给模型让模型决定是否需要申请读取文件内容另外一次 review 请求里的comments数组是有上限的我记得当时踩到的是 100 条左右具体数值要以 API 文档为准。超出上限就分批提交成多轮 review。上线的第一个月我没做分批结果有一个 PR 因为评论量过大直接 422整轮评审失败。6.3 每次 push 都在刷屏团队直接静音拆指纹机制之前一个 PR 只要 push 三次Hermes 就会发三轮几乎一样的评论。团队成员很快把它的通知静音了真正的风险问题也被一起忽略——这是比刷屏本身更严重的问题信任一旦耗尽工具就失去了价值。去重逻辑在 5.3 已经讲了这里补充一个细节指纹要存到数据库或对象存储不能只存内存。webhook 服务重启之后内存指纹就丢了第二天一上线又开始刷屏我当时就被这个低级失误坑过。6.4 API 限流是怎么被触发的跑了一个月之后某天半夜有一批历史 PR 需要重跑评审把 PAT 的 5000 次/小时限额打满了之后一整天所有请求都 403。排查发现是context-builder为了拿每个文件的完整内容对同一个 PR 发了几十次请求重跑任务又堆叠在一起瞬时请求量远超预期。三个整改措施全链路加内存缓存同一个 PR 的上下文在 10 分钟内不重复拉取评论提交改成批量 review一次 API 调用解决所有评论加指数退避重试并监控X-RateLimit-Remaining响应头剩余额度低于 500 时自动降级只评审 blocker 级别风险其他问题临时跳过6.5 模型幻觉的高发场景与打压手段我把前 100 个 PR 的错误评论全部拉出来做标注幻觉高发场景主要有三类建议修改一个不存在的函数——模型从其他文件或训练记忆里串过来了对加密/签名代码提出安全问题——因为它不认识这个代码模式引用的代码行内容和实际不符——多行 diff 合并时行号错位针对这三类最直接有效的手段是在 prompt 里强制要求每条评论的 body 必须以 diff 原文引用开头比如代码query user_input然后result-writer校验这个引用是否真的存在于 diff 中校验不通过的直接丢弃。这个方法简单粗暴但确实把幻觉评论率从 22% 降到了 6% 左右。7. 效果评估与下一步扩展7.1 我用三个指标衡量 Hermes 的价值跑了三个月、累计 240 个 PR 之后我统计了三个数字有效问题率precisionHermes 累计报了 1180 条意见人工评审标注认同的 437 条整体 precision 约 37%。但如果只看 blocker 和 major 两级precision 能到 58%。也就是说严重级别越高模型判断越可靠这正好符合它的使用定位。评审耗时PR 从提交到获得第一条有效评审意见的中位数从 6.2 小时降到 1.8 小时。省掉的主要是等人工评审者切换上下文的时间。人工评审行为变化在 Hermes 覆盖的 PR 里评审人在行级评论上花的时间少了大约 30%这部分时间被转移到了讨论设计和架构方向上。37% 的整体 precision 听起来不高但注意这是所有意见的统计里面包含大量 minor 和 nit。对机器人来说这个数字已经足够有价值——它单次运行成本只有几分钱到几毛钱而每一批意见里都有值得人类花时间看的内容。7.2 后续迭代的几个方向按目前的使用反馈后面几个迭代方向基本确定了支持评审对话人工评审者在 PR 里回复这个建议不成立因为 XXXHermes 通过 webhook 接收评论事件重新分析并更新自己的意见接入 GitHub Checks API 做门禁当存在未解决的 blocker 时在 PR 状态区显示失败。是否启用合并保护由团队按项目情况决定与静态扫描工具联动让 Hermes 专注语义级问题重复代码、坏味道交给 SonarQube 这类工具避免重复劳动多模型路由小 PR 用便宜的小模型大 PR 用最强的模型按 diff 行数和复杂度动态路由控制成本跑了这段时间我个人的体会是自动化代码评审真正的难点从来不是接入 API 或写 prompt而是设定一个合理的期望边界。你不能指望它替代人类评审它是把人从验收代码能不能跑的机械劳动里解放出来让人把精力放回这个设计方向对不对。最后分享一个小技巧在 Hermes 的每条评论末尾加了一行_Hermes bot · 置信度 0.96_。人工评审者会先看置信度和级别再决定要不要展开。让机器人自己承认我不确定反而比假装权威更容易建立信任。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价