代码审查是软件开发质量保障的核心环节,有效审查可提前发现 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 开发者 后续每次 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 CLInanobot(本方案)
运行模式CLI 交互式——有人参与的编码/调试Gateway 守护进程——后台静默运行,WebSocket API
触发方式手动输入命令 / IDE 插件Webhook 自动触发,完全无人值守
集成深度面向终端用户标准化 WebSocket 协议,任何语言可接入
多任务一次一个会话多通道并行处理多个 MR
可观测性终端输出内置 WebUI 仪表盘
项目知识无持久化记忆Dream 记忆系统,可预扫描全仓库

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

在这里插入图片描述

2.3 安装与配置

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

使用 pip 从 PyPI 安装:

# 安装(PyPI 包名为 nanobot-ai)
pip install nanobot-ai

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/HKUDS/nanobot/main/scripts/install.sh | sh

Windows PowerShell:

irm https://raw.githubusercontent.com/HKUDS/nanobot/main/scripts/install.ps1 | iex

配置文件 ~/.nanobot/config.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 服务:

nanobot gateway

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

则可以使用:

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:

{"type": "message", "content": "审查 prompt", "chat_id": "会话ID"}

nanobot → 客户端(事件流):

事件说明
ready连接建立后发送,含 chat_id
turn_startAgent 开始处理
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 调用):

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 方案(本方案):

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 接收端

# 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 连接管理(线程级单例)

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 构建

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

{
  "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

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 启动

# 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 验证

# 验证 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 统一管理
项目知识仅当前 diffDream 记忆系统积累项目级知识
安全代码经第三方 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,我们迈出了从工具到平台的关键一步。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