AI 代码审查助手方案优化 — 基于 nanobot Agent 的新方案详解

代码审查是软件开发质量保障的核心环节,有效审查可提前发现 60-70% 的缺陷。传统人工审查成本高、标准不一;而直接调用 LLM API 的方案又存在 Key 管理混乱、Provider 耦合、无会话上下文、缺乏项目背景知识等问题。

接博主之前这篇《AI应用落地实战: 基于 GitLab Webhook + LLM 实现自动化 AI 代码审查助手》。

在此文中博主介绍分享一种 基于 GitLab Webhook + LLM 实现自动化 AI 代码审查助手方案。

本文提出一种基于 nanobot Agent 的 AI 代码审查方案,通过 nanobot 统一管理 LLM 调用,审查助手通过标准 WebSocket 协议与 Agent 通信,实现与具体 LLM 解耦、异步并行审查、项目级记忆积累和知识沉淀等能力。审查不再是「零上下文」的独立判断 ------ Agent 知道这个项目的架构设计、接口约定、命名规范等背景

优势

  • ✅ 审查不再是「零上下文」的独立判断 ------ Agent 知道这个项目的架构设计、接口约定、命名规范
  • ✅ 无需每次提交整个项目代码,避免安全风险 ------ 背景知识已预先沉淀为文档
  • ✅ 审查建议更深入,能发现与项目架构不匹配、与现有模块冲突等深层次问题
  • ✅ 文档可人工校验和修正,确保背景知识的准确性
  • ✅ 支持任意语言和技术栈(C/C++、Go、Rust、Python、Java 等),不受扫描器语言识别限制

项目开源地址https://gitcode.com/qq8864/gitcoder

一、传统方案的局限

直接调用 LLM API 做代码审查,面临这些痛点:

问题 表现
API Key 管理混乱 每个项目独立配置 Key,泄露风险高,多项目难以统一管控
Provider 耦合 切换模型(GPT-4 → DeepSeek)需改代码;不同 Provider SDK 差异大
重试/错误处理复杂 API 超时、限流(429)每个应用都要自己实现退避策略
无会话上下文 单次请求无状态,无法追问或跨文件关联分析
可观测性薄弱 API 调用是黑盒,Token 消耗难以追踪
缺失项目背景 每次只看到当前 diff,对项目架构、编码规范、历史一无所知

二、nanobot --- AI Agent 网关平台

nanobot 是一个开源的 AI Agent 网关与个人 AI 助理 平台,作为本方案的基础设施。

它有个啥好处呢?它本身就是个类似于cluadeCode或codex的智能体,只不过呢它更加的小巧且支持gateway网关模式,即可以以服务的形式常驻后台,通过WebSocket协议方便其它客户端接入与之交互。之前的方案中只是把单次的git提交的diff信息直接调用LLM的api接口让AI审查,缺乏大量的上下文信息,而使用nanobot 则可以对整个项目进行后台扫描掌握全局信息。

Nanobot又被称为轻量级OpenClaw,只有 4000 行 Python 代码且比 OpenClaw 小 99%。可以获得 工具调用、长期记忆、基于 cron 的唤醒,以及 多代理协调,感觉就像您机器上住着一个小团队。

传统单次会话方式每次审查都独立进行,缺少整个项目的全局视角,无法理解代码变更在项目中的位置和意义。

本方案的突破:通过 nanobot 的 Memory 能力和人工引导,为每个目标项目建立背景知识文档,在审查时自动注入为上下文。

