
代码审查这件事很多团队都处在一种很尴尬的状态规则写在 Wiki 里执行全靠催。PR 在列表里躺两天没人碰reviewer 终于点开扫一眼就回个LGTM。我去年花了大半个业余时间折腾 open-code-review就是为了解决我自己团队的这种尴尬——让大模型先做第一轮全量过审人再去处理真正需要判断力的问题。这篇文章把我从选型、部署、接入到打磨提示词踩过的坑尽量写清楚给想在自己仓库里落地自动代码审查的朋友一个可以直接抄的参考。不管你是后端、前端还是测试出身只要仓库还在用 Git这套思路基本都适用。1. 自动代码审查在解决什么问题以及它的能力边界1.1 代码审查为什么沦为了形式主义先说一个我观察到的残酷现实大多数团队的 code review 根本不是质量问题而是流程问题。一个五人后端团队一天平均合入 8 个 PR每个 PR 大概 300 到 800 行变更。如果认认真真做 review每个人每天至少要花 2 个小时。现实是没人愿意拿两个小时的完整时间去看别人的代码于是出现了两类典型操作一类人扫一眼变量命名和格式就回个approve另一类人干脆拖着不看最后被 CI 的 merge 策略逼着点了通过。这种状态里真正值得人花时间关注的逻辑错误、边界条件、资源泄漏反而被淹没在大量这行超过 80 字符这个变量名看不懂之类的低水平反馈里。我并不是说风格不重要而是说人的注意力是稀缺资源应该留给机器看不出来的东西。1.2 LLM自动审查解决了哪一环解决不了哪一环open-code-review 的核心思路用一句话概括就是机器先行、人来兜底。大模型在代码审查上有什么天然优势第一速度快给一个 diff 它能秒回第二稳定不会因为今天赶着下班就放水第三模式化问题识别能力确实强空指针、未捕获异常、并发安全、密钥硬编码这类问题喂给 LLM 被它挑出来的概率并不低。但它也有明显的边界。LLM 没法理解你们公司的业务上下文不知道哪个订单状态组合是合法的也不知道哪个老接口是为了兼容历史数据才长这样。所以 open-code-review 的定位从第一天起就不是替代 reviewer而是当一个不知疲倦的前置筛子把低水平重复劳动接走把 P1/P0 级的问题用红色高亮推到人面前然后退出房间。这个定位直接决定了后面的架构取舍。既然要当筛子就一定要可配置、可裁剪、能限流而不是一股脑把所有代码都塞给模型。2. open-code-review 的架构拆解变更采集、LLM审查、结果回写2.1 三大模块变更采集、LLM审查、结果回写我落地下来open-code-review 整个工作链路可以拆成三个模块。第一个模块是变更采集。它要解决的核心问题是到底应该把哪些代码交给模型看。工具内部会通过merge-base计算 PR 头部分支和合并基线的最新公共提交然后用git diff --unified3拿到变更内容。这一步不能省略如果不往深里做直接拿 head 和 base 整个比较很容易把合并冲突产生的重复 diff 也带进去。第二个模块是 LLM 审查。它把变更内容拼装成一段结构化的 prompt携带路径过滤和严重级别配置发给后端。这里最关键的设计是强制模型返回 JSON而不是自然语言。原因很简单自然语言的审查意见没法归档没法统计也没法在 PR 里精确定位到行。先用一段带有强约束的 prompt 把模型输出框死后面所有环节才能自动化。第三个模块是结果回写。open-code-review 既支持把审查结果输出成 Markdown 报告文件也支持通过 CR 平台的 REST API 把评论以 review comment 的形式挂到指定文件的指定行上。我个人强烈建议直接回写到 PR 里因为审查意见只有出现在开发者正在看的页面上才有意义否则它只是一封没人读的邮件。2.2 触发链路CI接入与本地扫描两种模式的取舍open-code-review 支持两种触发方式我一开始两个都试了后来才想明白各自的使用场景。第一种是 CI 模式。在 GitHub Actions 或者 GitLab CI 里监听 pull_request 事件每当开发者 push 新 commit 或者提新 PR 时自动跑一次。这是团队默认推荐模式因为它是强制性的不需要任何人在本地记得执行CI 的 enter 键就是触发器。代价是你必须给 CI 配置一个带操作权限的 Token通常用GITHUB_TOKEN的默认权限就够千万不要直接把个人 Token 放进去。第二种是本地模式。命令行直接传--since main之类的增量参数检查你工作区当前未提交的内容或者本地分支与主分支之间的差异。我平时改代码前会先跑一下有点像提交前再照一遍镜子。它的优点是完全不依赖远程权限适合个人快速自查缺点是没法替团队强制质检因为你不能要求每个人都记得在本地跑一遍。我在项目里遇到过一个很常见的误区有人把 open-code-review 同时接在 CI 和本地写了一个 cron结果同一个 PR 的 review 评论被重复提交了三遍。这个问题后来靠给每个 PR 加一个审查状态的标记位解决的代码在触发前先查一下这个 PR 是否已经审查过审查过就跳过。这个细节很重要建议在接入时优先考虑幂等性。2.3 diff裁剪与上下文压缩节省token的关键设计LLM 接口是按 token 计费的而且上下文窗口有限所以裁剪不是优化项而是必须项。我实际用下来这里有三条硬经验。第一lock 文件必须过滤掉。package-lock.json、poetry.lock、go.sum这类文件一个 PR 动辄几千行变更扔给模型纯属烧钱。第二生成物必须过滤掉。编译产物、protobuf 生成代码、ORM 迁移文件都算它们的共同特征是人不会在 code review 里一行行读。第三大文件要做上下文压缩。比如一个本来是 2000 行的文件你只需要把--unified2之后的部分发过去也就是只带修改行上下各两行的小窗口。这样模型虽然在函数签名上下文上会丢失一些信息但它至少能保证 token 消耗在一个可控范围内。裁剪策略平衡下来我见过最夸张的 PR 从 8000 行 diff 被压到 1200 行有效 token审查成本直接降了 70%准确率却没有肉眼可见的下降。3. 部署和接入实操从二进制到第一份审查报告3.1 环境准备与依赖清单部署 open-code-review 最省事的方式是直接下载编译好的二进制文件扔到 CI 的 runner 上就能跑完全不用配 Python 或者 Node 依赖。如果团队环境偏好容器化也可以直接在 Dockerfile 里 ADD 一个 release 包我用下来觉得两种方式都可行差别只在镜像体积。依赖方面只有两样东西本机 Git以及一个 OpenAI 兼容的 LLM 接口。兼容这个条件很关键意味着你可以把后端从云端模型切换到本地模型。我在本地试过 Ollama 起的 qwen2.5-coder:14b也在服务器上用过其他 OpenAI 兼容网关open-code-review 只需要你填base_url和api_key就行剩下的全都一样。本地模型的好处是代码不会出内网适合对数据敏感的项目坏处是 14B 级别的模型在复杂逻辑审查上确实没有云端大模型那么敏锐。3.2 配置文件字段逐个解释仓库根目录放一个open-code-review.yml就够了。这是我最终定稿的配置模板字段不多但每个都有讲究version: 1 llm: base_url: http://127.0.0.1:11434/v1 api_key_env: LLM_API_KEY model: qwen2.5-coder:14b temperature: 0.2 max_tokens: 2048 remote: type: github token_env: GITHUB_TOKEN review: include_paths: - server/** - shared/** exclude_paths: - docs/** - tests/** - *.lock skip_generated: true concurrency: 2 severity: - blocker - major - minor - suggestion逐个说下值得注意的字段。temperature我调成 0.2因为代码审查不是创意写作温度越低输出越稳定编造问题的情况越少。max_tokens设 2048 是因为一次审查意见不需要长篇大论如果模型被要求一次输出太多内容很容易在尾部开始胡编。concurrency设成 2 是一个保守值防止同时有五个 PR 触发时把 LLM 服务打爆。关键的环境变量我都用了*_env方式引用而不是直接在配置文件里写死明文。这个设计不是洁癖是因为配置文件会进 Git 仓库如果里面放真实密钥等于把密钥直接暴露给所有有仓库读取权的人。3.3 跑通第一次自动审查的完整过程假设你已经把二进制装好并在仓库根目录写好了配置文件要手工触发一次对 PR 123 的审查命令长这样export LLM_API_KEYyour-key export GITHUB_TOKENyour-github-token open-code-review review \ --repo your-org/your-repo \ --pr 123 \ --base main \ --head feature/foo第一次跑你可能会遇到两个情况要么报找不到 merge-base要么报 PR 校验失败。前者通常是因为 CI 里 checkout 没有fetch-depth: 0导致本地缺少历史提交工具算不出合并基线。后者一般是 Token 权限不够检查一下 Token 是否具备对该仓库的 pull request 写入权限。跑通之后open-code-review 会在 PR 下面打一条评论开头会写着Auto review generated by open-code-review下面是每个问题对应的文件、行号和严重级别。我第一次看到评论落位到具体代码行的时候说实话是有一种这玩意儿真能干活的兴奋感的。这时候只是验证了链路走通真正麻烦的还在后面怎么让这个机器人少说废话、多说实话。4. 审查质量调优提示词、规则与过滤条件的实战4.1 让LLM更懂你的代码库系统提示词设计open-code-review 最值得花时间的部分是对内置 prompt 模板进行改造。默认模板只适合通用场景但我劝你不要直接用它输出的意见会非常泛比如建议增加错误处理这种正确的废话。你真正希望模型给出来的是GetUser返回的 error 没有被检查如果getUser内部碰到数据库连接失败这里会以 nil 指针继续往下执行。我的办法是把系统提示词分成三个部分角色定义、审查规矩、输出格式约束。角色定义告诉模型它是你们团队资深 reviewer熟悉你们的技术栈审查规矩列清楚什么类型的问题最常见比如空指针、goroutine 泄漏、错误吞掉、硬编码凭证等等输出格式约束要求只返回 JSON 数组。system: 你是一名资深代码审查员正在审查一个 PR 的 diff。 严重级别定义 - blocker会导致线上事故、数据丢失或安全漏洞 - major明显逻辑错误、性能风险或并发问题 - minor可读性、风格、轻微的架构问题 - suggestion优化建议不阻塞合入 只输出 JSON 数组不要输出任何解释 [{file: path, line: 18, severity: major, issue: 问题描述, suggestion: 修改建议}]实际用下来最关键的一步是在角色定义里注入你们团队的高频错误清单。比如我团队最近连续三次出现时区被写死为 UTC8 的 bug我就在 prompt 里加了一句特别注意所有时间处理相关的代码确认是否使用配置化时区。模型在之后几次审查中真的很精准地把所有硬编码时区都圈了出来这个能力是人肉 reviewer 很难长期保持的。4.2 Scope路径过滤减少噪音比堆功能更重要我发现很多第一次用自动审查工具的人会陷入一个误区让机器人看所有文件角色定义为资深架构师然后被大批 minor/suggestion 级别的噪音淹没一两天之后大家就把机器人评论当成垃圾消息直接忽略了。减少噪音的核心是路径过滤。open-code-review 的include_paths和exclude_paths本质上是一套白名单和黑名单机制我建议从第一天就配置好。实战中我的标准配置如下路径模式处理方式原因server/**shared/**include核心业务逻辑值得全量审查web/**include前端逻辑也要看但跟后端不同提示词docs/***.mdexclude文档变动不需要动静态审查**/*_test.goexclude测试文件单独用另一套规则处理*.lockvendor/**exclude生成物和依赖锁定文件纯浪费 token我见过一些团队把include_paths配得太窄导致新加入的infra/目录永远不被审查后来线上出了事故才想起来。路径过滤每一轮发版都该跟着仓库结构调整走一遍不是你配一次就能用一年的东西。4.3 严重级别分级从P0到P3怎么定规矩严重级别分级不是简单地把模型输出的文本映射成标签就完了它直接决定通知渠道和人工 review 的优先级。我在团队里定了一套很具体的规则blocker 和 major 级别的评论机器人会额外在 PR 下打一条置顶总评并且推到团队的 IM 机器人群里。minor 不进群只在 PR 页面显示。suggestion 默认折叠开发自己决定要不要处理。这个设计的效果是开发者在群里看到机器人报了一个 blocker心里会咯噔一下而不会再花时间浏览几十条 suggestion。开大会说一百遍请大家认真对待 code review不如让机器人按严重级别决定消息的投递半径人只会第一时间关注真正重要的事情。级别本身也要定义准确。blocker 必须是实打实的线上事故风险比如公网 IP 硬编码、无条件递归、事务没提交、密码进日志。如果定义得太宽随便一个性能隐患就打 blocker团队会很快疲劳。这条边界是我在跑了接近一个月之后和团队复盘才逐渐磨出来的。5. 翻车现场我在接入过程中踩过的几个坑5.1 diff行号偏移导致评论定位错误这套工具上线第一周就翻了一次车。机器人把一个 major 意见评论挂到了完全错误的代码行上开发点开一看那个位置是一行注释而真正有问题的那行代码在它上方七八行。一开始我怀疑是调用的 SDK 的问题后来发现根子在行号换算。排查链路是这样的我先用git diff --unified3查看原始 hunk header看到类似 -45,7 52,9 这种格式。模型返回的 JSON 里写的是第 45 行但这是它在看简化 prompt 时自行推算的行号模型并不知道 diff 里新旧行号之间存在 offset。也就是说评论里的 line 字段必须从旧文件行号换算成新文件行号多一个删除行后面所有行号都会偏一位。我后来直接在采集 diff 环节加了一层偏移量换算逻辑解析每个 hunk根据-a,b c,d的对应关系维护一个旧行号到新行号的映射表再把模型输出的行号统一转换成新文件坐标。修完之后评论落位准确率基本是百分之百除了极少数多 hunk 大文件偶尔还会偏一行但那已经不是工具问题了是模型对行号的理解本身就存在边界。5.2 并发审查触发限流上线第二周团队开始习惯 CI 自动审查之后一个新的问题浮出来了仓库经常同时挂着三四个 PR加上每次 push 都会触发一次LLM 服务端开始频繁报 HTTP 429 和 502。我最初没有在 open-code-review 的配置里限制并发因为单个 PR 的审查时长最多也就 30 秒我觉得怎么都挤得过去。结果是我低估了推送频率。有的同事一天 push 十几次加上服务端本身还有别人在用限流几乎是必然发生的。排查日志时发现一个很直观的现象限流都集中在同一秒内多个请求同时打到服务端的时候单独一个请求从没出过问题。修复方案是在配置里把concurrency设成 2同时加了一层指数退避重试。重试次数我设的是 3 次间隔分别为 1 秒、2 秒、4 秒超过第三次就直接丢弃本次队列任务等下一次 push 再触发。这个方案上线之后429 基本消失审查最长等待从之前的十几分钟降到了两分钟以内。5.3 中文评论在GitHub接口里乱码这个问题比较隐秘只在通过脚本直接调用 GitHub API 时出现。我最初用 shell 脚本通过 curl 模拟发起 review comment 请求返回的评论在 GitHub 页面上一片乱码类似首先这种。定位过程很有意思。我打开审查结果文件里面中文是正常的我直接 curl 访问 GitHub API 看返回 JSON也是正常的唯独页面上显示乱码。后来才意识到问题出在 POST 请求的Content-Type头。用 curl 发送 JSON 时如果只写了application/json没有显式带上charsetutf-8GitHub API 对中文会按 ISO-8859-1 解析中文必然乱码。解决办法是把 curl 的 header 改成curl -X POST \ -H Authorization: Bearer $GITHUB_TOKEN \ -H Accept: application/vnd.githubjson \ -H Content-Type: application/json; charsetutf-8 \ -d comment.json \ https://api.github.com/repos/your-org/your-repo/pulls/123/comments后来我在代码里统一用官方 SDK 来发请求SDK 会自己处理好编码问题就没有再遇到过。给后来者的建议很简单能用 SDK 就别用裸 curl能显式带 charset 就别偷懒。6. 团队落地让开发真正愿意看机器人的评论6.1 机器审查与人工审查的接力棒交接自动审查工具落地失败的原因通常不是技术问题而是流程问题。如果你的团队里机器人评论只是追加在 PR 底部开发者该不看还是不看。我把流程改成了一条明确的接力链第一步机器人审查结果自动打标blocker 和 major 级别的问题会以总评形式置顶在 PR 第一条评论里。第二步机器人不自动设置请求变更状态只做标记不对合入做硬性拦截因为初版误报率会偏高硬拦截会激怒所有人。第三步人工 reviewer 的精力集中在两件事上看机器人标注的 blocker/major 是否属实以及检查机器人覆盖不到的业务逻辑和架构方向。这套接力链跑了两周之后团队里逐渐形成一种共识机器人说的问题是第一层如果这个第一层有明显误报大家会直接在评论里艾特我反馈。我每周根据反馈调整 prompt误报率从最开始的 30% 降到了第三周的 10% 左右。6.2 数据反馈闭环用统计指标说服团队想让团队真的长期用下去光靠机器审查很酷完全不够得用数据说话。我每两周会拉一次 review 数据统计几个指标平均审查时间、机器人审查评论总数、severity 为 major 以上的问题数、这些问题在合入前被解决的比例。一段时间跑下来我们的平均首次审查时间从原来的 20 小时降到了 5 分钟以内机器人评论覆盖的代码行数远超人工覆盖范围。更让团队信服的一个数据是上线后由代码审查发现的线上 P1 级问题数量在接入后的三个月里没有明显增加而审查本身消耗的人力时间下降了三分之一。这个数据反馈闭环还有一个隐藏的好处它反向推动了团队写更小粒度的 PR。因为 PR 越小机器审查精度越高人看着也舒服。有人开始主动把一个 1000 行的大 PR 拆成三个小 PR就是为了配合自动审查和人工 review 的节奏。我没强迫任何人改工作习惯是数据本身让大家意识到了什么样的提交方式最容易被认真审查。最后再分享一个小技巧别把 prompt 固化下来就完事。我每两周会翻一次机器人近期的评论看哪些意见是开发者点赞的哪些是反复被驳回的。点赞多的部分就加强误报多的部分就削弱。这个项目给我最大的感受是自动代码审查的价值不在于它一次能发现多少问题而在于它能把人的注意力从重复劳动中释放出来让真正需要脑子判断的事得到更多关注。