资讯动态

基于LLM的自动化代码评审系统:从diff获取到评论回帖的工程实践

发布时间:2026/10/13 13:35:32 来源:尧图企业网站定制
很久以前代码评审还是一件挺有仪式感的事评审人坐下来逐行看diff在评论区里逐条讨论。但迭代一快起来需求一变多PR堆积如山评审就慢慢变成了流程里的一个负担——要么直接点个批准按钮了事要么等评审人空闲时再来翻一翻就是几百行改动关键问题反而被淹没了。后来我做了一个小项目内部代号叫 open-code-review本质上是一套自动化的代码评审工作流每当开发者推送新的提交或创建合并请求时系统自动拉取变更代码交给语言模型进行审查再把审查结果以评论的形式贴回合并请求页面。听起来不复杂但真正落地时从请求触发、代码差异提取、上下文组装到提示词设计、评论格式规范、重复评论去重每一步都有不少坑。这篇文章就把我搭建这套系统时踩过的坑、最终采用的实现方案、以及一批可以直接抄走的配置和代码完整地拆开讲一遍。如果你也在考虑给自己团队搭一套“不知疲倦的评审助手”或者单纯对这项工作流感兴趣那这篇实操记录应该能帮你省下不少走弯路的时间。1. 项目思路与设计拆解1.1 从哪里发现问题人工评审的四个痛点先说结论这套工具不是要替代人工评审而是要先把人工评审从“机械劳动”里解放出来。我自己经历过一个阶段团队从两三个人逐渐扩充到十几个开发合并请求从每天几个涨到每天几十个。早期评审还能认真看完每行改动后来就逐步变形了简单逻辑错误和规范问题占了大头但评审人却要花同样多的时间去读那些明显可以机器判断的代码。再往后评审排队时间越来越长代码合并速度被迫放慢而代码风格不统一、死代码、潜在空指针这类基础问题反倒在最后时刻才被翻出来。传统静态检查工具比如各类Linter能解决一部分规范问题但它们只能做规则匹配比如某个函数名单词拼错、某个文件缺少版权声明、某类错误没有捕获。它们理解不了代码的业务含义一个异步请求后面接了一个同步阻塞操作它不会告诉你这会造成主线程卡顿一个多处重复的相似函数它也不会提醒你“是不是该抽象一个公共模块出来”。LLM代码评审正好补上了这一层它能基于上下文理解代码意图在“业务逻辑风险”和“代码规范问题”之间找到平衡。同时它能生产自然语言描述告诉开发者“为什么这里要注意”“潜在影响是什么”而不是冷冰冰地抛出一个错误码。1.2 核心需求拆解一个评审机器人真正要干什么在做 open-code-review 之前我先自己列了一份需求清单明确这个项目的最简可用形态是什么样的它能监听代码仓库的事件尤其是新PR、新提交这类触发点。它能获取本次变更的diff信息并且是“可读取的、结构化”的diff。它能把这些变更交给模型用一套稳定的提示词引导模型给出评审意见。它能把这些意见写回仓库对应的讨论区并且要被开发者看得懂、易回复。它要保留“人在回路”的入口——机器评审意见只是参考最终代码是否合并还是由人决定。这套思路看起来简单但实际操作中“获取diff”这一步就会劝退很多人很多仓库托管平台对diff有尺寸限制大PR可能直接截断多个commit的改动需要聚合、去重二进制文件、锁文件、自动生成文件应该被过滤掉否则模型会被垃圾输入干扰评审质量直接下降。1.3 为什么选择“平台事件触发 服务端分析 评论回帖”的结构我当时也考虑过两种替代方案一是开发一个本地命令行工具开发者自己把代码diff导出来手动发送给模型看再手动把结果粘贴回评论区。这种方式灵活但不可持续——如果使用的人多了平台怎么记录评审历史每个人自己的API密钥怎么管理这会把辅助工具硬生生做成一个运营负担。二是做一个完整的独立评审平台把仓库镜像过来重做一套界面和流程。这种方式治理能力最强但开发成本高而且会割裂开发者的工作流。最终选择了“平台事件触发 服务端分析 评论回帖”的组合方式。如果以GitHub为例就是webhook或项目管理用途的Pull Request事件触发服务端函数服务端调用模型分析diff再调用接口回写评论。结构干净开发量可控又能自然融入团队已有的评审流程。2. 基础设施与技术选型2.1 托管环境的选择从零部署到Serverless对于这种轻到中量的自动化任务我建议优先考虑无服务器函数或容器化任务不要把整套服务常驻挂一台云主机上。原因很简单事件触发是低频的你不需要让一个进程24小时等着按调用次数付费要比按小时付费划算得多。我当时选用了一个FaaS平台配置了一个HTTP触发器。代码仓库通过webhook把事件消息推送到这个HTTP接口函数被临时拉起处理完一件事就销毁。冷启动耗时也在可接受范围内一般几百毫秒。有一点需要提醒如果你的仓库流量很大合并请求频繁更新同一个PR可能触发多次评审因此服务端必须做幂等控制。简单做法是在函数入口判断这个PR的某个版本是否已经被评审过如果评过了直接返回旧的评审结果而不是每次重新调用模型。2.2 开发语言与依赖Python的取舍本项目我选了Python没有选Node.js或Go。没有特别高深的原因主要是生态里处理Git、处理HTTP请求、处理JSON的库都很成熟而且如果要实验不同的提示词策略Python脚本迭代起来更快。代价是启动慢一点、内存占用大一点但这些对低频率的评审任务来说完全不是问题。关键依赖就三个requests发送HTTP请求调用webhook或APIPyGithub或你对应平台的SDK操作PR的评论、获取diff内容anthropic或openaiSDK调用模型推理接口。其实逻辑核心不在这些库而在于你怎么设计提示词和过滤规则。这两个做好了哪怕只用原生HTTP调用也能跑通。2.3 模型选择效果、成本、速度怎么权衡我试过几类模型总结出一个经验代码评审对模型的“上下文长度”要求远高于“算术推理能力”。日常开发中的多数问题比如空指针风险、错误处理缺失、资源泄漏、接口用法错误都藏在上下文里模型要能同时看到变更部分和周边相关函数才能得出准确判断。所以如果你的预算有限不要选超大参数量的通用旗舰模型而是用专为代码任务优化、上下文窗口足够大的中端模型。预算充足的话可以设计成“两阶段评审”先用轻量模型做格式和规范扫描再用更强的模型做逻辑和架构层面的深度分析两层结果合并输出。关于API成本我算过一笔账一份200行变动的diff拆分后大约5到10个分块每个分块调用一次模型接口大约消耗3000到8000个token。如果每天处理30个PR折算下来一天几十块人民币左右。但你要注意这是理想情况如果提示词设计粗糙把几万token一次性全塞给模型成本会翻上好几倍。2.4 提示词设计这是整个项目最大的变量提示词决定了评审意见是“有用的建议”还是“正确的废话”。我在《仓库级AI代码评审系统》那篇文章里详细讲过一些策略这里提炼出我认为最经典的四个原则给模型设定角色和背景。不要只说“你是代码评审专家”而是要说清楚你的团队用什么语言、什么框架、这次变更属于哪个模块、潜在风险区域在哪。明确输出格式。我用JSON结构输出字段包括“严重级别”“文件路径”“行号”“问题描述”“改进建议”。这比让模型输出大段自然语言更好解析可以直接转成评论表格或逐行评论。给模型提供“逆向示例”。就是告诉它“如果发现这种情况不要说”。例如“不要对没有完整上下文的片段做重构建议”“不要重复静态检查工具已能发现的问题”。限定关注范围。一次评审不要同时追求“架构级评审”和“编码风格评审”两者关注层级差太多混在一起模型会顾此失彼。2.5 尝试过但放弃的方案为什么不直接用Copilot的自动评审市面其实已经有自动Pull Request评审产品我最初也想直接引入。但用了之后发现两个问题第一这类产品通常是黑盒策略我看不到判断依据也没法在提示词层面做定向优化第二它的反馈多是结论式的缺少可操作的改进路径很多情况下只给出一句“此文件存在潜在性能问题”却不说具体位置和原因。自建 open-code-review 很大程度上就是为了获得“可控性”由我自己决定模型看什么、不看什么我可以推入团队特有的规范文档我可以调整输出格式以对接我的评论工具。为此我多写了大概500行代码但从长期维护来看这个投入是值得的。3. 核心模块与实现细节3.1 PR事件监听与响应链路整个工作流从一次Git操作开始。以合并请求场景为例开发者推送分支然后创建PR。仓库平台把PR发生的事件触发点有 opened、synchronize、reopened等以webhook形式通知给服务端。我在服务端做了一份事件过滤器只处理被“激活”的事件。对同一个PR我按 head_sha当前提交SHA做了评审记录。如果再一次收到synchronize事件并且head_sha和上次评审过的SHA一致直接跳过。这意味着一个完整的响应链路是接收webhook请求验证请求签名防止伪造解析事件类型与PR元数据检查该PR的当前版本是否评审过拉取diff执行评审逻辑回写评审结果记录评审版本。3.2 Diff获取与分块策略Diff获取是第一个大坑。直接用平台的API拉取一个大型PR的diff大概率会得到一个超级长的文本模型很快就糊了。我最终的做法是分三步第一步拉取基础信息包括变更文件列表、每个文件的增删行数。这一步用API的参数可以直接拿到。第二步过滤不需要评审的文件。你需要建立一个关键词黑名单锁文件package-lock.json、poetry.lock等、编译产物dist、build、out目录、自动生成文件、图片或二进制文件、以及单纯重命名但无内容变更的文件。这些文件如果进入提示词除了浪费token还会干扰模型对主要逻辑的判断。第三步将剩余的diff按文件、按变更块hunk拆分成一组有序的小块。每个小块最多能容纳 30~40 行的上下文超出部分再切。这样每个提示词都对应一个小块模型能集中精力处理局部问题。有一点要注意拆分之后要尽力保留“上下文中的函数签名和全局符号”。只给纯增量代码模型根本不知道这个函数是干嘛的。我的做法是给每个分块的头部附一段简短的“文件定义信息”比如这个文件是什么模块、有哪些关键函数。3.3 评审提示词模板一个可以直接复用的版本我的默认为多格式JSON输出提示词设计如下以Python项目为例MINI版工程版还需增加项目规范约束你是一名高级Python代码评审专家被要求审查一段代码变更。 背景信息 - 仓库模块用户认证模块 - 变更类型新增登录接口 - 主要风险点并发安全、token生成、异常处理 请分析以下diff片段 {diff_chunk} 评审要求 1. 关注逻辑错误、资源泄漏、并发安全、异常处理、安全漏洞 2. 不要建议引入新依赖除非确实必要 3. 不要重复静态检查工具能检测出的格式问题 4. 所有建议必须基于当前diff不做无依据的重构建议 输出严格按以下JSON格式返回 {{ review_comment: 总体的简要说明不超过50字, issues: [ {{ severity: critical|warning|suggestion, file: 文件路径, line_start: 20, line_end: 24, issue_type: bug/security/performance/style, description: 问题描述及发生原因, suggestion: 具体改进建议尽量给出代码示意 }} ] }} 不要输出JSON以外的任何内容。把这个模板发给模型之后得到的输出再经过程序解析把JSON里的issues字段按严重级别排序再组装成一条Markdown评论。另外我一般会加一条规则告诉模型如果所有问题都低于“warning”级别建议只输出“review_comment”不输出issues数组。这能有效压制无效评论的数量。模型发太多“建议提取一个常量”之类的废话会严重损伤开发者对工具的信任。3.4 评审结果回写与展示优化回写评论不是简单地把模型结果贴上去。我仔细研究过平台评论区在不同情况下的展示效果如果一次贴出几十条问题开发者很可能直接忽略如果评论结构清晰、按文件分组、按严重级别排序效果就完全不同。我的评论模板如下## 自动化评审报告commit xxx - 整体结论待改进 - 问题数量4 (critical: 1 / warning: 2 / suggestion: 1) ### 文件auth_service.py **critical** 行 56: 存在SQL注入风险 使用拼接字符串的方式处理用户输入建议改为参数化查询。 **warning** 行 78: 异常被静默吞掉 捕获异常后没有处理建议记录日志并给出提示。 --- *由 open-code-review 自动生成内容仅供参考请以人工评审结论为准。*这个结构在网页上渲染出来非常干净开发者在评论区还能直接用回复功能和机器人对话比如回复“忽略这条警告”后续机器人就知道该屏蔽此类问题。3.5 核心代码演示我摘录了最核心的一段代码做成了“最小可运行版”逻辑框架你可以直接据此改造import hmac import json import hashlib from typing import Dict, Any def verify_signature(payload_body: bytes, secret: str, signature_header: str) - bool: 校验 webhook 签名防止伪造请求。 if not signature_header: return False expected hmac.new( secret.encode(), payload_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(fsha256{expected}, signature_header) def load_reviewed_versions(redis_client, pr_id: str) - str: 读取该PR最近一次评审的提交SHA。 return redis_client.get(freview:{pr_id}) def save_reviewed_version(redis_client, pr_id: str, sha: str) - None: 记录本次评审的提交SHA。 redis_client.set(freview:{pr_id}, sha, ex60 * 60 * 24 * 7) def filter_diff_files(changed_files: list) - list: 过滤掉不需要评审的文件类型。 ignore_patterns [package-lock.json, poetry.lock, *.min.js, *.map, dist/, build/] result [] for f in changed_files: filename f[filename] if any(filename.endswith(pat) or filename.startswith(pat) for pat in ignore_patterns): continue result.append(f) return result def chunk_diff(diff_text: str, max_lines: int 40) - list: 将大段diff按文件按块拆分。 chunks [] current_lines [] current_count 0 for line in diff_text.splitlines(): current_lines.append(line) if line.startswith() or current_count max_lines: chunks.append(\n.join(current_lines)) current_lines [] current_count 0 current_count 1 if current_lines: chunks.append(\n.join(current_lines)) return chunks def build_prompt(diff_chunk: str, module_context: str) - str: 构造发送给模型的评审提示词。 return f 你是高级代码评审专家上下文为空模块下的代码评审。 模块背景{module_context} diff代码片段如下 {diff_chunk} 请按既定JSON格式输出评审意见。 上面这段代码只是一个骨干。实际项目中towards调用模型那一步还需要处理超时、重试和JOSN解析失败的情况以及评论内容超过平台长度限制需要截断的情况。3.6 评审报告的聚合与通知评审完成之后要不要主动推送给开发者我的建议是要但别太吵。在PR流程里评论本身就构成了通知。但如果团队里有人不常看评论也可以考虑把概要发到即时通讯工具的机器人接口上。具体做法是先在PR页面贴完整评论然后向即时通讯群推送一条摘要包含PR编号、变更行数、严重问题数量、以及问题最多的事件频次最高的文件名。摘要里不展示具体内容只提供一个直达链接开发者在群内点击后就能进入PR页查看。这样既能保持群信息干净又能保证重要问题不被错过。我曾经做过一轮测试群内只推摘要、不推全文之后大家对评审报告的查看率反而提升了不少因为一眼扫过去就能判断这个PR是不是需要自己立即关注。4. 实操过程中遇到的高频问题与排查方法4.1 模型总是给出“正确的废话”最典型的一种结果模型说“这段代码可能存在问题需要进一步关注”但具体是什么问题、怎么改全都没有讲。这种意见等于没给。我的应对方法是两条一是在系统提示词里明确禁止不得输出无法定位到具体行号或函数名的问题。必须给出建议代码。举例说明告诉模型“如果你不能给出修改后的代码示意那说明你并没有理解这个问题就不要输出”。二是从输出解析端做二次过滤对返回的JSON检查每条issue的description是否包含具体变量名或函数名如果一条都没有直接丢弃并记录。用这个方法废意见率能显著下降。4.2 并发请求导致模型限流与超时当同时收到好几个PR事件时如果代码使用同步方式逐一调用模型整体的处理时间就会拉得特别长。平台那边可能直接判定超时把请求断掉。解决方案是引入并发控制和重试机制。我的做法是用线程池限制并发数默认上限3。每个请求设置合理的超时时间比如60秒。对瞬时限流错误429做指数退避重试最多重试3次退避间隔设为2秒、4秒、8秒。如果某一块diff经过全部重试仍然失败就在最终报告中标记“该文件评审超时”不要以空结果代替。这里特别要说明重试操作要小批次、勤探测不要直接把并发数开到10以上。代码评审调用的是模型API它本身的吞吐是有限制的你并行推太猛反而把整个队列堵死。4.3 Diff太大超过单次提交上限有些开发者一个PR能改几十个文件、几千行代码。无论你按文件拆分还是按块拆分总量都很大。这种情况下一次性把所有块送进模型肯定会被平台的上下文窗口限制拦下来。我的建议是分级处理而不是硬塞第一优先级处理源码目录下的核心模块、涉及安全认证的部分、数据库连接相关、配置变更。第二优先级处理工具类文件、测试文件。第三优先级处理文档、示例代码通常一带而过。同时要对每个变更块做个“信息量估算”假如一个块只改了缩进或换行没有实质性的逻辑变化直接过滤掉不评审。4.4 同一PR被重复评审由于开发者会多次推送提交到同一分支webhook事件会频繁触发如果每次都重新整段评审不仅烧钱评论还会刷屏。这个问题的根治方案是“按提交版本去重”。我把每次评审对应的head_sha存在缓存里下次事件进来先比对。同时我还做了一个额外的措施只对“最后一次提交的那次变更内容”做增量评审而不是每次从基础分支开始重新整体评审。有人会问那历史提交中出现的问题不就漏掉了吗确实会漏。所以正确的节奏是当开发者第一次提PR时做一次全量评审之后每次提交只评增量部分和它可能影响到的周边函数。全量评审的结果存在报告文档里持续更新这样就不会漏。4.5 评论被平台判定为垃圾信息这个是真真切切遇到过的问题。如果用同一个机器人账号短时间内给几十个PR贴评论某些托管平台会触发反垃圾频率限制评论发不出去。解决方案是拉长提交间隔把要发表的评论先排进一个队列每10秒处理一条。另外每行评论的内容也不要全相同最好加上本次PR编号的hash值作为水印避免平台判定为大量重复内容。4.6 模型对旧代码的误报如果一个大型项目已经存在了五年里面有很多历史遗留代码。当变更diff恰好触碰这些旧代码时模型很容易因为看到了不规范的写法而给出“大规模重构”的建议。这类建议实际上不可执行因为改动面太大影响范围不可控。我加了一条硬性规则模型只评审本次变更涉及的行对于未变更的上下文代码只作为背景参考不做评价。同时在提示词里要求模型给建议时要给出“影响范围”字段如果“影响范围”包含超过三个函数或两个文件就列为“建议类”而不是必须执行的问题。5. 进阶扩展与应用场景思考5.1 支持多仓库、多分支策略当团队有多个仓库、不同分支有不同保护策略时不能一个机器人打天下。我在设计时用一份配置文件来描述规则- repo: backend-service branches: [main, release/*] ignore_files: [*.md, migrations/*] model: medium comment_tag: open-code-review - repo: frontend-app branches: [develop] ignore_files: [package-lock.json, dist/*] model: fast comment_tag: open-code-review-frontend这份配置的好处是你可以针对不同仓库的代码风格调整提示词和模型避免在某一个仓库里堆满噪音。同时还能在政策上切换“后端仓库的评审报告必须由模型给出任何warning级别都要阻塞合并前端仓库的警告只是建议。”5.2 与CI流水线的联动如果只是贴评论工具影响力是有上限的。我发现最有价值的用法是把评审结果作为CI的一个“乐观检查门禁”如果评审报告存在critical级别问题CI直接失败如果只是warning或suggestionCI仍然通过但会把报告链接附在流水线结果里。这种设计虽然名为“门禁”但不僵硬开发者在紧急修复线上问题时可以手动输入特定指令强制绕过。毕竟机器评审永远只是辅助把你监督权交还给人这工具才能真正活在一个团队里。5.3 使用本地模型作为降级方案我一直认为云端的模型API是大多数团队的默认选择但有些团队对代码隐私要求极高不适合把代码送到第三方平台。这时一个可行的方案是部署一个本地模型。本地模型对机器配置要求高起始用7B级别模型做常识性的规范评审再用130B级别模型做业务逻辑深度分析。需要说明的是同一个提示词在本地模型输出稳定性上往往不如云端API所以解析层要更温和把格式不合格的输出靠正则做一次硬解析尽量提取出文件名和行号。5.4 生成评审结果汇总与团队度量报告最后一件我觉得非常有价值的事是每周生成一份“评审报告”。自动统计每个仓库的PR数量、模型评审发现的问题数量、低严重级别问题的重复出现频率、平均修复周期等。这些数据可以直接沉淀为团队技术债务的度量输入。不过有一点我要提醒千万别把“模型发现问题数量”直接当成团队代码质量的KPI否则开发者会把大量精力花在迎合模型的提示词偏好上完全偏离初衷。更合理的做法是拿这些数据做“趋势观察”——例如某个模块连续三周都在出现同类空指针问题那就应该有人去复盘设计而不是单纯地去“多写几个if条件”。6. 一些想对后来者说的经验话open-code-review从最早的一个几百行脚本演化到后来能服务多个仓库的自动化服务过程中最深刻的体会是这类工具成败的关键不在模型本身而在“噪声控制”。一个每周只提三个精准问题的机器人远比一个每行代码都评论的机器人受欢迎且有效。普通人拿到模型API第一反应是让它“多看看、多说说”但做代码评审恰恰相反你要用提示词和过滤规则限制它“只看该看的、只说有用的”。每一次评审意见都消耗开发者的注意力一次错误的提醒需要人工真正去看代码、理解上下文后才能判定它不对这个成本比机器生成意见的成本高得多。如果你也想在自己的仓库中跑起类似的开源小工具我建议你别一上来就追求完整功能。先满足一个最简流程一个webhook入口、一个模型调用函数、一个评论回贴方法让它对某一个测试仓库生效。运行两周看看模型给出的意见分布再用两周时间去打磨提示词和评审规则。当这个循环稳定之后再考虑扩展到多仓库、门禁联动这些进阶能力。最后再分享一个思路工具本身只是代码真正让它持续发挥效益的是“反馈闭环”。要持续挖掘那些被开发者忽略的低质量评论定期把新的案例补充进提示词中。这个习惯比写任何完美代码都更重要。我自己就是靠着这份维护让一个本来可能被丢进垃圾桶的自动化小项目成了团队每天都在使用的依赖工具。

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

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

免费获取报价 →
↑