交互时序图:
GitLab .env 审查助手 LLM nanobot Agent nanobot WebUI 开发者 GitLab .env 审查助手 LLM nanobot Agent nanobot WebUI 开发者 #mermaid-svg-ls7AaY9M1hUvZtwz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ls7AaY9M1hUvZtwz .error-icon{fill:#552222;}#mermaid-svg-ls7AaY9M1hUvZtwz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ls7AaY9M1hUvZtwz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ls7AaY9M1hUvZtwz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ls7AaY9M1hUvZtwz .marker.cross{stroke:#333333;}#mermaid-svg-ls7AaY9M1hUvZtwz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ls7AaY9M1hUvZtwz p{margin:0;}#mermaid-svg-ls7AaY9M1hUvZtwz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ls7AaY9M1hUvZtwz text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ls7AaY9M1hUvZtwz .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ls7AaY9M1hUvZtwz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ls7AaY9M1hUvZtwz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ls7AaY9M1hUvZtwz .sequenceNumber{fill:white;}#mermaid-svg-ls7AaY9M1hUvZtwz #sequencenumber{fill:#333;}#mermaid-svg-ls7AaY9M1hUvZtwz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ls7AaY9M1hUvZtwz .messageText{fill:#333;stroke:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ls7AaY9M1hUvZtwz .labelText,#mermaid-svg-ls7AaY9M1hUvZtwz .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .loopText,#mermaid-svg-ls7AaY9M1hUvZtwz .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ls7AaY9M1hUvZtwz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ls7AaY9M1hUvZtwz .noteText,#mermaid-svg-ls7AaY9M1hUvZtwz .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ls7AaY9M1hUvZtwz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ls7AaY9M1hUvZtwz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ls7AaY9M1hUvZtwz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ls7AaY9M1hUvZtwz .actorPopupMenu{position:absolute;}#mermaid-svg-ls7AaY9M1hUvZtwz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ls7AaY9M1hUvZtwz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ls7AaY9M1hUvZtwz .actor-man circle,#mermaid-svg-ls7AaY9M1hUvZtwz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ls7AaY9M1hUvZtwz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后续每次 MR 审查自动加载 "分析项目 C:\work\my-service 的架构" 发送分析请求 调用 LLM 分析项目 返回项目背景文档(markdown) 配置 PROJECT_CONTEXT_FILE 加载 project-context.md 审查请求 + 项目背景上下文 LLM 审查(带项目全局视野) 深度审查建议 发布评论到 MR

nanobot 是一款开源、超轻量级的个人 AI 智能体,真正实现为你所有。它保持智能体核心小巧且易于理解,同时为你提供实际长期运行工作所需的实用组件:WebUI、聊天渠道、工具、内存、MCP、模型路由、自动化和部署。

nanobot的github地址: https://github.com/HKUDS/nanobot

nanobot国内镜像地址https://gitcode.com/gh_mirrors/nanob/nanobot

2.1 核心能力

  • 统一 LLM 管理:支持 DeepSeek、OpenAI GPT、Claude、通义千问、Gemini 等,一个配置切换所有 Provider
  • WebSocket Agent 通道:外部应用通过 WebSocket 连接 Agent,以自然语言交互而非直接调用 LLM API
  • 技能系统(Skills):可扩展的技能体系(GitHub、定时任务、图片生成等)
  • 记忆与上下文:Agent 具备持久化记忆,可跨会话保持上下文

一句话: nanobot 让"把 AI Agent 嵌入你的应用"像连接 WebSocket 一样简单。

2.2 为什么选择 nanobot

• 持久化工作流:目标、记忆、工具和聊天上下文可在长时间运行的工作中保持。

• 原生聊天触达:支持 WebUI、API、Telegram、飞书、Slack、Discord、Teams 及电子邮件。

• 模型自由:兼容 OpenAI 的 API、本地大语言模型(LLMs)、图像生成、搜索及备用方案。

• 精简核心:内部代码可读性强,内置 MCP、内存、部署和自动化功能。

• 掌控你的技术栈:无需依赖大型平台,即可进行检查、自定义、自托管和扩展。

维度 Claude Code / Codex CLI nanobot(本方案)
运行模式 CLI 交互式------有人参与的编码/调试 Gateway 守护进程------后台静默运行,WebSocket API
触发方式 手动输入命令 / IDE 插件 Webhook 自动触发,完全无人值守
集成深度 面向终端用户 标准化 WebSocket 协议,任何语言可接入
多任务 一次一个会话 多通道并行处理多个 MR
可观测性 终端输出 内置 WebUI 仪表盘
项目知识 无持久化记忆 Dream 记忆系统,可预扫描全仓库

