基于LLM Agent的自动化代码评审CLI工具实战
2026/9/20 22:33:40 网站建设 项目流程

1. 为什么我要自己搭一套 open-code-review

团队里代码评审这件事,说多了都是泪。我们组一共八个人,后端五个、前端两个、还有一个兼职运维,每周至少三十个合并请求。刚开始大家还认真看,后来就变成“点个赞就过”,再后来连点赞都省了,直接一句“LGTM”甩过去。不是不想好好审,是真的审不过来——一个人一天写五百行代码,另一个人要花四十分钟去理解上下文,这买卖怎么算都亏。

我最早接触open-code-review这个概念,是在翻一些开源项目的贡献指南时注意到的。它本质上是一套把代码评审流程自动化的思路:用CLI工具把Git仓库里的变更抓出来,交给LLM Agent做第一轮分析,再把结果整理成人类能快速消化的形式。注意,它不是要取代人,而是把“找明显问题”这种体力活接过去,让人专注在架构、业务逻辑和边界条件上。

这套东西适合谁?我觉得三类人最需要:一是小团队里没有专职代码评审角色的,二是开源项目维护者面对大量外部提交的,三是自己写个人项目但想保持代码质量的。哪怕你只有一个人写代码,让一个 Agent 帮你过一遍,也比自己写完直接提交强得多。

我前后折腾了大概三周,踩了不少坑,也总结出一套相对稳定的方案。下面我把整个思路、实现细节和踩坑记录都摊开讲,你照着抄作业就行。

2. 整体设计与技术选型思路

2.1 核心需求拆解:到底要解决什么问题

在动手之前,我先把需求列清楚,不然很容易做成一个四不像的东西。我的核心诉求有这么几条:

  • 能自动获取 Git 变更:不管是工作区的未提交改动,还是两个分支之间的差异,都要能拿到。
  • 能调用 LLM 做分析:把 diff 内容喂给模型,让它找出潜在问题。
  • 结果要可读:不能甩一堆 JSON 给我,得是人能看懂的格式。
  • 能集成到现有流程:最好是一条命令搞定,不要让我开一堆窗口。
  • 成本可控:不能每次评审都烧掉几十块钱的 token。

这五条里,第四条和第五条是最容易被忽略的。很多人一上来就追求“全自动”,结果做出来的东西要么慢得要死,要么贵得离谱,最后没人用。

2.2 为什么选 CLI 而不是 Web 服务

我一开始也想过做个 Web 界面,点一下按钮就出评审报告。但后来放弃了,原因很简单:代码评审发生在开发者的终端里。你写完代码,git add之后顺手敲一条命令,评审结果直接打在屏幕上,这个体验是最顺的。如果还要切到浏览器、登录、粘贴 diff,那还不如不审。

CLI 的另一个好处是容易组合。你可以把它塞进 Git 的 pre-push 钩子,也可以放在 CI 里跑,甚至可以配合git worktree在多分支场景下使用。Web 服务做不到这么灵活。

提示:如果你团队里有人对命令行不熟,可以先做一个最简单的版本,只支持open-code-review这一条命令,不带任何参数,默认评审当前工作区改动。降低使用门槛比堆功能重要得多。

2.3 LLM Agent 和普通 LLM 调用的区别

这里要澄清一个概念。很多人把“调 LLM”和“用 Agent”混为一谈,其实差别很大。

普通 LLM 调用是一问一答:我把 diff 贴进去,模型给我一段分析,结束。它不会自己去读文件、不会去查历史提交、不会去跑测试。

Agent 则不一样。Agent 是一个带工具调用能力的循环:它可以自己决定“我需要看一下这个函数的定义”,然后调用读文件的工具;发现某个变量来源不明,它可以去git log里查这个文件的历史。它把一个大任务拆成若干小步骤,逐步完成。

我最终选的是Agent 模式,因为代码评审天然需要上下文。只看 diff 而不看周边代码,模型很容易给出误报。比如你改了一个函数的返回值类型,diff 里只显示这一行,但 Agent 可以去读调用方,判断这个改动会不会导致类型不匹配。

2.4 工具链选型:Git、CLI 框架与模型接入

具体到实现,我的选型是这样的:

环节选型理由
变更获取Git 原生命令稳定、无需额外依赖
CLI 框架Python + argparse轻量、跨平台、团队都会
Agent 编排自研轻量循环避免引入过重框架
模型接入兼容 OpenAI 接口的任意模型方便切换,不被单一供应商绑定
输出格式Markdown + 终端着色人机皆宜

这里重点说模型接入。我坚持用兼容 OpenAI 接口的方式,是因为这样可以在不同模型之间自由切换。今天用这个,明天觉得贵了换那个,代码一行不用改,只改环境变量。这一点在实际使用中太重要了,因为模型的价格和能力变化太快,绑定死一家是自找麻烦。

