ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于LLM Agent与Git钩子的本地化代码评审流水线搭建指南

基于LLM Agent与Git钩子的本地化代码评审流水线搭建指南 1. 为什么我要自己搭一套 open-code-review团队里代码评审这件事说多了都是泪。项目一多人一忙PR 挂三天没人看是常态好不容易有人看了评论就俩字“可以”真出了线上问题回头翻记录发现当初谁也没注意到那个边界条件。商业化的代码评审工具我也试过几款功能确实全但要么按人头收费贵得离谱要么代码得传到别人的服务器上公司安全那边第一个不答应。所以我就琢磨着能不能用现成的 LLM Agent 加 Git 钩子自己拼一套跑在本地或者内网机器上的代码评审流水线这就是 open-code-review 这个项目的由来。说白了open-code-review 就是一套开源的、基于命令行CLI的自动化代码评审方案。它的核心思路特别朴素用 Git 拿到本次改动涉及的代码差异diff把 diff 连同必要的上下文一起喂给一个 LLM Agent让 Agent 按照我们预设的评审规则去逐行分析最后把结果以评论的形式回写到 PR 或者直接输出到终端。整套东西不依赖任何特定厂商的云服务模型可以换规则可以改跑在哪儿你自己说了算。这套东西适合谁呢如果你是团队里负责工程效能的那个人或者你是个独立开发者手头项目没人帮你 review又或者你们公司对代码外传有严格限制、用不了 SaaS 工具那这套方案基本就是为你准备的。它不需要你懂多深的机器学习只要你会用 Git、能跑命令行、看得懂配置文件就能把它跑起来。下面我把自己从零搭这套东西的完整过程拆开讲包括踩过的坑和最后稳定下来的配置。2. 整体架构与核心思路拆解2.1 为什么是 CLI 加 LLM Agent 这个组合先说说为什么选 CLI 而不是搞个 Web 服务。代码评审这个动作天然发生在开发者的工作流里——你写完代码git commitgit push然后开 PR。如果评审工具是一个独立的网页你就得切出去、登录、粘贴代码这个摩擦成本足以让大部分人放弃。而 CLI 工具可以直接挂在 Git 钩子上或者作为 CI 流水线的一步开发者感知不到额外的操作评审结果自动就出来了。再说 LLM Agent。这里要区分几个容易混的概念因为热词里也反复出现。LLM 是那个“大脑”比如 DeepSeek、GPT 系列、Claude 系列它们本质上是根据输入预测输出的模型。Agent 是在 LLM 外面包了一层“手脚”和“记忆”的东西——它能调用工具比如读文件、跑命令、查 Git 历史能根据中间结果决定下一步干什么。Embedding 则是把文本转成向量用来做相似度检索比如你想让评审 Agent 参考团队历史上的评审意见就得先把那些意见做 embedding 存起来。open-code-review 里LLM 负责“看懂代码并挑毛病”Agent 负责“决定看哪些文件、按什么顺序看、要不要追问”两者配合才能干成事。我选 CLI 加 Agent 而不是纯脚本调 API关键原因是 Agent 能处理“多轮”和“工具调用”。纯脚本只能把 diff 一股脑塞给模型模型一次性输出结果遇到大改动就抓瞎。而 Agent 可以先看 diff 概览发现某个文件改动很大再单独把这个文件的完整内容读进来甚至去查这个函数在别处怎么被调用的最后才下结论。这个能力对代码评审太重要了因为很多问题不是看几行 diff 能看出来的。2.2 模型选型的取舍逻辑模型这块我前后换了三次。一开始图便宜用了某个小参数模型结果它连基本的空指针判断都看不出来评审意见全是“建议添加注释”这种废话。后来换成 DeepSeek 的推理模型效果立竿见影它能真的顺着代码逻辑走一遍指出“这里如果用户传了负数下面的除法会出问题”。但推理模型慢一个中等规模的 PR 要跑两三分钟而且 token 消耗大。最后的方案是分层日常的小改动用响应快的通用模型改动超过一定行数或者涉及核心模块时自动切到推理模型。这个切换逻辑写在 Agent 的配置里根据 diff 的行数和文件路径来判断。实测下来大部分 PR 在三十秒内能出结果只有少数大改动会慢一些但换来的是评审质量明显提升。这里有个经验不要迷信“最强模型”。代码评审这个任务模型需要的是对编程语言和常见 bug 模式的敏感度而不是通用知识。有些专门针对代码微调过的模型在评审任务上比通用大模型表现更好而且便宜。你可以准备两三个模型在配置里写好各自的适用场景让 Agent 自己选。2.3 评审规则的来源与组织评审规则从哪来我的做法是三层。第一层是通用规则比如“检查是否有未处理的异常”“检查循环边界”“检查资源是否释放”这些是语言无关的写在全局配置里。第二层是语言和框架相关的规则比如 Python 里检查可变默认参数Go 里检查 error 是否被忽略这些按文件后缀匹配。第三层是项目特有的规则比如“所有数据库操作必须走我们封装的 DAO 层”“日志里不能打用户手机号”这些写在项目根目录的一个配置文件里跟着代码库走。规则的组织方式我试过两种。一种是写成自然语言描述直接拼进 prompt 里简单直接但模型有时候会漏。另一种是写成结构化的检查项每项有 id、描述、严重级别、示例Agent 逐项过。后者更可控但写起来麻烦。我最后用的是混合通用规则用自然语言项目特有规则用结构化因为项目特有的规则往往更具体、更需要严格执行。提示规则不是越多越好。我一开始写了四十多条结果模型被淹没在规则里反而漏掉了明显的 bug。后来精简到十五条核心规则评审质量反而上去了。规则要精不要多。3. 环境准备与核心组件配置3.1 Git 环境的安装与关键配置这套东西跑起来的前提是 Git 环境正常。Windows 上装 Git 我推荐直接去官网下安装包安装时注意几个选项默认编辑器选你顺手的PATH 环境选“Git from the command line and also from 3rd-party software”这样在任意终端里都能用 git 命令。装完在终端里跑git --version确认一下。Linux 和 macOS 一般自带没有的话用包管理器装就行。装完 Git 有几项配置必须做不然待会儿 Agent 拿 diff 会出问题。第一是git config --global core.quotepath false这个不设的话中文文件名会被显示成八进制转义Agent 读起来全是乱码。第二是git config --global i18n.commitEncoding utf-8和i18n.logOutputEncoding utf-8保证提交信息和日志的编码统一。第三是配置用户信息git config --global user.name和user.email这个虽然基础但很多人忘了配导致 commit 失败。还有一个容易被忽略的点git worktree。open-code-review 在评审时有时候需要同时检出多个分支的代码来对比如果直接用git checkout切换会打断你当前的工作。用git worktree add可以在另一个目录里检出代码互不干扰。我在 Agent 的工具集里就加了一个 worktree 管理工具需要看历史版本时自动创建临时 worktree评审完自动清理。3.2 CLI 工具链的搭建CLI 这块核心是一个能跑 Agent 的命令行程序。市面上有几个选择比如 codex cli、claude cli 这类它们本质上是把 LLM 的对话能力包装成命令行工具支持工具调用和文件读写。我选的是能自定义工具集的那种因为代码评审需要读 Git 信息、读文件、甚至跑静态检查工具通用 CLI 不一定都支持。如果你用的是 codex cli 这类工具安装后第一件事是确认版本和 API 配置。Windows 上有个常见问题装完了在 Windows Terminal 里跑codex --version能出版本号但一执行就报 “unable to locate the codex cli binary”。这通常是 PATH 没配好或者终端缓存了旧的环境变量。解决办法是关掉终端重开或者手动把安装目录加到 PATH 里。如果报 “login failed. check api token”那就是 API token 没配或者过期了去对应平台重新生成一个填到配置里。我自己是写了一个薄薄的包装脚本叫ocropen-code-review 的缩写它负责解析命令行参数、读取项目配置、组装 Agent 的输入、调用底层 CLI、最后把结果格式化输出。这个脚本用 Python 写的一百多行核心逻辑就是拼 prompt 和调 subprocess。你也可以用 Node 或者 Go 写看团队技术栈。3.3 模型接入与 API 配置模型接入这块我建议把 API 配置抽成一个独立的配置文件不要硬编码在代码里。配置文件里至少要有模型名称、API 地址、API key、超时时间、最大 token 数。API key 不要提交到 Git 仓库里用环境变量注入或者放在.gitignore覆盖的本地文件里。如果你在内网环境模型是本地部署的那 API 地址就填内网地址。本地部署的模型响应速度取决于你的硬件7B 参数的模型在消费级显卡上跑生成速度大概每秒十几个 token评审一个中等 PR 可能要一两分钟。这个速度能不能接受取决于你的 PR 频率。如果一天就几个 PR等等也无妨如果一天几十个那就得考虑用 API 或者加显卡。注意API key 泄露是常见事故。我见过有人把 key 写在代码里提交到公开仓库结果被人扫到一晚上跑掉几百块的额度。用环境变量用.env文件加.gitignore这是底线。4. 实操过程与核心环节实现4.1 从 Git diff 到评审输入的完整链路整个链路的第一步是拿到 diff。这里有个细节git diff默认只显示工作区和暂存区的差异但代码评审通常要看的是“这个分支相对于目标分支改了什么”。所以正确的命令是git diff main...feature-branch三个点表示从共同祖先开始比较。这个命令的输出就是评审的原始输入。拿到 diff 之后不能直接扔给模型因为 diff 里有很多噪音锁文件的变化、自动生成的代码、格式化工具造成的整文件重排。我的做法是先过滤用.gitattributes标记哪些文件是自动生成的评审时跳过再用一个简单的脚本过滤掉纯空白变化和只改 import 顺序的 diff。过滤完的 diff 通常能缩小三分之一模型处理起来更快更准。然后是上下文补充。diff 只显示改动行附近几行但很多 bug 需要看更远的上下文才能发现。比如一个函数改了参数你得看所有调用点是否都改了。Agent 在这里的作用就体现出来了它先看 diff发现某个函数签名变了就主动去搜索这个函数在项目里的所有引用把相关代码片段拉进来一起分析。这个“主动拉上下文”的能力是纯脚本做不到的。4.2 评审 Agent 的提示词设计提示词是整套系统的灵魂。我前后改了十几版最后稳定下来的结构是这样的先给 Agent 一个角色设定告诉它“你是一个有十年经验的资深工程师正在评审同事的代码你的目标是找出真正会导致 bug 的问题而不是挑风格毛病”。然后给评审规则分通用和项目特有。接着给 diff 和上下文。最后给输出格式要求比如“每条意见包含文件路径、行号、严重级别、问题描述、修改建议”。这里有个关键技巧让 Agent 先“思考”再“输出”。我在提示词里明确要求它先列出“我注意到了哪些改动”“这些改动可能影响哪些地方”“我怀疑哪里有问题”然后再给出最终评审意见。这个思考过程不一定要展示给用户但它能显著提升评审质量因为模型在生成思考过程时实际上是在做推理后面的结论会更有依据。另一个技巧是给例子。我在提示词里放了两三个“好意见”和“坏意见”的示例。好意见比如“第 42 行当 user 为 None 时下面的 user.name 会抛 AttributeError建议加判空”。坏意见比如“建议添加更多注释”。模型看了例子之后输出风格会明显向好的方向靠拢。4.3 结果回写与集成到工作流评审结果出来了怎么送到开发者面前我做了三种输出方式。第一种是终端直接打印带颜色高亮适合本地跑。第二种是生成一个 Markdown 文件放在项目根目录适合在编辑器里看。第三种是回写到 PR 的评论里这个需要调用代码托管平台的 API比如 GitLab 的 API 或者 Gitee 的 API把每条意见作为一条评论发上去。回写到 PR 这个功能最实用但配置也最麻烦。你需要一个有权限的 token需要知道 PR 的编号需要处理 API 的限流。我的做法是把这个功能做成可选的默认只输出到终端和文件需要回写时加一个--post参数。这样本地开发时不会误发评论CI 环境里才开启回写。集成到 CI 的话就是在流水线里加一步跑ocr review --base main --post。如果评审发现了严重问题可以让这一步返回非零退出码阻断合并。但我不建议一上来就阻断先跑一段时间看看误报率等大家信任这个工具了再开启阻断。4.4 一个完整的评审实例拿一个真实的例子走一遍。假设有个 Python 项目某个 PR 改了用户注册的逻辑。diff 显示在register函数里加了一行user.save()但前面没有检查user是否已经存在。Agent 拿到 diff 后先看改动然后去搜索register函数的调用点发现它在视图函数里被调用传入的user是从请求参数构造的。接着 Agent 检查数据库模型发现email字段有唯一约束。于是它得出结论如果两个请求同时用同一个邮箱注册第二个请求会在save()时抛 IntegrityError但代码没有捕获这个异常会导致 500 错误。这条意见的格式是文件views/auth.py行号 87严重级别“高”问题描述“并发注册同一邮箱时 save() 会抛 IntegrityError 导致 500”修改建议“在 save() 前先查询邮箱是否存在或者捕获 IntegrityError 并返回友好提示”。这条意见如果靠人看 diff很容易漏掉因为 diff 里只加了一行看不出并发问题。Agent 通过查调用点和模型约束把隐藏的问题挖出来了。5. 常见问题与排查技巧实录5.1 模型输出不稳定怎么办模型输出不稳定是这套系统最大的痛点。同一个 PR跑两次可能给出不同的意见有时候漏掉明显的问题有时候又冒出一些莫名其妙的意见。我总结了几个原因和对策。第一个原因是温度参数。温度越高输出越随机。代码评审这种任务温度应该设得很低我一般设 0.1 到 0.3。有些 API 默认温度是 0.7 甚至 1.0不改的话输出会非常飘。第二个原因是上下文太长。模型在长上下文里会“迷失”忘记前面的规则。对策是把最重要的规则放在 prompt 的开头和结尾中间放 diff。如果 diff 实在太长就分段评审每个文件单独跑一次最后合并结果。第三个原因是模型本身的能力边界。有些模型就是不擅长发现特定类型的 bug比如并发问题、资源泄漏。这时候要么换模型要么在规则里明确写“特别注意并发场景下的竞态条件”给模型一个提示。5.2 diff 解析出错的排查diff 解析出错的表现是Agent 说“第 X 行有问题”但你去看那个文件第 X 行根本不是它说的内容。这通常是行号计算错了。diff 里的行号是相对于文件版本的新文件和旧文件的行号不一样。如果你的 Agent 用的是旧文件的行号去定位新文件就会错位。解决办法是在解析 diff 时同时记录新旧两个行号并且在给模型的输入里明确标注“以下代码是新版本的第 X 到 Y 行”。另外如果 diff 里有多个 hunk代码块每个 hunk 的行号是独立的不能简单累加。我写了一个 diff 解析器把每个 hunk 的新旧起始行号都提取出来Agent 输出意见时要求它引用 hunk 的编号和相对行号最后由脚本换算成绝对行号。还有一个坑是文件重命名。如果 PR 里把a.py重命名成了b.pydiff 里会显示rename from a.py和rename to b.py但后面的 hunk 用的是新文件名。如果你的解析器没处理 rename就会找不到文件。这个用git diff --find-renames可以缓解但最好在解析逻辑里显式处理。5.3 API 调用失败与重试策略API 调用失败太常见了网络抖动、限流、服务端 500都会导致评审中断。我的策略是对可重试的错误超时、429、5xx自动重试三次每次间隔指数退避第一次等 1 秒第二次 2 秒第三次 4 秒。对不可重试的错误401、403、400直接报错退出因为重试也没用。重试的时候要注意幂等性。如果第一次调用其实成功了只是响应没收到重试会导致重复评审。我的做法是给每次评审生成一个唯一的 request id服务端如果支持的话用这个 id 去重。不支持的话就在本地记录已经评审过的 commit hash避免重复。还有一个问题是超时设置。默认的超时时间往往太短大 PR 还没生成完就断了。我把超时设成了 300 秒并且对于流式输出的 API用流式读取每收到一个 chunk 就重置超时计时器。这样只要模型还在输出就不会超时。5.4 常见问题速查表问题现象可能原因排查方法解决方案报错找不到 CLI binaryPATH 未配置或终端缓存新开终端跑which或where重开终端或手动加 PATH登录失败 check api tokentoken 过期或未配置检查环境变量和配置文件重新生成 token 并注入中文文件名乱码core.quotepath 未关闭git config --get core.quotepath设为 false行号对不上diff 新旧行号混用检查解析器是否区分新旧行号同时记录新旧行号并标注模型输出飘忽温度过高或上下文过长检查温度参数和 prompt 长度降温、分段评审API 限流请求频率过高看返回的 429 状态码加退避重试降低并发评审漏掉明显 bug规则太多或模型能力不足精简规则换更强模型规则控制在 15 条内回写 PR 失败token 权限不足或 API 变更检查 token scope 和 API 文档补权限或更新 API 调用提示这张表建议打印出来贴在显示器边上。我刚开始搭的时候前两周基本就在这几个问题里打转有了速查表之后排查效率高多了。6. 评审质量调优与团队落地经验6.1 如何衡量评审质量搭好了系统不代表就完事了你得知道它到底有没有用。我用了几个指标来衡量。第一个是“有效意见率”就是 Agent 提出的意见里开发者认为确实需要修改的比例。这个指标低于 30% 的话说明误报太多开发者会逐渐忽略所有意见。第二个是“漏报率”就是线上出了 bug回头查发现 Agent 当时评审过这段代码但没提出来。这个指标需要事后复盘但很有价值。第三个指标比较主观叫“开发者信任度”。我每隔一段时间会问团队成员“你觉得这个工具提的意见有用吗”如果大家说“还行偶尔能发现点东西”那就是及格如果说“挺有用的我提交前会先跑一遍”那就是优秀。信任度上来了工具才真正融入工作流。调优的方向很明确提高有效意见率降低漏报率。提高有效意见率靠精简规则和优化提示词降低漏报率靠换更强模型和补充上下文。这两个目标有时候是矛盾的强模型可能提更多意见其中有些是误报。我的经验是优先保证有效意见率因为误报多了开发者会烦烦了就不看了再强的模型也白搭。6.2 团队协作中的落地策略一个人用这套工具和团队用完全是两码事。一个人用你自己调参数、看结果怎么都行。团队用你得考虑别人的习惯和接受度。我的落地策略是分三步走。第一步是“影子模式”。工具在 CI 里跑但结果只记录不展示也不阻断合并。跑两周收集数据看看误报率和漏报率。同时让一两个愿意尝鲜的同事试用收集反馈。第二步是“建议模式”。结果展示在 PR 评论里但明确标注“以下意见由自动化工具生成仅供参考”。不阻断合并开发者可以选择忽略。这个阶段的目标是让大家习惯看到这些意见并且开始讨论哪些意见有用、哪些没用。第三步是“阻断模式”。对于严重级别为“高”的意见如果开发者没有回应比如回复“已修复”或者“误报”则阻断合并。这个阶段要谨慎一定要等前两个阶段的数据证明工具足够可靠了再开启。而且要给开发者一个“强制合并”的选项以防工具误报导致紧急修复被卡住。6.3 持续迭代的机制这套系统不是搭完就一劳永逸的。代码库在变团队在变模型也在更新。我建立了一个简单的迭代机制每个月回顾一次评审记录把误报的意见挑出来分析原因是规则问题就改规则是提示词问题就改提示词是模型问题就考虑换模型。同时把漏报的线上 bug 也挑出来看看能不能通过补充规则或者上下文来覆盖。另外我会定期把新的评审意见和团队历史上的评审意见做对比。如果 Agent 提的意见和人类评审员提的高度重合说明它学到了团队的评审风格如果它提了很多人类没提的那可能是发现了新角度也可能是误报需要人工判断。这个对比用 embedding 做相似度匹配就行不需要多复杂。注意不要频繁改提示词。我一开始每两天改一次结果每次改完都要重新观察效果根本没法判断哪个版本好。后来改成一个月改一次每次只改一个变量这样才能看出效果。7. 我踩过的几个大坑第一个坑是过度依赖模型。我一开始觉得模型越强越好把所有规则都交给模型判断。结果发现模型对项目特有的约定一无所知比如我们内部规定所有时间必须用 UTC模型根本不知道评审时从来不提。后来我把项目特有的规则单独抽出来用结构化的方式强制检查模型只负责通用逻辑和上下文分析效果才好起来。第二个坑是忽略了 diff 的噪音。有段时间 Agent 老是抱怨“这个文件格式不对”我一看是自动生成的 protobuf 文件几千行模型被这个文件拖慢了还产生了大量无意义的意见。后来加了.gitattributes过滤评审速度直接快了一倍。第三个坑是没做缓存。同一个 commit 被评审了三次因为 CI 重跑了三次。每次都要调 API浪费钱也浪费时间。后来加了一个简单的缓存用 commit hash 做 key评审结果存本地命中缓存就直接返回。这个改动很小但省了不少成本。第四个坑是提示词里放了太多例子。我一开始放了十个好意见和十个坏意见想让模型充分理解。结果 prompt 太长模型反而抓不住重点。后来精简到各两个例子效果反而更好。提示词这东西精炼比详尽重要。8. 后续可以扩展的方向这套系统目前跑得挺稳但还有不少可以扩展的地方。一个是接入静态分析工具比如让 Agent 在评审前先跑一遍 linter 和类型检查把工具的输出作为参考信息一起给模型。这样模型不用自己发现所有问题可以专注于工具发现不了的逻辑问题。另一个是建立团队评审知识库。把历史上所有评审意见做 embedding 存起来Agent 评审新代码时先检索相似的历史意见作为参考。这样新来的同事也能享受到团队积累的评审经验。还有就是多模型投票。对于同一个 PR用两个不同的模型分别评审取交集作为高置信度意见取并集作为待确认意见。这个方案能提高准确率但成本翻倍适合对质量要求极高的核心模块。我个人在实际操作中的体会是这套东西的价值不在于替代人类评审而在于把人类评审员从重复的、机械的检查中解放出来让他们专注于架构设计和业务逻辑这些真正需要人类判断的地方。Agent 负责抓空指针、资源泄漏、边界条件人类负责想“这个功能设计得对不对”“这个抽象合不合理”。分工明确了评审效率和评审质量都能上去。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进