场景决定选型。 Claude Code 和 Codex 是出色的编码助手,但代码审查需要的是"收到 GitLab Webhook → 自动审查 → 评论到 MR"的闭环。nanobot 的守护进程模式天然契合这种无人值守的自动化集成场景。

2.3 安装与配置

安装极其简单,各大系统平台都支持。

使用 pip 从 PyPI 安装:

bash 复制代码
# 安装(PyPI 包名为 nanobot-ai)
pip install nanobot-ai

macOS / Linux:

bash 复制代码
curl -fsSL https://raw.githubusercontent.com/HKUDS/nanobot/main/scripts/install.sh | sh

Windows PowerShell:

bash 复制代码
irm https://raw.githubusercontent.com/HKUDS/nanobot/main/scripts/install.ps1 | iex

配置文件 ~/.nanobot/config.json:

json 复制代码
{
  "websocket": {
    "enable": true,
    "host": "127.0.0.1",
    "port": 8765,
    "token": "your-ws-token"
  },
  "providers": {
    "deepseek": {
      "enable": true,
      "api_key": "sk-xxx",
      "model": "deepseek-chat"
    }
  }
}

启动 gateway 服务:

bash 复制代码
nanobot gateway

注:因为我要用它的常驻后台形式,所以使用nanobot gateway。如果是想用类似于CLI命令行的形式,

则可以使用:

bash 复制代码
nanobot agent

三、系统架构

3.1 总体设计

复制代码
GitLab MR Webhook
     │ POST /api/gitlab/mr-webhook
     ▼
┌─────────────────────────────┐
│     审查助手 (Flask)          │
│  Webhook 接收 → 异步线程池    │
│  └→ 每个 Worker 通过 WS 连接  │
└────────┬────────────────────┘
         │ WebSocket (nanobot Gateway 协议)
┌────────▼────────────────────┐
│  nanobot Agent 网关           │
│  会话管理 → Agent 引擎 → LLM │
└─────────────────────────────┘

3.2 核心数据流

复制代码
GitLab MR Event → app.py 接收 Webhook
  → 解析 Payload,提取 project_id / mr_iid / diffs
  → 过滤二进制/非代码文件
  → 按文件拆分为独立任务,提交 ThreadPoolExecutor
  → 立即返回 202

[后台线程] tasks.py:
  1. build_review_prompt() --- 将 diff + 上下文组装为自然语言 prompt
  2. call_nanobot_agent() --- 通过 WebSocket 发送 prompt 给 nanobot
  3. post_gitlab_comment() --- 将审查结果发布到 MR 评论

3.3 nanobot Gateway 协议

所有消息为 UTF-8 JSON 文本帧:

客户端 → nanobot:

json 复制代码
{"type": "message", "content": "审查 prompt", "chat_id": "会话ID"}

nanobot → 客户端(事件流):

事件 说明
ready 连接建立后发送,含 chat_id
turn_start Agent 开始处理
delta 流式增量输出
message 完整消息块
error 错误事件
turn_end 本轮处理结束

连接生命周期:

复制代码
客户端                          nanobot
  │── WebSocket 握手 ──────────▶│  ws://host:port?token=xxx&client_id=myapp
  │◀── ready ──────────────────│  {event:"ready", chat_id:"uuid"}
  │── {type:"message",...} ───▶│  发送审查 prompt
  │◀── turn_start ────────────│  开始处理
  │◀── delta / message ──────│  流式 + 完整结果
  │◀── turn_end ──────────────│  本轮结束

四、代码实现

4.1 传统方案 vs nanobot 方案对比

传统方案(直接 LLM SDK 调用):

python 复制代码
import openai, time

client = openai.OpenAI(api_key=LLM_API_KEY, base_url=LLM_API_BASE_URL)

def call_llm_api(diff_content, commit_summary):
    messages = [
        {"role": "system", "content": "你是一个专业的 AI 代码审查助手..."},
        {"role": "user", "content": f"请分析下面的代码变更...\n{diff_content}"}
    ]
    for attempt in range(3):
        try:
            response = client.chat.completions.create(
                model=LLM_MODEL, messages=messages, timeout=60)
            return response.choices[0].message.content
        except (openai.RateLimitError, openai.APITimeoutError) as e:
            if attempt < 2:
                time.sleep(2 ** attempt)
                continue
            raise

