ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于LLM Agent的本地代码审查工具:open-code-review实战

基于LLM Agent的本地代码审查工具:open-code-review实战 1. 为什么我要自己动手做一个 open-code-review 工具代码审查这件事做过几年开发的人都有体会。团队规模小的时候大家靠自觉提交前互相瞄一眼问题不大。一旦项目变大、协作人数变多代码审查就成了一个既重要又痛苦的环节。重要是因为它能拦住大量低级错误、风格不一致和潜在缺陷痛苦是因为人工审查耗时耗力审查者容易疲劳标准也难以统一。市面上有不少商业化的代码审查服务功能确实强大但有几个问题让我一直不太舒服。第一是数据要上传到别人的服务器公司内部代码多少有些敏感走外部服务总归要过安全审批流程冗长。第二是定制化程度有限每个团队的规范不一样想加一条自己的检查规则往往要等平台排期。第三是成本按人头按月收费人一多就是一笔不小的开销。所以我就想能不能用现在的大语言模型能力配合 Git 和命令行工具自己搭一个轻量的代码审查工具。核心思路很简单在本地或者自己的服务器上跑一个 CLI 程序它读取 Git 仓库的变更把 diff 内容送给大语言模型分析模型返回审查意见程序再把意见整理成可读的报告。整个过程数据不出内网规则自己定成本就是模型调用的费用可控得多。这个项目我给它起名叫 open-code-review定位就是一个开源的、基于 CLI 的、由 LLM Agent 驱动的代码审查助手。它适合几类人一是中小团队的技术负责人想低成本建立代码审查机制二是独立开发者一个人写代码没人帮忙 review想让 AI 当个第二双眼睛三是对 LLM Agent 感兴趣、想找个实际场景练手的工程师。不管你之前有没有用过类似的工具只要你会基本的 Git 操作就能把它跑起来。接下来我会把这个项目的设计思路、核心实现、实操步骤和踩过的坑完整地讲一遍。内容会比较长但都是实打实的经验不是那种看完就忘的概述。2. 整体设计与技术选型拆解2.1 核心架构为什么是 CLI 加 LLM Agent 的组合先说架构选择的逻辑。代码审查工具的实现方式有好几种我逐一分析过。第一种是纯规则引擎用正则表达式或者 AST 分析来检查代码。这种方式速度快、成本低但只能发现预定义的问题比如命名不规范、缺少注释、圈复杂度过高。它没法理解代码的意图也就没法给出“这段逻辑可能有并发问题”这种需要推理的意见。第二种是集成到 IDE 里的插件。体验好写代码时就能看到提示。但问题是它通常只关注当前文件看不到一次提交里多个文件的关联变更而且 IDE 插件开发成本高要适配不同编辑器。第三种是服务端平台提交 PR 后自动触发审查。功能全但部署重依赖多小团队用起来性价比不高。我最终选的是 CLI 加 LLM Agent 的组合。CLI 的好处是轻量、可组合、易自动化。它可以手动跑也可以挂到 Git 钩子里自动跑还能塞进 CI 流水线。LLM Agent 的好处是它能理解代码语义给出有推理过程的意见而不是死板的规则匹配。这里要澄清几个容易混淆的概念因为热词里很多人问“agent 和 llm 和 ai 模型有什么区别”。简单说LLM 是底层的大语言模型比如 DeepSeek、GPT 系列、Claude 系列它们本质上是“文本进、文本出”的预测引擎。Agent 是在 LLM 之上加了一层“感知环境、调用工具、做决策”的逻辑。比如一个代码审查 Agent它会先调用 Git 命令拿到 diff再把 diff 和审查规则拼成提示词送给 LLM拿到结果后可能还要调用文件写入工具生成报告。LLM 是大脑Agent 是把这个大脑用起来的手脚和流程。Embedding 则是另一回事它是把文本转成向量表示常用于相似度检索代码审查里如果要做“历史相似问题匹配”会用到但不是核心必需。所以 open-code-review 的定位就是一个 Agent它封装了 Git 操作、提示词构造、模型调用和报告生成这一整套流程用户只需要敲一条命令。2.2 技术栈选择与理由技术栈方面我选了 Python 作为主语言。原因有几个一是 Python 调用各家 LLM 的 SDK 最方便生态最全二是 Git 操作有 GitPython 这样的成熟库省去自己解析命令输出三是团队里做数据、做脚本的同事大多会 Python维护门槛低。Git 交互这块我没有完全依赖 GitPython而是关键地方直接调 git 命令。为什么因为 GitPython 在某些边缘场景下行为和原生 git 不一致比如处理重命名、二进制文件、子模块的时候。直接调 git 命令虽然土但行为可预测出问题也好排查。热词里有人搜“git -c diff.mnemonicprefixfalse -c core.quotepathfalse --no-optional-locks”这其实是很多工具调用 git diff 时的标准做法目的是让输出稳定、不受用户本地配置影响。我在项目里也用了类似的参数保证 diff 格式一致。模型接入层我做了抽象支持多种后端。默认走 OpenAI 兼容接口这样 DeepSeek、通义、以及各种自部署的模型都能接。配置里改个 base_url 和 model 名字就行。这样做的好处是不绑定单一供应商哪家便宜用哪家哪家效果好换哪家。报告输出支持三种格式终端彩色输出、Markdown 文件、JSON。终端输出适合手动跑的时候看Markdown 适合存档或者贴到协作工具里JSON 适合被其他程序消费比如接到自动化流程里。2.3 审查策略设计怎么让模型给出有用的意见这是整个项目最核心也最难的部分。直接把 diff 丢给模型说“帮我审查代码”得到的结果往往很泛比如“建议增加注释”“注意错误处理”没什么价值。我的做法是分层构造提示词。第一层是角色设定告诉模型它是一个资深工程师关注哪些方面。第二层是审查维度清单把审查拆成几个明确的维度正确性、安全性、性能、可维护性、风格一致性。第三层是项目上下文包括这次变更涉及的文件路径、相关的项目规范摘要。第四层才是具体的 diff 内容。还有一个关键设计是“分块审查”。一次提交可能改了十几个文件几千行 diff直接全塞进去会超出上下文窗口而且模型注意力会被稀释。我的做法是按文件切分每个文件单独审查最后汇总。对于特别大的文件再按 hunk 切分。这样每个审查单元都足够聚焦模型给出的意见也更具体。另外我加了一个“严重程度”分级。模型返回的每条意见都要标注是 blocker、major、minor 还是 info。这样审查者可以先看 blocker 和 majorminor 和 info 可以批量处理。分级的标准我也写进了提示词避免模型乱标。3. 核心细节解析与实操要点3.1 环境准备Git 和 Python 的安装配置先把基础环境弄好。Git 是必须的因为整个工具依赖它读取变更。Windows 用户去官网下载安装包一路下一步就行注意安装时勾选“Add Git to PATH”否则命令行里调不到 git。安装完打开终端敲git --version能输出版本号就说明装好了。Linux 用户用包管理器装Ubuntu 系是sudo apt install gitCentOS 系是sudo yum install git。macOS 用户如果装了 Homebrew直接brew install git没装的话装完 Xcode Command Line Tools 也会自带。装完 Git 要做基本配置至少配个用户名和邮箱否则提交会报错git config --global user.name 你的名字 git config --global user.email 你的邮箱如果你用 Gitee 或者自建的 Git 服务还需要配 SSH 密钥。生成密钥的命令是ssh-keygen -t rsa -b 4096 -C 你的邮箱一路回车然后把~/.ssh/id_rsa.pub的内容复制到平台的密钥设置里。这一步热词里很多人搜“git 配置 gitee 密钥”其实就是这个流程。Python 方面我建议用 3.10 以上版本因为用了一些新的类型语法。装 Python 最简单的方式是用官方安装包或者 conda。装完确认python --version和pip --version都能正常输出。提示Windows 用户如果同时装了多个 Python 版本注意 pip 装包和 python 运行要指向同一个解释器否则会出现“装了包但 import 不到”的问题。用python -m pip install xxx这种写法最稳妥。3.2 项目初始化与依赖安装把项目代码拉下来之后先建虚拟环境。这一步别省否则依赖冲突会让你怀疑人生。python -m venv venv # Windows venv\Scripts\activate # Linux/macOS source venv/bin/activate激活后命令行前面会有(venv)标识。然后装依赖pip install -r requirements.txt核心依赖就几个openai用于调模型gitpython作为 Git 操作的补充rich用于终端彩色输出click用于构建命令行接口pyyaml用于读配置文件。依赖不多装起来很快。接下来是配置。项目根目录有个config.example.yaml复制成config.yaml然后改。配置项包括模型后端地址、API 密钥、模型名称、审查维度开关、严重程度阈值等。llm: base_url: https://api.deepseek.com/v1 api_key: 你的密钥 model: deepseek-chat temperature: 0.2 max_tokens: 4096 review: dimensions: - correctness - security - performance - maintainability min_severity: minor max_diff_lines: 500这里temperature设成 0.2 是有讲究的。代码审查需要稳定、可复现的结果温度太高模型会发挥创意同一段代码两次审查给出不同意见没法用。0.2 是个平衡点既保留一点灵活性又足够稳定。max_diff_lines是单个审查单元的最大行数超过就切分。这个值我调过几轮500 行是个比较合适的数。太小会导致切分过碎模型看不到完整上下文太大又会让模型注意力分散。3.3 提示词工程决定审查质量的关键提示词写得好不好直接决定输出质量。我前后改了十几版分享几个关键心得。第一角色设定要具体。不要只说“你是一个程序员”要说“你是一个有十年经验的资深后端工程师擅长发现并发问题、边界条件错误和安全隐患”。越具体模型的输出越对路。第二审查维度要给出判断标准。比如“正确性”这一项我明确写了检查边界条件、空值处理、异常路径、类型匹配、并发安全。模型有了清单就不会泛泛而谈。第三要求模型给出具体的行号和修改建议。只说“这里有问题”没用要说“第 42 行当 list 为空时list[0]会抛异常建议先判断if list:”。第四明确输出格式。我要求模型返回 JSON 数组每条包含file、line、severity、dimension、message、suggestion六个字段。这样程序好解析也方便后续处理。第五加 few-shot 示例。在提示词里放一两个“输入 diff、输出审查意见”的样例模型会模仿这个风格。这个技巧效果非常明显强烈建议用。注意提示词里不要放真实的敏感代码作为示例用构造的假代码就行。示例的作用是示范格式和风格不是提供业务逻辑。3.4 严重程度分级与阈值控制分级标准我定得比较明确避免模型乱标级别含义处理建议blocker会导致功能错误、数据损坏或安全漏洞必须修改后才能合并major明显的逻辑缺陷或性能问题建议本次修改minor可维护性、可读性问题可后续处理info风格建议、优化提示参考即可配置里的min_severity控制输出下限。如果设成 major那 minor 和 info 就不输出适合赶进度的时候用。设成 minor 是默认值日常用比较合适。这里有个经验模型有时候会把风格问题标成 major把真正的逻辑问题标成 minor。我在提示词里加了纠偏说明明确“只有当问题会导致运行时错误或安全风险时才能标 blocker 或 major”。加了这句之后分级准确率明显提升。4. 实操过程与核心环节实现4.1 从 Git 仓库读取变更的完整流程工具的第一步是拿到要审查的代码变更。这里要处理几种场景审查未提交的改动、审查最近一次提交、审查某个分支相对于另一个分支的差异、审查指定的 commit 范围。我用 git 命令来实现核心是git diff的不同参数组合# 未提交的改动工作区 vs 暂存区 git diff # 已暂存的改动 git diff --cached # 最近一次提交 git diff HEAD~1 HEAD # 分支对比 git diff main..feature-branch # 指定 commit 范围 git diff abc123..def456为了让输出稳定我统一加了这些参数git -c diff.mnemonicprefixfalse -c core.quotepathfalse --no-optional-locks diff 参数diff.mnemonicprefixfalse保证 diff 里的路径前缀是标准的a/和b/不受用户配置影响。core.quotepathfalse让中文路径正常显示不会被转义成八进制。--no-optional-locks避免在只读操作时去抢索引锁防止和其他 Git 操作冲突。这几个参数是很多工具类项目的标配值得记住。拿到 diff 文本后要解析成结构化的数据。我用一个简单的状态机来解析遇到diff --git开新文件遇到开新 hunk遇到是新增行遇到-是删除行。解析出来的结构包含文件路径、变更类型新增/修改/删除/重命名、每个 hunk 的起止行号和具体内容。这里有个坑二进制文件的 diff 是没法审查的要跳过。判断方法是看 diff 里有没有Binary files字样。还有重命名的情况git diff默认可能显示成删除加新增要加-M参数让它识别重命名否则会误报大量“文件被删除”。4.2 调用 LLM 进行审查的实现细节拿到结构化的 diff 后就要构造请求发给模型。我用的是 OpenAI 兼容的 chat completions 接口因为兼容性最好。请求构造的核心逻辑是这样的先拼系统提示词包含角色设定、审查维度、输出格式要求、few-shot 示例。然后把当前文件的 diff 作为用户消息发过去。注意每个文件单独发一次请求而不是所有文件打包发一次。为什么要分开一是上下文窗口限制二是聚焦度。实测下来单文件审查的意见质量明显高于多文件混合审查。虽然请求次数多了但总 token 数其实差不多因为提示词部分可以复用如果用支持 prompt caching 的接口还能省钱。代码大致长这样def review_file(client, config, file_diff): messages [ {role: system, content: build_system_prompt(config)}, {role: user, content: build_user_prompt(file_diff)} ] response client.chat.completions.create( modelconfig.model, messagesmessages, temperatureconfig.temperature, max_tokensconfig.max_tokens, response_format{type: json_object} ) return parse_response(response.choices[0].message.content)response_format设成 json_object 能强制模型输出合法 JSON省去解析容错。不过要注意不是所有模型后端都支持这个参数DeepSeek 是支持的一些自部署的模型可能不支持那就得在提示词里强调输出 JSON 并自己做容错解析。错误处理要做足。网络超时、限流、返回格式错误都要重试。我设了三次重试每次间隔指数退避。如果三次都失败就记录到日志并跳过这个文件不能让一个文件失败导致整个审查中断。4.3 报告生成与结果呈现审查完所有文件后要把结果汇总成报告。我做了三种输出格式。终端输出用 rich 库做彩色渲染blocker 标红major 标黄minor 标蓝info 标灰。每个问题显示文件路径、行号、严重程度、问题描述和修改建议。最后给一个统计摘要比如“本次审查发现 2 个 blocker、5 个 major、8 个 minor”。Markdown 输出适合存档和分享。格式是每个文件一个二级标题下面列出该文件的所有问题用表格呈现。开头放一个总览表格列出各严重程度的数量。JSON 输出是给程序用的结构清晰包含所有原始数据方便接入其他系统。这里分享一个实用技巧报告里加上“本次审查的 diff 范围”和“使用的模型名称”。这样过一段时间回头看能知道这份报告是基于哪个版本、哪个模型生成的便于追溯。4.4 接入 Git 钩子实现自动审查手动跑命令适合偶尔用但要让代码审查真正落地最好是自动化。我用 Git 的 pre-push 钩子来实现每次 push 之前自动审查本次要推送的提交有问题就提示。在.git/hooks/目录下建一个pre-push文件内容大致是#!/bin/bash open-code-review review --range origin/main..HEAD --min-severity major if [ $? -ne 0 ]; then echo 发现严重问题请先处理后再推送 exit 1 fi注意钩子脚本要有执行权限chmod x .git/hooks/pre-push。另外钩子是本地的不会同步到仓库团队每个人都要自己配。如果想统一管理可以把钩子脚本放在项目里用一个安装脚本分发。提示pre-push 钩子里如果审查时间太长会让人烦躁。建议只审查 major 以上的问题并且设置超时。我实测下来审查一次中等规模的提交大概 20 到 40 秒可以接受。如果超过一分钟就要考虑优化或者改成异步通知。5. 常见问题与排查技巧实录5.1 模型调用相关的典型问题问题一API 返回 401 或 403。这是密钥问题。先检查config.yaml里的 api_key 有没有填错有没有多余的空格。如果密钥是从环境变量读的确认环境变量在当前终端会话里生效了。Windows 下用set命令设的环境变量只在当前窗口有效新开窗口就没了建议用系统设置里的环境变量或者写进配置文件。问题二返回内容不是合法 JSON。如果模型后端不支持response_format就得自己容错。我的做法是先尝试直接解析失败后用正则从文本里提取 JSON 块找第一个{到最后一个}再失败就记录原始输出并跳过。实测下来加了 few-shot 示例后格式错误的概率很低。问题三审查结果为空。可能是 diff 为空比如没有未提交的改动。也可能是min_severity设得太高把问题都过滤掉了。排查方法是先用--min-severity info跑一次看有没有输出。如果还是没有检查 diff 解析环节是不是出了问题。问题四中文注释乱码。这是编码问题。Git diff 输出默认用系统编码Windows 下可能是 GBK。解决办法是在调用 git 时指定-c core.quotepathfalse并且在 Python 里用encodingutf-8读取输出。如果还是乱码检查终端本身的编码设置。5.2 Git 操作相关的疑难杂症问题一diff 里出现大量无关变更。常见原因是行尾符不一致。Windows 用 CRLFLinux 用 LF如果仓库里混用diff 会显示整个文件都改了。解决办法是在仓库根目录加.gitattributes文件统一行尾符* textauto eollf问题二重命名文件被识别成删除加新增。前面提过加-M参数。如果重命名同时改了内容可能还需要调低相似度阈值用-M50%这样的写法。问题三子模块变更没法审查。子模块在 diff 里只显示一个 commit hash 的变化没有具体内容。这种情况直接跳过在报告里标注“子模块变更需手动审查”。问题四大文件导致超时。有些自动生成的文件比如 lock 文件、编译产物动辄几千行审查它们没意义还费钱。我在配置里加了忽略规则支持 glob 模式比如*.lock、dist/**、*.min.js。这些文件直接跳过。5.3 审查质量相关的调优经验经验一模型不是越大越好。我对比过几个模型发现对于代码审查这种任务中等规模的模型往往性价比更高。大模型确实更强但贵好几倍而且在这个任务上差距没有想象中大。建议先用便宜模型跑遇到具体问题再针对性换。经验二提示词里的示例要覆盖典型场景。我放了三个示例一个空指针问题、一个 SQL 注入风险、一个命名不规范。覆盖了从严重到轻微的不同级别。模型看了之后输出风格和分级都稳定多了。经验三定期回顾误报和漏报。我每周会抽时间看看这周审查报告里哪些是误报、哪些漏了。误报多说明提示词太严格漏报多说明维度覆盖不够。根据反馈迭代提示词几轮下来质量提升很明显。经验四不要完全信任模型的分级。模型对严重程度的判断有时会偏。我的做法是在报告里保留模型的分级但同时显示问题类型让审查者自己判断。比如一个“未处理的异常”被标成 minor审查者看到类型就知道要重视。5.4 常见问题速查表现象可能原因排查方向命令报找不到 gitGit 未安装或未加入 PATH检查git --version模型调用超时网络问题或后端限流检查网络降低并发加重试输出乱码编码不一致统一用 UTF-8加 quotepath 参数审查结果为空diff 为空或阈值过高降低 min-severity检查 diff报告里文件路径不对diff 前缀解析错误检查 mnemonicprefix 参数重复审查同一文件缓存未生效检查缓存 key 的构造逻辑钩子不触发没有执行权限chmod x钩子文件大文件卡住未配置忽略规则加 ignore 配置跳过生成文件6. 一些关于 LLM Agent 落地的个人体会做这个项目的过程中我对 LLM Agent 这个东西有了更实际的认识。热词里很多人问“agent 和 llm 有什么区别”我的理解是LLM 是一个能力很强的“大脑”但它不知道怎么用工具、不知道流程、不知道什么时候该停下来。Agent 就是把这些补上。在 open-code-review 里LLM 负责“看懂代码并给出意见”Agent 负责“拿到 diff、切分、构造提示词、调用模型、解析结果、生成报告”这一整套流程。没有 Agent 这层封装用户就得自己手动做这些事那就谈不上工具了。另一个体会是Agent 的可靠性不取决于模型多强而取决于工程细节做得多扎实。diff 解析的边界情况、错误重试、编码处理、超时控制这些看起来不起眼的地方恰恰决定了工具能不能日常用。我见过不少 demo 很惊艳但没法落地的 Agent 项目问题都出在这些工程细节上。还有一点是关于成本控制。LLM 调用是要花钱的如果不加控制一个活跃仓库一天跑下来费用可能超出预期。我的做法是默认只审查 major 以上、加文件忽略规则、对相同 diff 做缓存用 diff 的哈希做 key、限制单次审查的最大文件数。这几招下来成本能压到很低的水平。最后分享一个我觉得很有用的扩展方向把审查结果和历史记录关联起来。比如某个文件反复出现同类问题可以在报告里提示“该文件近一个月已出现 5 次空指针问题建议重点重构”。这需要把每次审查结果存下来做一个简单的统计分析。我目前用 SQLite 存查询很方便。这个功能对团队改进代码质量很有帮助因为它能暴露系统性的问题而不只是单次的问题。
返回列表