把 AI 审查接入 PR:难点在工程细节
作者将 PR 差异获取、规则驱动的 LLM 审查和行内评论串成 GitHub Action,核心逻辑放在服务端以便统一更新提示词。文章讨论 Rack 3.2 请求体读取、Sequel JSONB 处理及减少模型无效评论等落地问题。
AI 深度解读
AI 代码审查最容易做出来的,是让模型对每个 PR 都提几条意见;最难做好的,是让这些意见值得人看。作者搭建 GitHub Action 的出发点很具体:审查质量不能总取决于当天哪位资深工程师有空,自动化工具至少要托住下限。
这篇经验里,真正有用的是两类问题:Webhook 和数据库怎样悄悄丢掉数据,以及怎样压住模型的猜测和挑刺。作者没有给出效果对照数据,但他对审查噪声的处理,很适合正在接入 AI 编程工具的团队参考。
Action 只做入口,麻烦留在服务端
作者设计的流程很直接:
PR opened/updated
→ GitHub sends a webhook
→ API pulls the diff
→ LLM reviews it against a ruleset
→ Action posts inline comments on the PR
Action 只负责调用 API、把结果发布到 PR,审查逻辑放在服务端。这样修改提示词时,不必让使用者逐个更新 workflow 文件。代价是服务端的请求处理、数据保存和结果序列化都成了关键链路。
第一个坑出在 Grape 和 Rack 3.2 上。作者最初使用 Grape 声明参数并校验,测试能过,上线后 Webhook 的所有字段却都被判定为缺失。他定位到上游提前读取了请求体,而该环境下的输入流不能回退,轮到 Grape 校验时只剩空字符串。
作者在这个端点放弃框架参数校验,改为直接解析:
post '/v1/webhooks/github' do
payload = JSON.parse(request.body.read)
# 将键转换为符号,手动校验,再继续处理
end
要看的是 request.body.read:读取发生在哪一层,会直接影响后续还能否拿到数据。这个片段展示了作者的处理方式,但没有交代上游消费者和中间件顺序如何调整,不能理解为请求体已经耗尽后,再读一次就能恢复。
第二个坑来自 Postgres JSONB。作者用 Sequel 保存审查结果,后台却把明明有发现的记录显示成「0 issues, score null」。
原因在于 Sequel::Postgres::JSONBHash 使用 DelegateClass(Hash),value.is_a?(Hash) 返回 false。序列化代码据此跳过了 JSONB 字段,而且没有报错。作者给出的经验是:先显式转换,再处理数据,不要直接用 is_a?(Hash) 判断 JSONB 列。
这两处故障都提醒我们:页面显示「没有问题」,也可能只是数据没有成功走完整条链路。
怎样让模型少说几句,却更有用
作者认为,最费时间的是校准模型。一个连 import 都要挑刺的审查机器人,很快就会被团队忽略。
首先,只给 diff 不够。作者最初直接让模型审查 diff,结果模型把重命名的方法标成「可能未使用」,实际调用者却在 diff 之外。
他的改法是提供被修改文件的周边上下文,同时明确告诉模型能看到哪些文件、看不到哪些文件。这里改善的是判断依据:模型不能因为眼前没有调用,就推断整个项目没有调用。作者描述了改善方向,但没有提供误报率变化。
其次,输出必须方便程序处理。作者要求 JSON,字段包括 file、line、severity、comment。自由格式 Markdown 看起来完整,发布行内评论时却还要靠正则重新拆解。结构化输出也让服务端能够在发布前筛掉低价值发现。
过滤对象包括 rubocop、eslint 已经覆盖的风格问题,内部辅助函数的文档注释建议,以及低于严重程度阈值的内容。具体阈值,原文没有给出。
作者最看重的是「丢弃发现数与发布发现数的比例」,并靠人工调整。这个比例能反映过滤强度,但单靠它无法判断审查是否准确。
还有一个容易漏掉的要求:改动没有问题时,返回空列表。
允许模型保持沉默,是审查工具建立信任的必要条件。
为什么要从少量严格规则开始
作者没有把所有要求塞进一个巨大提示词,而是拆成安全、错误处理、命名等聚焦规则组成的 ruleset。
这样做有两个好处:用户追问某条评论的依据时,可以回到具体规则;调整某个方向时,也更容易控制修改范围。不过,原文没有提供回归测试结果,不能据此保证改一条规则就绝不会影响其他输出。
作者回头看,最想改变的是起步顺序:先用少量严格规则,再逐步扩展。范围铺得太大,后续几周都在消除噪声;从窄范围开始,后续工作更容易表现为增加有效能力。
异常处理也守住了边界。AI 不可用时,Action 不会自动批准,而是转交人工审查。这里的 fail-closed,可以理解为自动审查失效后,不把失效当作放行理由。至于如何触发人工审查、是否阻止合并,作者没有说明。
项目仍处于早期,原文提到 GitLab 支持、按仓库配置 ruleset、PR 汇总报告,但没有清楚区分这些能力的完成状态。仓库名是 review-action,产品页名称是 code-critic.com。
我的判断
我认同作者把注意力放在「哪些评论不该出现」上。代码审查消耗的是同事的注意力,评论多并不等于质量高。让 eslint、rubocop 处理明确的风格规则,让模型处理需要上下文的判断,这种分工比较合理。
但这仍是一篇工程经验分享。作者明确说没有硬数据,也没有与纯规则工具、人工审查或其他上下文方案进行对照。我们无法据此判断它减少了多少遗漏,又增加了多少误报。
还要防止把「少说」当成唯一目标。过滤得足够严格,评论自然变少,也可能把真实问题一起丢掉。评估时需要同时检查留下的评论是否成立,以及被过滤的发现里有没有重要问题。
能拿来做什么
- 给前端或 Node 项目接入 AI 审查时,先选少量边界清楚的规则,并让 eslint 等工具继续负责已有的确定性检查,减少重复评论。
- 把被修改文件的周边代码与 diff 一起提供,并明确说明可见范围;涉及其他文件的结论,应避免仅凭当前上下文下判断。
- 将审查结果约定为包含
file、line、severity、comment的 JSON,允许返回空列表,再由程序决定哪些发现进入 PR。 - 联调时分别检查「模型返回空结果」和「请求、存储、序列化失败」;AI 不可用时保留人工审查路径,避免异常被当成审查通过。