nanobot 方案(本方案):

python 复制代码
import asyncio, json, websockets

async def call_nanobot_agent(prompt: str) -> str:
    """通过 nanobot Agent 网关进行代码审查"""
    async with websockets.connect(
        f"ws://127.0.0.1:8765?token={TOKEN}&client_id=gitcoder"
    ) as ws:
        ready = json.loads(await ws.recv())
        chat_id = ready["chat_id"]

        await ws.send(json.dumps({
            "type": "message", "content": prompt, "chat_id": chat_id
        }))

        parts = []
        while True:
            data = json.loads(await ws.recv())
            event = data.get("event")
            if event in ("delta", "message"):
                parts.append(data.get("text", ""))
            elif event == "turn_end":
                break
            elif event == "error":
                raise Exception(data.get("text", "Unknown error"))
        return "".join(parts)
关注点 传统方案 nanobot 方案
API Key 应用自行管理 nanobot 统一管理
Provider 切换 改代码 改 nanobot 配置
错误重试 自行实现(~15 行) nanobot 内置
流式输出 复杂回调 内置事件支持
会话管理 不适用 chat_id 内置隔离

4.2 Webhook 接收端

python 复制代码
# app.py
from flask import Flask, request, jsonify
from concurrent.futures import ThreadPoolExecutor
from tasks import code_review_task

app = Flask(__name__)
executor = ThreadPoolExecutor(max_workers=5)

@app.route('/api/gitlab/mr-webhook', methods=['POST'])
def mr_webhook():
    data = request.json
    project_id = data['project']['id']
    mr_iid = data['object_attributes']['iid']
    diffs = get_mr_diffs(project_id, mr_iid)

    for diff in diffs:
        if should_skip_file(diff):
            continue
        context = build_file_context(project_id, mr_iid, diff)
        executor.submit(code_review_task,
            project_id=project_id, mr_iid=mr_iid,
            file_path=diff['new_path'], context=context)

    return jsonify({'status': 'accepted'}), 202

4.3 WebSocket 连接管理(线程级单例)

python 复制代码
class NanobotConnection:
    _local = threading.local()

    @classmethod
    async def connect(cls):
        existing = getattr(cls._local, 'connection', None)
        if existing and not existing.closed:
            return existing
        ws = await websockets.connect(
            f"{NANOBOT_WS_URL}?token={TOKEN}&client_id=gitcoder-{threading.get_ident()}")
        cls._local.connection = ws
        return ws

    @classmethod
    async def close(cls):
        conn = getattr(cls._local, 'connection', None)
        if conn and not conn.closed:
            await conn.close()
        cls._local.connection = None

4.4 审查 Prompt 构建

python 复制代码
def build_review_prompt(file_path, context_dict, commit_summary):
    lines = [
        f"请对以下代码变更进行专业的代码审查。",
        f"文件路径:{file_path}",
        f"变更摘要:{commit_summary}",
        ""
    ]
    for i, hunk in enumerate(context_dict['hunks']):
        lines.append(f"=== 变更区域 {i+1} ===")
        if hunk.get('context_before'):
            lines.append("--- 变更前上下文 ---")
            lines.extend(hunk['context_before'])
        lines.append("--- 源代码(旧) ---")
        lines.extend(hunk['source_lines'])
        lines.append("--- 目标代码(新) ---")
        lines.extend(hunk['target_lines'])
        if hunk.get('context_after'):
            lines.append("--- 变更后上下文 ---")
            lines.extend(hunk['context_after'])
        lines.append("")

    lines += [
        "请从以下角度分析:",
        "1. 潜在 Bug 和逻辑错误",
        "2. 安全风险(SQL 注入、XSS 等)",
        "3. 性能问题",
        "4. 代码规范和可读性",
        "5. 设计模式和改进建议",
        "",
        "请用中文输出,标注严重程度(高/中/低)。"
    ]
    return "\n".join(lines)

