代码审查是软件开发质量保障的核心环节,有效审查可提前发现 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_api和write_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 Skill :
nanobot 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,我们迈出了从工具到平台的关键一步。