十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于LLM Agent的自动化代码评审CLI工具实战

基于LLM Agent的自动化代码评审CLI工具实战 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 ToolsGit 已经自带了。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.pyAgent 循环和工具调用。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_steps10): 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推送被误拦误报导致退出码非零先关闭拦截调优后再开启终端输出乱码编码问题设置 PYTHONIOENCODINGutf-8模型不调用工具提示词没说明工具用途在系统提示里明确列出可用工具5.6 几个我踩过的坑第一个坑是 Git 的core.quotepath设置。默认情况下Git 会把非 ASCII 文件名转义成八进制导致 diff 里的文件名变成一堆乱码。解决办法是git config --global core.quotepath false这一条我建议所有人都设上不管用不用这个工具。第二个坑是 Windows 下的路径分隔符。Windows 用反斜杠但 Git 内部用正斜杠。我在处理文件路径时统一转成正斜杠避免在 Windows 上跑不通。第三个坑是模型对 diff 格式的误解。有些模型会把 diff 里的-行当成“删除的代码”然后报告“你删除了重要逻辑”。其实那只是上下文行。解决办法是在提示词里明确说明 diff 格式的含义。第四个坑是并发问题。我一开始想并行处理多个文件结果发现模型接口有速率限制并发请求会被拒。后来改成串行虽然慢一点但稳定。6. 一些关于扩展和长期维护的想法这套东西我用了大半年中间迭代了十几个版本。现在回头看最值得投入的地方不是模型本身而是上下文获取的质量。模型再强你给它的信息不对它也分析不出好东西。所以如果你要自己搭一套我建议把七成精力花在“怎么把正确的代码片段喂给模型”上剩下三成再考虑模型选型和提示词优化。另外不要追求一步到位。我见过有人一上来就想做全自动评审加自动修复结果做了两个月还没上线。我的做法是先做最小可用版本只支持工作区改动、只输出文本、不集成任何钩子。跑通之后再逐步加功能。这样每一步都有正反馈也容易发现问题。关于模型的选择我的态度是保持可替换。今天这个模型好用明天可能就有更便宜更好的出来。把接口抽象好切换成本降到最低这样你永远不会被绑死。最后说一个我最近在试的方向把评审结果按时间积累起来形成一个“团队常见问题库”。比如某个模块反复出现空指针问题就可以在提示词里针对这个模块加一条特别提醒。这个思路还在验证中但初步效果不错误报率又降了一些。如果你也在做类似的事情欢迎交流。
返回列表