资讯动态

本地化代码审查工具open-code-review:LLM Agent与Git工作流整合实践

发布时间:2026/10/19 8:30:12 来源:尧图企业网站定制
1. 为什么我要自己动手做一个 open-code-review 工具代码审查这件事做过团队协作的人都有体会。理想状态下每次提交都应该有人认真看一遍指出命名问题、边界条件、潜在的空指针、重复逻辑。但现实是评审人自己手里一堆活打开 diff 扫两眼点个 approve 就过去了。时间一长代码库里的技术债越堆越高等到某天线上出问题回头翻提交记录才发现当初那个明显的疏漏根本没人注意到。市面上当然有商业化的代码审查服务也有各种 IDE 插件。但我在实际项目里用下来总有几个绕不过去的坎一是代码要上传到第三方平台公司内部项目根本不允许二是规则固定想让它按团队自己的规范来检查配置起来非常别扭三是费用按人头算小团队还好人一多成本就上来了。所以我一直想做一个能跑在自己机器上、完全本地化、规则可定制的代码审查工具这就是open-code-review的由来。这个工具的核心思路很直接用CLI的方式调用读取Git仓库的变更把 diff 内容交给LLM Agent去分析最后输出一份结构化的审查报告。它不依赖任何云端服务所有代码和模型调用都在本地或你指定的环境里完成。适合的对象也很明确——中小团队的技术负责人、独立开发者、以及对代码质量有要求但不想引入重型平台的工程师。哪怕你只是一个人写项目用它做提交前的自检也能挡掉不少低级错误。我把它定位成一个能塞进现有工作流的小工具而不是一个需要专门学习的新平台。你可以在提交前手动跑一次也可以挂到 Git 的 pre-commit 钩子里自动触发。下面我会把整个设计思路、关键实现、踩过的坑以及怎么跟现有工具链配合完整地讲一遍。2. 拆解 open-code-review 的核心工作链路2.1 从 Git diff 到结构化审查任务的转换整个工具最基础的一环是把 Git 的变更内容变成 LLM 能理解的输入。这里有个容易被忽略的细节git diff的输出格式对人类友好但对模型来说信息密度不均匀。一个典型的 diff 包含文件路径、变更块头部hunk header、增删行标记还有上下文行。如果直接把原始 diff 丢给模型它很容易被那些 -12,7 12,9 的标记干扰把注意力放在行号计算上而不是代码逻辑上。我的做法是做一层预处理。先用git diff --unified3拿到标准输出然后按文件切分每个文件提取出变更块。对于每个变更块我保留增删行的实际内容把上下文行压缩成简短的定位信息。这样模型看到的是在某个函数的某个位置删掉了什么新增了什么而不是一堆带-前缀的文本。实测下来这种预处理能让模型对变更意图的判断准确率提升不少。具体实现上我用的是git diff --no-color --no-ext-diff来保证输出干净避免终端颜色码混进去。然后写了一个简单的解析器按diff --git分割文件按分割变更块。每个变更块记录三样东西变更前后的行号范围、删除的行内容、新增的行内容。这个结构后面既可以喂给模型也可以用来做本地规则检查比如检测是否删除了某个关键函数的调用。提示如果你的仓库里有大量二进制文件或者自动生成的代码建议在.gitattributes里标记出来或者在工具里加一个忽略列表。否则模型会浪费大量 token 去分析那些根本不需要审查的内容。2.2 LLM Agent 在审查流程里到底扮演什么角色这里要先厘清一个概念。很多人把Agent和LLM混着用其实两者不是一回事。LLM 是底层的语言模型比如 DeepSeek、GPT 系列、Claude 系列它们本质上是输入文本、输出文本的函数。而 Agent 是在 LLM 之上加了一层调度逻辑让它能调用工具、维护状态、分步骤完成任务。在 open-code-review 里LLM 负责理解代码和生成审查意见Agent 负责决定先看哪个文件要不要再查一下这个函数的定义审查完了要不要生成摘要。我设计的 Agent 流程是这样的第一步接收预处理后的 diff 列表第二步对每个文件的变更块逐个分析生成初步意见第三步把所有意见汇总去重、排序、按严重程度分级第四步输出最终报告。这个流程里LLM 的调用是多次的每次只处理一个变更块而不是一次性把整个 diff 塞进去。这样做的好处是每次调用的上下文更聚焦模型不容易看花眼而且单个变更块出错不会影响整体。另一个关键点是提示词的设计。我试过好几种写法最后稳定下来的版本包含这几部分角色设定你是一个严格的代码审查者、审查维度正确性、可读性、性能、安全性、输出格式JSON包含文件、行号、严重程度、问题描述、修改建议、以及几条硬性约束不要评论代码风格偏好、不要建议重写整个函数、如果没问题就返回空数组。这几条约束非常重要没有它们模型会变得非常话痨把每个变量命名都点评一遍报告根本没法看。2.3 CLI 交互设计与退出码约定既然是 CLI 工具交互体验就得符合命令行用户的习惯。我定的规则是默认输出人类可读的报告带颜色高亮加--json参数输出机器可解析的 JSON加--quiet只输出有问题的条目加--strict时只要发现严重问题就返回非零退出码。这个退出码约定很关键因为它决定了这个工具能不能挂到 CI 流程里。比如在 pre-commit 钩子里如果审查发现严重问题退出码非零提交就会被阻止。命令设计上我参考了常见 CLI 工具的习惯# 审查当前工作区未提交的变更 open-code-review # 审查最近一次提交 open-code-review --commit HEAD # 审查两个提交之间的差异 open-code-review --range main..feature # 输出 JSON 格式方便其他工具消费 open-code-review --json # 严格模式有严重问题就退出码非零 open-code-review --strict参数解析我用的是 Python 的argparse没有引入额外的 CLI 框架。原因很简单这个工具的参数不多argparse足够用而且它是标准库不增加安装负担。如果你用 Node.js 写commander或yargs都是不错的选择但要注意别为了几个参数引入一个庞大的依赖树。3. 把 LLM 接进来模型选型与调用策略3.1 本地模型和远程 API 的取舍这是整个项目里我最纠结的一个决策点。用远程 API比如 DeepSeek 或者 GPT 系列好处是模型能力强、不需要本地算力、接入简单。坏处是代码要发出去虽然大多数 API 提供商承诺不保留数据但对于公司内部项目这个风险很多团队不愿意承担。用本地模型比如通过 Ollama 跑一个代码能力不错的开源模型好处是数据完全不出本机坏处是对硬件有要求而且模型能力参差不齐。我最后的方案是做成可切换的。配置文件里指定provider支持openai-compatible和ollama两种。openai-compatible可以对接任何兼容 OpenAI 接口的服务包括 DeepSeek 的 API、公司内部部署的推理服务等。ollama则走本地。这样用户可以根据自己的情况选。对于代码审查这个场景我的经验是如果只是做基础的正确性检查本地跑一个 7B 到 14B 的代码模型就够用了如果要检测更微妙的逻辑问题远程的大模型效果明显更好。配置文件的格式我用的是 TOML放在项目根目录的.open-code-review.toml也支持放在用户主目录下作为全局配置。内容大概长这样[llm] provider openai-compatible base_url https://api.example.com/v1 model deepseek-chat api_key_env OCR_API_KEY max_tokens 2048 temperature 0.2 [review] ignore_patterns [*.lock, dist/*, *.min.js] severity_threshold warning max_files 20api_key_env这个设计是为了避免把密钥写进配置文件。工具启动时从环境变量读取这样配置文件可以安全地提交到仓库里团队成员共享同一份配置但各自的密钥独立管理。3.2 提示词工程让模型说人话而不是套话提示词这块我踩了不少坑。最开始我写的提示词是请审查以下代码变更指出所有问题。结果模型返回的东西五花八门有的用中文有的用英文有的列了一堆无关紧要的风格建议有的干脆把整个函数重写了一遍。后来我逐步收紧形成了现在这个版本你是一个经验丰富的代码审查者。请审查以下代码变更只关注以下维度 1. 正确性是否有逻辑错误、边界条件遗漏、空值处理缺失 2. 安全性是否有注入风险、敏感信息泄露、权限校验缺失 3. 性能是否有明显的性能问题如循环内查询、不必要的重复计算 4. 可维护性是否有难以理解的逻辑、过长的函数、重复代码 不要评论变量命名风格、缩进、注释多少、个人编码偏好。 输出格式为 JSON 数组每个元素包含 - file: 文件路径 - line: 行号 - severity: error | warning | info - message: 问题描述用中文一句话说清楚 - suggestion: 修改建议用中文具体可操作 如果没有发现问题返回空数组 []。这个提示词的关键在于不要评论那一段。没有它模型会忍不住对每个变量名发表意见。另外severity的分级也很重要它决定了报告的可读性。我把error定义为必须修复否则可能出 bugwarning定义为建议修复有潜在风险info定义为可以关注但不紧急。这样用户在--strict模式下可以只拦截error级别的问题。3.3 处理模型返回的脏数据即使提示词写得很清楚模型偶尔还是会返回不符合格式的内容。比如 JSON 外面包了一层 markdown 代码块标记或者某个字段缺失或者severity返回了critical这种我没定义的值。这些情况必须在代码里处理掉否则工具会直接崩溃。我的处理策略是分三层。第一层解析前先做清洗把json 和这种标记去掉把首尾空白去掉。第二层用json.loads解析如果失败尝试用正则提取出最外层的[...]再解析。第三层解析成功后逐条校验字段缺失的字段填默认值severity不在枚举范围内的降级为info。经过这三层处理基本上不会因为模型返回格式问题导致工具挂掉。注意不要假设模型每次都会返回合法 JSON。我实测下来大概每二十次调用会有一次返回格式有问题比例不高但足以让工具在关键时刻掉链子。所以容错逻辑是必须的不是可选的。4. 和 Git 工作流的深度整合4.1 挂到 pre-commit 钩子上的正确姿势把 open-code-review 挂到 pre-commit 钩子上是最自然的用法。但这里有个坑pre-commit 钩子执行时Git 的索引状态和正常工作区不完全一样。如果你直接用git diff拿到的是工作区和暂存区的差异而不是暂存区和 HEAD 的差异。正确的做法是用git diff --cached来获取即将提交的内容。钩子脚本大概长这样#!/bin/sh # .git/hooks/pre-commit open-code-review --cached --strict --quiet if [ $? -ne 0 ]; then echo 代码审查发现严重问题提交已阻止。 echo 如需跳过请使用 git commit --no-verify exit 1 fi这里--cached参数让工具去读暂存区的内容--strict让它在有error级别问题时返回非零--quiet让输出简洁一些。最后那个提示信息很重要它告诉用户怎么绕过检查。虽然我们不希望用户绕过但必须给他们这个选项否则遇到紧急情况会很麻烦。另外要注意pre-commit 钩子默认不会被克隆到新仓库里。所以我在工具里加了一个open-code-review install-hook命令自动把钩子脚本写到.git/hooks/目录下并设置可执行权限。这样新加入项目的成员跑一次这个命令就能配好。4.2 在 CI 流水线里做增量审查除了本地钩子CI 里也可以跑。但 CI 环境和本地不一样它通常是对整个 PR 做审查而不是单次提交。这时候用--range参数指定目标分支和当前分支的差异比较合适。比如在 GitHub Actions 里- name: Run code review run: | open-code-review --range origin/main..HEAD --json review.json env: OCR_API_KEY: ${{ secrets.OCR_API_KEY }} - name: Check review results run: | python -c import json with open(review.json) as f: issues json.load(f) errors [i for i in issues if i[severity] error] if errors: print(f发现 {len(errors)} 个严重问题) for e in errors: print(f\ {e[file]}:{e[line]} - {e[message]}\) exit(1) 这个流程的好处是审查结果可以存档也可以作为 PR 评论自动发出来。不过 CI 里跑要注意 token 消耗如果 PR 很大diff 内容多调用次数会上去。我在工具里加了max_files配置超过这个数量的文件就只审查前 N 个并在报告里注明因文件数量限制部分文件未审查。4.3 处理大仓库的性能问题大仓库是另一个挑战。一个几千文件的仓库如果每次提交都全量审查那工具就没法用了。我的策略是只审查变更涉及的文件而且只审查变更块附近的内容。但即使这样如果一次提交改了五十个文件调用次数还是很多。优化手段有几个。第一并发调用。LLM 的 API 调用是 IO 密集型的用线程池并发跑能显著缩短总时间。我用concurrent.futures.ThreadPoolExecutor控制并发数默认 4 个可以通过配置调整。第二缓存。同一个变更块如果之前审查过且代码没变结果可以缓存起来。我用变更块的哈希值作为 key把结果存到本地 SQLite 里。第三跳过。对于纯文档变更、配置文件变更、测试文件变更可以通过ignore_patterns跳过不浪费调用。实测下来一个中等规模的提交10 个文件左右每个文件几十行变更用并发加缓存总耗时能控制在 30 秒以内。这个速度对于 pre-commit 钩子来说可以接受如果嫌慢可以改成只在 CI 里跑。5. 踩过的坑和对应的解决方案5.1 模型把上下文行当成变更内容这是最早遇到的一个问题。Git diff 里变更块会包含几行上下文用来帮助定位。这些上下文行在 diff 里没有或-前缀但模型有时候分不清会把上下文行也当成变更内容来审查然后报告说这行代码有问题实际上这行根本没改。解决方案是在预处理阶段就把上下文行剥离掉只保留增删行。但完全剥离又有个问题有些变更只有理解了上下文才能判断对错。比如删掉了一行return result如果不知道这个函数在做什么就没法判断这个删除是否合理。所以我的做法是保留上下文但在提示词里明确标注哪些是变更、哪些是上下文。具体格式上我用[REMOVED]和[ADDED]标记增删行上下文行不加标记并在提示词里说明只有带标记的行是本次变更其他行仅用于提供上下文。5.2 行号对不上导致报告无法定位另一个坑是行号。模型返回的行号是基于它看到的输入文本的行号而不是文件里的真实行号。如果直接把模型返回的行号写到报告里用户点过去会发现位置完全不对。解决办法是在预处理阶段就记录每个变更块在文件中的真实起始行号然后在模型返回结果后把模型的行号映射回真实行号。具体来说我在每个变更块的输入里加上一行[FILE: path] [HUNK: start_line]让模型知道这个块对应文件的哪一行开始。模型返回行号时我再用这个起始行号做偏移计算。这个映射逻辑要小心处理因为模型可能返回相对于变更块的行号也可能返回相对于文件的行号需要在提示词里明确约定。5.3 API 调用失败和重试策略网络请求失败是常态尤其是在并发调用的时候。我遇到过几种失败超时、限流、服务端 500、返回内容被截断。每种的处理方式不一样。超时和 500 可以直接重试我设置了最多 3 次重试每次间隔指数退避。限流通常是 429 状态码需要等更久我读到响应头里的Retry-After字段按它指定的时间等待。返回内容被截断比较麻烦通常是因为max_tokens设小了模型还没输出完就被切断了。这种情况重试也没用需要调大max_tokens或者把变更块拆得更小。我在工具里加了一个检测如果返回的 JSON 解析失败且内容长度接近max_tokens就在报告里提示可能因 token 限制导致输出不完整。提示并发调用时建议把并发数控制在 4 到 8 之间。太高容易触发限流太低又起不到加速效果。具体数值取决于你用的 API 服务的限制可以先从 4 开始试。5.4 误报和漏报的平衡代码审查工具最怕两种极端一种是误报太多用户被淹没在无意义的提示里最后干脆不看另一种是漏报太多用户觉得这工具没用还不如自己看。平衡这两者是个持续调优的过程。我的经验是宁可漏报也不要误报。因为误报会消耗用户的信任而漏报至少不会造成干扰。所以在提示词里我加了一条如果不确定某个问题是否真实存在不要报告。这条约束显著降低了误报率。另外对于info级别的问题我默认不在--quiet模式下显示只在完整报告里列出。这样用户日常使用时看到的是真正需要关注的问题。还有一个技巧是让模型给出置信度。我在输出格式里加了一个confidence字段取值 0 到 1。然后在报告生成时把置信度低于 0.6 的条目过滤掉。这个字段模型给得不一定准但作为一个额外的过滤维度还是有用的。6. 扩展方向与个人实践体会这个工具目前能满足我日常的代码审查需求但还有不少可以扩展的地方。比如支持自定义规则让团队把自己的编码规范写成规则文件工具在调用 LLM 之前先跑一遍本地规则检查这样既省 token 又能保证规范落地。再比如支持审查历史记录把每次审查的结果存下来过一段时间可以分析哪些类型的问题反复出现帮助团队改进。另一个有意思的方向是和 IDE 集成。虽然 CLI 已经够用但如果能在 VS Code 里直接看到审查结果体验会更好。不过这个优先级不高因为 CLI 已经能覆盖大部分场景而且 IDE 插件需要维护额外的代码库。我在实际使用中最大的体会是不要指望这个工具能替代人工审查。它的定位是第一道防线帮你挡掉那些明显的、机械性的问题让人工审查能聚焦在架构设计、业务逻辑这些更需要判断力的地方。把它当成一个不知疲倦的初级审查者而不是一个资深架构师。心态摆正了用起来就顺了。最后分享一个小技巧如果你用的是远程 API建议在项目初期先用本地模型跑一段时间把提示词和规则调稳定了再切到远程。因为调提示词阶段需要反复试错用远程 API 会消耗不少额度。等提示词稳定了再切到能力更强的远程模型效果和成本都能兼顾。

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

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

免费获取报价 →
↑