五、部署

5.1 架构示意

复制代码
┌──────────┐  WS   ┌──────────┐  API  ┌────────┐
│ nanobot  │◄─────►│ 审查助手  │◄─────►│ GitLab │
│ (AI 网关) │ :8765 │ (Flask)  │       │        │
└──────────┘       └──────────┘       └────────┘

5.2 配置

nanobot config.json

json 复制代码
{
  "websocket": { "enable": true, "host": "127.0.0.1", "port": 8765, "token": "nanobot-review-token" },
  "providers": { "deepseek": { "enable": true, "api_key": "sk-xxx", "model": "deepseek-chat" } }
}

审查助手 .env

bash 复制代码
GITLAB_URL=https://gitlab.example.com
PRIVATE_TOKEN=glpat-xxxx
NANOBOT_WS_URL=ws://127.0.0.1:8765
NANOBOT_TOKEN=nanobot-review-token
WEBHOOK_SECRET=webhook-secret-xxx
MAX_WORKERS=5

5.3 启动

bash 复制代码
# 1. 启动 nanobot gateway
nanobot gateway

# 2. 启动审查助手
python app.py

# 3. GitLab 配置 Webhook → http://your-server:5000/api/gitlab/mr-webhook
#    Secret Token: webhook-secret-xxx, Trigger: Merge Request Events

5.4 验证

bash 复制代码
# 验证 nanobot WebSocket(需安装 websocat)
websocat "ws://127.0.0.1:8765?token=nanobot-review-token&client_id=test"

# 验证审查助手接口
curl -X POST "http://127.0.0.1:5000/api/gitlab/mr-webhook" \
  -H "Content-Type: application/json" \
  -H "X-Gitlab-Token: webhook-secret-xxx" \
  -d '{"object_kind": "merge_request", "project": {"id": 1}, "object_attributes": {"iid": 1}}'

六、最佳实践

6.1 审查质量

  • 上下文增强:除 diff 外传入相关文件、历史变更、团队规范
  • 分层审查:先快速扫描,对高风险文件深度分析
  • Prompt 迭代:持续优化审查 prompt,加入团队特定规范

6.2 性能

  • 连接池复用:线程 Worker 复用 WebSocket 连接,减少握手开销
  • 批量提交:同一 MR 的多个文件可打包为一个 prompt
  • 缓存跳过:未变更文件跳过审查,节约 Token

6.3 安全

  • GitLab Token 仅授予 read_apiwrite_repository 权限
  • nanobot 与审查助手间使用内网 127.0.0.1 通信
  • 定期更换 nanobot WebSocket Token
  • 记录审计日志(prompt、response、耗时)

七、与传统方案对比总结

维度 传统方案 本方案(nanobot)
代码行数 ~450 行(含重试、错误处理) ~400 行(AI 交互仅 ~30 行)
Provider 切换 改代码 + 重启 改 nanobot 配置
API Key 管理 每个项目独立配置 nanobot 统一管理
项目知识 仅当前 diff Dream 记忆系统积累项目级知识
安全 代码经第三方 API 传输 nanobot 本地运行,代码不离开内网
扩展性 每实例独立配置 所有实例共享一个 nanobot

八、未来展望

  • 封装为 nanobot Skillnanobot skill install code-review 一键启用
  • 跨文件分析:关联分析 MR 中的全部变更
  • 自动修复:AI 发现 bug 并生成修复 patch
  • CI 集成:审查结果作为 Pipeline stage,失败阻止合并

结语

本方案实现了从"直接调用 LLM API"到"通过 nanobot Agent 网关"的架构升级。核心收益:LLM 实现细节与应用业务逻辑完全解耦,AI 能力成为基础设施而非嵌入在应用代码中。

nanobot Agent 网关提供了一个通用的 AI 能力入口,代码审查助手是第一个实践。同样的模式可扩展到文档生成、智能运维、代码转换等更多场景。

"好的架构不是设计出来的,而是演进出来的。"

--- 从 v1.0.0 到 v1.1.0,我们迈出了从工具到平台的关键一步。