至于具体用哪个模型,我的经验是:评审这种任务不需要最强的模型。它需要的是稳定的指令遵循能力和足够大的上下文窗口。我用过几个不同档位的模型,发现中等档位的模型在“找明显 bug”这件事上已经够用,只有在涉及复杂业务逻辑时才需要上更强的。所以我的策略是默认用中等档位,遇到大 diff 再手动切换。

3. 核心细节解析与实操要点

3.1 Git 变更获取:三种场景要分清

获取变更看起来简单,其实有三种场景,处理方式完全不同。

第一种是工作区未提交的改动。这时候用git diff就能拿到,但要注意它默认不包含已git add的内容。完整写法是:

git diff HEAD

这条命令会把工作区和暂存区的改动一起显示出来,对比的是 HEAD 提交。我一开始用git diff,结果发现git add之后的改动消失了,排查了半天才反应过来。

第二种是两个分支之间的差异。比如你在 feature 分支上,想评审相对于 main 的所有改动:

git diff main...HEAD

注意这里是三个点,不是两个点。三个点表示“从共同祖先到 HEAD 的改动”,两个点表示“两个分支当前状态的差异”。在评审场景下,三个点才是你想要的,因为它排除了 main 分支上别人提交的内容。

第三种是单个提交的改动。用git show <commit-hash>就行,但要注意它默认会显示提交信息,需要加--format=去掉。

注意:如果你的项目里有大文件或者二进制文件,diff 会非常长。建议在获取变更时加一个过滤,把二进制文件排除掉,否则 token 消耗会爆炸。

3.2 Diff 预处理:别把原始 diff 直接喂给模型

这是我最想强调的一点。原始 diff 直接喂给模型,效果很差。原因有三个:

第一,diff 里有大量噪音,比如行号、+/-符号、上下文行,模型需要花精力去解析这些格式,而不是专注在代码逻辑上。

第二,diff 是碎片化的。一个函数被改了五处,diff 里就是五段不连续的片段,模型很难建立整体认知。

第三,大 diff 会超出上下文窗口。一个几百行的改动,加上周边上下文,很容易就上万 token。

我的做法是做一层预处理:

  • 把 diff 按文件分组,每个文件单独处理。
  • 对每个文件,提取出改动的函数或类,而不是逐行分析。
  • 如果改动太大,先做一次摘要,再分块分析。

具体实现上,我用了一个简单的启发式方法:扫描 diff 中的@@标记,找到每个 hunk 的起始行号,然后去原文件里把包含这个行号的函数完整读出来。这样模型看到的是“完整的函数 + 改动标记”,而不是“孤立的几行”。

3.3 Agent 的工具设计:给模型配哪几把刀

Agent 的能力取决于你给它什么工具。我最终保留了四个工具,不多不少:

  • read_file:读取指定文件的完整内容。当模型需要看某个函数的定义时用。
  • search_code:在仓库里搜索关键词。当模型想知道某个变量在哪里被使用时用。
  • git_log:查看某个文件的历史提交。当模型想了解某段代码的演变时用。
  • run_lint:对指定文件跑静态检查。当模型想验证自己的判断时用。

这四个工具覆盖了代码评审中最常见的需求。我没有加“运行测试”的工具,因为测试环境往往很复杂,让 Agent 去跑测试容易出问题,而且耗时太长。

工具的描述(description)写得越清楚,模型用得越准。比如read_file的描述我写的是“读取指定文件的完整内容,参数是相对于仓库根目录的路径。如果文件不存在会返回错误信息。”这样模型就知道路径怎么写、出错会怎样。

3.4 提示词设计:让模型说人话

提示词这块我改了七八版,最后稳定下来的结构是这样的:

你是一个资深代码评审者。请分析以下代码改动,找出: 1. 潜在的 bug(逻辑错误、边界条件、空指针等) 2. 安全问题(注入、越权、敏感信息泄露等) 3. 性能问题(不必要的循环、重复计算等) 4. 可读性问题(命名、注释、结构等) 对于每个问题,请给出: - 文件路径和行号 - 问题描述 - 严重程度(高/中/低) - 修改建议 如果某个方面没有问题,不要强行找问题。宁可少报,不要误报。

最后那句“宁可少报,不要误报”非常关键。不加这句,模型会为了凑数硬找问题,报一堆无关痛痒的命名建议,把真正重要的 bug 淹没了。

另外,我要求模型输出 Markdown 格式,这样在终端里可以直接渲染,在 CI 里也可以直接贴到评论里。

4. 实操过程与核心环节实现

