1. 从零搭建代码评审机器人的真实动机代码评审这件事做过团队协作的人都有体会。一个人写完功能提交合并请求然后眼巴巴等着同事来审。同事手头一堆活可能拖到第二天才看看完提几条意见改完再等一轮。一个不大的改动来回折腾两三天是常事。更麻烦的是有些问题其实很机械——变量命名不规范、函数太长、缺少边界判断、日志打得不合理——这些完全可以用工具自动查出来却占用了大量人工评审时间。我所在的小团队一共六个人后端四个、前端两个每天产生的合并请求大概在十五到二十个之间。之前试过几种方案纯人工评审效率太低用现成的静态检查工具规则固定改起来不灵活想自己写一套又觉得工作量太大。后来琢磨着能不能用大模型的能力做一个开源的、可自己部署的代码评审助手让它先过一遍把明显的问题标出来人工只需要看那些真正需要判断力的部分。这个想法落地之后就是open-code-review这个项目的雏形。它的核心定位很明确一个可以自己部署、自己配置规则、对接代码托管平台、用大模型做代码评审的开源工具。它不是要替代人工评审而是把人工从重复劳动里解放出来。适合谁用我觉得三类人最需要一是小团队没有专职代码质量把控角色的二是想在自己项目里引入自动化评审但不想依赖商业服务的三是对大模型应用开发感兴趣、想拿一个完整项目练手的开发者。这篇文章我会把整个项目的设计思路、技术选型、核心模块实现、部署踩坑、以及实际跑起来之后的效果完整地讲一遍。不是那种“第一步装依赖、第二步改配置”的流水账而是把每个决策背后的原因说清楚让你看完能自己复现一套也能根据自己团队的情况做调整。2. 整体架构设计与技术选型背后的取舍2.1 为什么选择事件驱动而不是定时轮询项目要对接代码托管平台第一个要解决的问题就是怎么知道有新的合并请求需要评审最直接的想法是定时轮询每隔几分钟去查一次有没有新的合并请求。这个方案实现简单但问题很明显延迟高、浪费请求配额、而且当合并请求数量多的时候轮询周期内可能积压很多任务。我最终选择的是Webhook 事件驱动的方案。代码托管平台在合并请求创建或更新时会主动推送一个 HTTP 请求到我们配置的地址。服务端收到请求后解析出项目、分支、合并请求编号等信息然后触发评审流程。这样做的好处是实时性高基本是秒级响应而且不浪费接口调用次数只在有事件的时候才工作。但事件驱动也带来一个新问题如果服务端挂了事件就丢了。所以我在架构里加了一个消息队列做缓冲。Webhook 接收端只负责快速响应平台通常要求 200 状态码在几秒内返回把任务丢进队列就结束。真正的评审逻辑由消费者从队列里取任务来执行。这样即使评审服务暂时不可用任务也不会丢等恢复了继续处理。队列选型上我用了Redis 的 List 结构做简单的任务队列。没有上 Kafka 或者 RabbitMQ原因很简单这个项目的并发量不会特别大一个中等规模的团队每天几十到上百个合并请求Redis 完全扛得住而且部署和维护成本低得多。用LPUSH入队、BRPOP出队配合一个失败重试的机制够用了。2.2 大模型接入层的抽象设计评审的核心是让大模型去分析代码差异。但大模型服务商很多接口格式、认证方式、返回结构都不一样。如果直接把某一家厂商的 SDK 写死在业务逻辑里以后想换或者想同时支持多家就很麻烦。所以我做了一层模型接入抽象。定义一个统一的接口包含review_code(diff_content, rules)方法输入是代码差异和评审规则输出是结构化的评审意见列表。每个模型服务商实现这个接口业务层只依赖接口不关心底层用的是哪家。目前实现了两种接入方式一种是HTTP API 直连适用于提供标准 REST 接口的模型服务另一种是本地模型推理通过一个轻量级的推理框架加载开源模型适合对代码隐私要求高、不想把代码传到外部的场景。两种方式通过配置文件切换不需要改代码。这里有个经验不同模型对提示词的敏感度差别很大。同样的评审规则用 A 模型可能输出很规范用 B 模型就乱七八糟。所以我在接入层里加了一个提示词模板管理模块每个模型可以配置自己的提示词模板而不是全局共用一套。这个设计后来被证明非常有必要。2.3 评审规则的配置化思路如果评审规则写死在代码里每改一条规则都要重新部署那这个工具就没法用了。所以规则必须是可配置的。我的做法是规则定义在一个 YAML 文件里每条规则包含规则名称、规则描述、严重级别、适用的文件类型、以及具体的检查逻辑描述。检查逻辑描述不是代码而是自然语言比如“检查函数是否超过 50 行”“检查是否有未处理的异常”“检查变量命名是否符合驼峰规范”。这些描述会作为提示词的一部分传给大模型让模型根据描述去判断。为什么不用正则或者 AST 来做规则检查因为很多代码质量问题很难用固定模式描述。比如“这个函数职责是否单一”“这个变量名是否表意清晰”这些需要理解代码语义才能判断正是大模型擅长的。当然纯格式问题比如缩进、行宽用传统的 linter 更合适所以我在流程里也集成了基础的格式检查两者互补。规则文件支持按项目覆盖。全局有一套默认规则某个项目如果有特殊需求可以在项目根目录放一个.review-rules.yml评审时会合并全局规则和项目规则项目规则优先级更高。这样不同技术栈的项目可以有自己的评审标准。3. 核心模块拆解从 Webhook 接收到评审意见生成3.1 Webhook 接收与签名验证Webhook 接收端是整个系统的入口。它要做三件事验证请求来源、解析事件类型、把任务入队。验证请求来源很重要。如果不验证任何人都可以往这个接口发请求伪造评审任务。代码托管平台通常会在请求头里带一个签名用预先配置的密钥对请求体做哈希。服务端用同样的密钥和算法算一遍对比签名是否一致。不一致的直接拒绝。解析事件类型时要注意同一个 Webhook 地址可能会收到多种事件合并请求创建、合并请求更新、评论创建等等。我们只关心合并请求的创建和更新事件其他事件直接忽略。解析出项目标识、合并请求编号、源分支和目标分支之后组装成一个任务对象序列化成 JSON 存入 Redis 队列。这里有个细节去重。有时候平台会重复推送同一个事件或者用户在短时间内多次更新合并请求导致队列里堆积多个相同任务。我在入队前会检查队列里是否已经有相同项目相同合并请求的待处理任务如果有就跳过。这个逻辑用 Redis 的 Set 结构实现入队时加成员出队时移除。3.2 代码差异获取与预处理消费者从队列取出任务后第一步是获取代码差异。通过代码托管平台提供的 API传入项目标识和合并请求编号拿到差异内容。差异格式通常是统一的 diff 格式包含文件路径、变更类型新增、修改、删除、以及具体的变更行。拿到差异后不能直接丢给大模型要做预处理。原因有两个一是差异可能很大超出模型的上下文窗口二是差异里可能包含一些不需要评审的内容比如自动生成的代码、依赖锁文件、二进制文件等。预处理包括过滤文件根据文件扩展名和路径模式跳过不需要评审的文件截断差异如果单个文件的差异超过一定行数只取前 N 行或者按函数块切分提取上下文对于修改的文件除了变更行本身还需要获取变更行周围的代码作为上下文帮助模型理解。我设置了一个阈值单个合并请求的总差异行数超过 2000 行时只评审变更最集中的前五个文件并在评审意见里注明“由于差异过大仅评审了部分文件”。这是基于实际经验的妥协因为超大合并请求本身就应该拆分评审工具没必要为这种情况消耗大量资源。3.3 提示词工程让模型输出结构化的评审意见这是整个项目里最花时间打磨的部分。一开始我直接把差异内容丢给模型说“请评审这段代码”结果模型输出一大段散文式的评论有的问题说了有的没说格式也不统一没法程序化处理。后来改成结构化提示词。提示词包含几个部分角色设定你是一个资深代码评审员、评审规则列表从配置文件读取、输出格式要求每条意见包含文件路径、行号、严重级别、问题描述、修改建议、以及代码差异内容。输出格式要求用 JSON Schema 描述让模型按固定结构输出。实际测试下来模型对 JSON 格式的遵循程度和提示词的明确程度强相关。我总结了几条经验第一在提示词里给一个输出示例比只描述格式效果好得多第二严重级别限定为三个枚举值critical、major、minor不要让模型自由发挥第三要求模型对每条意见给出置信度评分低于阈值的意见可以过滤掉减少误报。还有一个坑模型有时候会把不是问题的地方标成问题比如把某种常见的代码风格当成错误。解决办法是在提示词里加一句“如果代码符合项目已有风格且无明显问题不要强行提出意见”。另外对于规则列表里没有覆盖的问题模型有时会自由发挥我加了一个约束“只针对给定的评审规则提出意见不要引入规则之外的评判标准”。3.4 评审意见的格式化与回写模型返回 JSON 格式的评审意见后需要解析、校验、然后回写到代码托管平台。解析时要做容错处理。模型返回的 JSON 可能不完整比如少了闭合括号或者某个字段类型不对。我用了一个宽松的 JSON 解析器解析失败时尝试修复常见问题比如补全括号、去掉多余的逗号。如果实在解析不了就把原始返回内容记录到日志同时给合并请求发一条评论说明“自动评审失败请人工检查”。校验主要是检查文件路径和行号是否在差异范围内。模型有时会给出一个不在变更行范围内的行号这种意见没法在平台上精确定位我会把它转成文件级别的评论而不是行级别的评论。回写方式有两种行内评论和汇总评论。行内评论直接挂在具体的代码行上适合指出的问题就在那一行汇总评论挂在合并请求的讨论区适合整体性的建议。我根据意见的类型自动选择如果意见包含明确的行号用行内评论否则用汇总评论。严重级别为 critical 的意见会额外 合并请求的创建者确保他能看到。4. 部署与配置中那些文档不会告诉你的坑4.1 环境变量与密钥管理项目需要配置不少敏感信息代码托管平台的访问令牌、Webhook 签名密钥、大模型服务的 API Key、Redis 连接信息等。这些绝对不能硬编码在代码里也不能提交到代码仓库。我用的是环境变量加配置文件的方式。敏感信息全部通过环境变量传入配置文件里只放非敏感的配置项比如评审规则路径、日志级别、队列名称等。配置文件里用占位符引用环境变量比如${PLATFORM_TOKEN}启动时替换。部署时环境变量通过容器编排平台的 Secret 机制注入或者用.env文件加载仅限本地开发。这里有个容易忽略的点日志里不能打印敏感信息。我在日志输出前做了一层脱敏处理把令牌、密钥等字段替换成***。这个逻辑看起来简单但如果不做一旦日志被收集到日志平台密钥就泄露了。4.2 Redis 连接与队列可靠性Redis 作为队列有几个配置项直接影响可靠性。第一要开启持久化。Redis 默认是内存存储重启后数据就没了。我配置了 AOF 持久化每秒同步一次这样即使 Redis 重启队列里的任务也不会丢。第二设置合理的超时时间。消费者从队列取任务时用BRPOP设置超时时间为 30 秒避免消费者长时间阻塞。第三处理失败任务。如果评审过程中出现异常任务不能直接丢掉要重新入队或者转入死信队列。我实现了一个简单的重试机制任务对象里带一个重试次数字段初始为 0。评审失败时重试次数加一如果小于 3 就重新入队否则转入一个失败队列同时发告警通知。失败队列里的任务可以手动重新触发排查完问题后重新处理。4.3 大模型调用的超时与重试策略大模型接口的响应时间波动很大快的时候几秒慢的时候可能几十秒。如果不设超时消费者线程会一直卡住影响其他任务处理。我设置了两级超时连接超时 10 秒读取超时 120 秒。连接超时是建立 TCP 连接的时间读取超时是等待模型返回的时间。超过读取超时后主动断开连接把任务标记为失败走重试流程。重试策略上不是所有失败都值得重试。网络超时、服务端 5xx 错误可以重试认证失败、请求参数错误这种重试也没用直接标记为永久失败。我在接入层里区分了可重试异常和不可重试异常分别处理。还有一个经验并发控制。如果同时有多个任务在调用大模型接口可能会触发服务端的限流。我在消费者端加了一个信号量限制同时进行的模型调用数量默认是 3 个。这个值可以根据模型服务的承载能力调整。4.4 评审规则的调试与迭代规则配置好之后怎么知道它有没有效果不能等真实合并请求来了再看那样太慢。我加了一个本地调试模式提供一个命令行工具输入一个本地代码仓库路径和两个分支名工具会模拟评审流程输出评审意见但不回写到平台。这样可以在本地快速验证规则是否合理、提示词是否有效。调试时我建议先用历史合并请求做测试。把过去已经评审过的合并请求拿出来跑一遍自动评审对比人工评审的意见看看自动评审覆盖了多少、误报了多少。根据结果调整规则和提示词。我自己的经验是第一版规则通常误报率比较高需要迭代两三轮才能达到可用的状态。5. 实际运行效果与人工评审的配合方式5.1 自动评审能覆盖哪些问题跑了一段时间之后我统计了一下自动评审发现的问题类型。占比最高的是代码风格和命名规范类问题大概占 40%。这类问题规则明确模型判断准确率高。其次是潜在的逻辑缺陷比如空指针风险、资源未释放、边界条件缺失占 30% 左右。这类问题模型有时会误报但漏报少人工复核一下就能确认。剩下的是可读性和维护性建议比如函数过长、注释缺失、重复代码占 30%。从严重级别分布看critical 级别的问题很少大概 5%但一旦发现就是真正重要的比如明显的安全漏洞或者会导致运行时错误的缺陷。major 级别占 25%minor 级别占 70%。这个分布和人工评审的感知基本一致。5.2 误报处理与人工反馈闭环误报是不可避免的。模型不是人它没有项目的完整上下文有时候会把合理的代码判断为问题。关键是怎么处理误报。我的做法是每条自动评审意见都带一个“忽略”按钮。评审者在平台上点击忽略后这个意见会被记录到一个反馈表里。定期分析这些反馈如果某条规则被频繁忽略说明规则本身有问题需要调整或者删除。如果某个文件的误报特别多可能是这个文件的代码风格比较特殊可以在项目规则里针对这个文件放宽标准。这个反馈闭环很重要。没有它规则就没法持续优化用久了大家就会觉得“自动评审都是废话”然后直接无视。有了反馈机制规则会越来越准评审者的信任度也会提高。5.3 人工评审应该聚焦在什么地方自动评审把机械性的问题过滤掉之后人工评审就可以聚焦在真正需要判断力的地方。根据我的观察以下几类问题自动评审很难处理好需要人工重点关注架构设计层面的问题。比如这个改动是否引入了不合理的依赖、是否破坏了模块边界、是否与整体设计方向一致。这些需要理解系统全貌模型做不到。业务逻辑的正确性。模型可以检查代码语法和常见模式但无法判断业务规则是否正确实现。比如一个折扣计算逻辑模型不知道业务上折扣应该怎么算只能检查代码有没有明显错误。性能和扩展性考量。模型可以指出一些明显的性能问题比如循环里查数据库但对于更复杂的性能影响比如缓存策略是否合理、并发场景下是否有竞争条件还是需要人工判断。所以我的建议是自动评审作为第一道过滤人工评审作为第二道把关。自动评审负责“把明显的问题找出来”人工评审负责“判断这个改动是否真的可以合并”。两者配合效率最高。6. 后续可以继续打磨的几个方向项目跑起来之后我发现还有几个地方可以继续优化。一个是增量评审目前每次合并请求更新都会重新评审全部差异但实际上只有新增的变更需要评审。如果能记录上次评审的位置只评审增量部分可以节省不少模型调用成本。另一个是评审意见的优先级排序。现在意见是按文件顺序排列的但评审者可能更关心 critical 级别的问题。如果能按严重级别和置信度综合排序把最需要关注的意见排在前面体验会更好。还有一个想法是多模型投票。对于同一条规则用两个不同的模型分别评审如果都认为有问题置信度就更高如果意见不一致就标记为需要人工确认。这样可以进一步降低误报率但成本也会增加需要权衡。这些方向我还在陆续尝试有新的进展再分享。如果你也在做类似的事情欢迎交流。