4.1 环境准备:从零开始搭起来

假设你从一台干净的机器开始,下面是完整步骤。

第一步,装 Git。Windows 用户去官网下载安装包,一路下一步就行。安装完在终端里敲git --version,能显示版本号就说明成功了。Mac 用户如果装了 Xcode Command Line Tools,Git 已经自带了。Linux 用户用包管理器装,比如apt install git

装完之后要配置用户名和邮箱,不然提交会报错:

git config --global user.name "你的名字" git config --global user.email "你的邮箱"

第二步,装 Python。我用的 Python 3.10,理论上 3.8 以上都行。装完之后建议建一个虚拟环境,避免污染系统环境:

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate

第三步,装依赖。我的项目只依赖两个库:一个是 HTTP 请求库,一个是终端着色库。

pip install requests colorama

就这两个,没有别的。我刻意保持依赖最少,因为依赖越多,出问题的概率越大。

4.2 核心代码结构:五个模块各司其职

整个项目我拆成五个文件,每个文件职责单一:

  • main.py:入口,解析命令行参数。
  • git_utils.py:封装所有 Git 操作。
  • agent.py:Agent 循环和工具调用。
  • llm_client.py:模型接口封装。
  • formatter.py:输出格式化。

这样拆的好处是,每个模块都可以单独测试。比如我想验证 Git 变更获取对不对,直接跑git_utils.py就行,不用启动整个流程。

git_utils.py里最核心的函数是get_diff,它根据传入的模式(工作区/分支/提交)返回对应的 diff 字符串。我在这里加了一个max_lines参数,超过这个行数就截断,并提示用户“改动过大,建议分批评审”。

4.3 Agent 循环实现:一个 while 循环搞定

Agent 的核心逻辑其实就是一个 while 循环:

def run_agent(diff, max_steps=10): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"请评审以下改动:\n\n{diff}"} ] for step in range(max_steps): response = call_llm(messages) if response.has_tool_call: tool_result = execute_tool(response.tool_call) messages.append(response.message) messages.append({"role": "tool", "content": tool_result}) else: return response.content return "达到最大步数限制,评审未完成"

max_steps设成 10 是我的经验值。设太小,Agent 还没看完就停了;设太大,万一模型陷入循环会浪费 token。10 步足够处理大多数中等规模的改动。

这里有个细节:每次工具调用后,要把工具结果追加到 messages 里,再进入下一轮。这样模型才能看到自己上一步做了什么。

4.4 输出格式化:让结果一眼能看懂

模型返回的是 Markdown 文本,我做了两层处理。

第一层是终端着色。用 colorama 把“高严重程度”标红,“中”标黄,“低”标灰。这样扫一眼就知道哪些要优先处理。

第二层是分组。按文件路径把问题分组,同一个文件的问题放在一起。这样你打开文件对照着改就行,不用来回跳。

输出大概长这样:

=== 评审结果 === 文件:src/user_service.py [高] 第 45 行:用户输入未做校验,可能导致注入 建议:在查询前对 username 做白名单过滤 [中] 第 78 行:循环内重复查询数据库 建议:把查询提到循环外,用批量查询替代 文件:src/utils.py [低] 第 12 行:变量名 data 含义不明确 建议:改为 user_list 或 order_data 共发现 3 个问题(高 1 / 中 1 / 低 1)

这个格式我用了几个月,团队里没人抱怨看不懂。

4.5 集成到 Git 钩子:让评审自动发生

手动敲命令终究会忘。我的做法是把它挂到pre-push钩子上,每次推送前自动跑一遍。

.git/hooks/pre-push里写:

#!/bin/bash python /path/to/open-code-review/main.py --mode branch --base main if [ $? -ne 0 ]; then echo "评审发现问题,请确认后再推送" exit 1 fi

注意这里我让脚本在发现问题时返回非零退出码,这样推送会被阻止。但我不建议一上来就这么做,因为误报会让人很烦。可以先跑一段时间,等准确率稳定了再开启拦截。

提示:Git 钩子默认不会被提交到仓库里,所以团队每个人都要自己配一遍。如果想让全组统一,可以把钩子脚本放在仓库里,然后写个安装脚本让大家跑一下。

5. 常见问题与排查技巧实录

5.1 模型返回格式不对怎么办

这是最常见的问题。模型有时候会忘记输出 Markdown,或者把严重程度写成中文“高”而不是我要求的格式。

我的解决办法是在提示词里给一个输出示例,明确告诉它“必须严格按照以下格式输出”。加了示例之后,格式错误率从大概三成降到了一成以下。

如果还是出错,我会在代码里加一层解析容错:用正则去匹配“文件路径”“行号”“严重程度”这些关键词,而不是依赖严格的格式。这样即使模型格式有点偏差,也能提取出关键信息。

5.2 Token 消耗太快怎么控制

我统计过,一个中等规模的改动(大概 200 行 diff),如果直接把原始 diff 喂进去,加上 Agent 的几轮工具调用,大概消耗 8000 到 15000 token。如果一天评审二十次,成本不低。

控制方法有三个:

  • 预处理压缩 diff:只保留改动的函数,去掉无关上下文。这一招能省一半以上。
  • 限制 Agent 步数max_steps设成 10,避免无限循环。
  • 缓存文件内容:同一个文件在一次评审中被多次读取时,用缓存避免重复传输。

我实测下来,这三招组合使用,token 消耗能降到原来的三分之一左右。

5.3 Agent 陷入循环怎么破

Agent 偶尔会陷入“读文件→发现问题→再读同一个文件→再发现同样问题”的死循环。我遇到过最夸张的一次,它连续读了同一个文件六遍。

解决办法是加一个已读文件记录。每次工具调用前检查一下,如果这个文件最近已经读过,就在工具结果里提示“该文件内容未变化,请基于已有信息继续分析”。这样模型就会转向其他操作。

另外,max_steps本身就是一道保险。到了步数上限强制退出,虽然结果可能不完整,但至少不会一直烧钱。

5.4 误报太多怎么调

误报是代码评审工具的头号杀手。报十个问题,八个是无关紧要的命名建议,用户很快就会失去信任。

我的调优过程是这样的:先跑一周,把所有误报收集起来,分类统计。我发现误报主要集中在三类:命名风格、注释缺失、以及模型对业务逻辑的误解。

针对前两类,我在提示词里明确说“不要报告命名和注释问题,除非它们会导致实际错误”。针对第三类,我加强了 Agent 的上下文获取能力,让它多读周边代码再下结论。

调整之后,误报率从大概四成降到了一成五左右。这个水平我觉得可以接受,因为剩下的一成五里,有些其实是模型看到了我没注意到的边界情况。

5.5 常见问题速查表

问题现象可能原因解决办法
模型返回空结果diff 太长被截断检查 max_lines 设置,分批处理
工具调用报错文件路径不对确认路径是相对仓库根目录
评审结果重复Agent 陷入循环加已读文件记录,降低 max_steps
推送被误拦误报导致退出码非零先关闭拦截,调优后再开启
终端输出乱码编码问题设置 PYTHONIOENCODING=utf-8
模型不调用工具提示词没说明工具用途在系统提示里明确列出可用工具

5.6 几个我踩过的坑

第一个坑是 Git 的core.quotepath设置。默认情况下,Git 会把非 ASCII 文件名转义成八进制,导致 diff 里的文件名变成一堆乱码。解决办法是:

git config --global core.quotepath false

这一条我建议所有人都设上,不管用不用这个工具。

第二个坑是 Windows 下的路径分隔符。Windows 用反斜杠,但 Git 内部用正斜杠。我在处理文件路径时统一转成正斜杠,避免在 Windows 上跑不通。

第三个坑是模型对 diff 格式的误解。有些模型会把 diff 里的-行当成“删除的代码”,然后报告“你删除了重要逻辑”。其实那只是上下文行。解决办法是在提示词里明确说明 diff 格式的含义。

第四个坑是并发问题。我一开始想并行处理多个文件,结果发现模型接口有速率限制,并发请求会被拒。后来改成串行,虽然慢一点,但稳定。

6. 一些关于扩展和长期维护的想法

这套东西我用了大半年,中间迭代了十几个版本。现在回头看,最值得投入的地方不是模型本身,而是上下文获取的质量。模型再强,你给它的信息不对,它也分析不出好东西。所以如果你要自己搭一套,我建议把七成精力花在“怎么把正确的代码片段喂给模型”上,剩下三成再考虑模型选型和提示词优化。

另外,不要追求一步到位。我见过有人一上来就想做全自动评审加自动修复,结果做了两个月还没上线。我的做法是先做最小可用版本:只支持工作区改动、只输出文本、不集成任何钩子。跑通之后再逐步加功能。这样每一步都有正反馈,也容易发现问题。

关于模型的选择,我的态度是保持可替换。今天这个模型好用,明天可能就有更便宜更好的出来。把接口抽象好,切换成本降到最低,这样你永远不会被绑死。

最后说一个我最近在试的方向:把评审结果按时间积累起来,形成一个“团队常见问题库”。比如某个模块反复出现空指针问题,就可以在提示词里针对这个模块加一条特别提醒。这个思路还在验证中,但初步效果不错,误报率又降了一些。如果你也在做类似的事情,欢迎交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询