nanobot 源码架构分析
目录
概述与核心概念
项目简介
NanoBot 是一个超轻量级的个人AI助手(约4000行核心代码),通过连接多个聊天平台(TG、Discord、WhatsApp、飞书、QQ、钉钉、Slack、Email、Mochat)并使用各种LLM提供商(OpenRouter、Claude、GPT、DeepSeek、MiniMax等)来响应消息。
应用场景与价值定位
AI Agent 的新形态:从开发者工具到办公助手
Claude Code、Cline 等工具主要服务于开发者场景,帮助程序员在 IDE 中完成代码编写、调试、提交等开发工作。然而,日常工作中还有大量非开发任务:查询信息、发送通知、管理日程、整理文档等。这些任务发生在办公软件中,而非代码编辑器。
NanoBot 的定位正是填补这一空白:将 AI 能力下放到日常办公软件里,让用户在工作沟通的场景中直接调用 AI。
办公软件中的虚拟员工
当你把一个 NanoBot 接入飞书群聊,它就不再只是一个聊天机器人,而更像是团队中的一名虚拟员工:
- 随时可召唤:在飞书、Slack、Discord 中 @nanobot 即可对话
- 持久记忆:记得用户的偏好、之前的对话上下文
- 工具能力:能执行文件操作、Shell 命令、Web 搜索,甚至调用外部 API
- 多身份:不同群聊可以是不同的"人",拥有不同的技能和知识
这类似于 Microsoft 365 Copilot 或 Slack AI,但 NanoBot 是轻量级、可自托管、可定制的。
轻量级与可扩展性
NanoBot 的核心代码仅约 4000 行,这意味着:
| 特性 | 价值 |
|---|---|
| 代码可读性 | 新成员一周内可理解完整架构 |
| 二次开发 | 添加新通道/新 Provider 只需几十行代码 |
| 部署简单 | pip install 即可运行,无复杂依赖 |
| 定制灵活 | 修改 bootstrap 文件即可改变 Agent 行为 |
对于希望拥有私有 AI 助手的团队或个人,NanoBot 提供了一个恰到好处的起点:比直接调用 API 更智能,比自建完整 Agent 框架更轻便。
核心设计原则
| 原则 | 描述 | 实现 |
|---|---|---|
| 解耦 | 通道与核心逻辑分离 | MessageBus 消息总线 |
| 可扩展 | 轻松添加新通道/提供商 | BaseChannel 接口 / ProviderSpec 注册表 |
| 持久化 | 会话和记忆持久化 | Session JSONL / MemoryStore |
| 异步 | 高效处理消息 | asyncio 异步编程 |
整体架构
系统启动与初始化
启动入口
Gateway 启动命令:nanobot gateway
# nanobot/cli/commands.py
async def run():
await cron.start() # 定时任务
await heartbeat.start() # 心跳任务(每30分钟)
await asyncio.gather(
agent.run(), # Agent 循环(串行处理)
channels.start_all(), # 并发启动所有 Channel
)
初始化流程
Channel 初始化
ChannelManager 根据配置初始化已启用的通道:
# nanobot/channels/manager.py
class ChannelManager:
async def start_all(self) -> None:
for name, config in self.config.channels:
if config.enabled:
channel = self._create_channel(name, config)
await channel.start()
# 支持的通道类型
CHANNELS = {
"TG": TGChannel,
"discord": DiscordChannel,
"whatsapp": WhatsAppChannel,
"feishu": FeishuChannel,
...
}
Agent 初始化
# 创建 AgentLoop
agent = AgentLoop(
bus=message_bus,
provider=llm_provider,
workspace=workspace_path,
model=config.agents.defaults.model,
max_tool_iterations=config.agents.defaults.max_tool_iterations,
memory_window=config.agents.defaults.memory_window,
)
# AgentLoop 内部组件
# - self.context = ContextBuilder(workspace)
# - self.sessions = SessionManager(workspace)
# - self.tools = ToolRegistry()
# - 注册默认工具: read_file, write_file, edit_file, exec, web_search, ...
Provider 初始化
# 创建 LiteLLMProvider
provider = LiteLLMProvider(
api_key=config.get_api_key(),
api_base=config.get_api_base(),
default_model=config.agents.defaults.model,
provider_name=config.get_provider_name(),
)
# 自动检测 Provider 类型
# 1. provider_name 直接匹配 → Gateway/Local
# 2. api_key 前缀检测 → "sk-or-" → OpenRouter
# 3. api_base 关键词检测 → "aihubmix" → AiHubMix
核心模块详解
1. Agent 模块 (nanobot/agent/)
Agent模块是nanoBot的核心处理引擎,负责接收消息、构建上下文、调用LLM、执行工具并返回响应。
1.1 AgentLoop (loop.py)
class AgentLoop:
def __init__(self, bus, provider, workspace, model, ...):
self.bus = bus # 消息总线
self.provider = provider # LLM提供商
self.workspace = workspace # 工作区路径
self.model = model # 使用的模型
self.context = ContextBuilder() # 上下文构建器
self.sessions = SessionManager() # 会话管理器
self.tools = ToolRegistry() # 工具注册表
核心方法:
| 方法 | 功能 |
|---|---|
run() |
启动 agent 循环,持续从总线消费消息 |
_process_message(msg) |
处理单条入站消息 |
_consolidate_memory() |
记忆整合(归档到 MEMORY.md/HISTORY.md) |
process_direct() |
直接处理消息(CLI 或 cron 触发) |
1.2 ContextBuilder (context.py)
负责构建系统提示词,是 Agent 与 LLM 交互的核心组件。
class ContextBuilder:
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
def __init__(self, workspace: Path):
self.workspace = workspace
self.memory = MemoryStore(workspace) # 长期记忆
self.skills = SkillsLoader(workspace) # 技能加载器
系统提示词组成:
- 核心身份信息 - 当前时间、运行时环境、Workspace 路径
- Bootstrap 文件 - AGENTS.md, SOUL.md, USER.md, TOOLS.md, IDENTITY.md
- 长期记忆上下文 - 从 MEMORY.md 读取
- 技能系统 - Always 技能完整加载 + Available 技能摘要
消息构建流程:
def build_messages(self, history, current_message, ...):
messages = []
# 1. 系统提示词
system_prompt = self.build_system_prompt(skill_names)
messages.append({"role": "system", "content": system_prompt})
# 2. 历史消息
messages.extend(history)
# 3. 当前用户消息
messages.append({"role": "user", "content": current_message})
return messages
1.3 Skills 技能系统
Skills 是 Markdown 文件 (SKILL.md),用于扩展 Agent 能力。
渐进式加载:
| 类型 | 加载方式 | 配置 |
|---|---|---|
| Always 技能 | 完整内容写入系统提示词 | always: true |
| Available 技能 | 仅在摘要中列出,按需读取 | always: false |
技能来源:
| 来源 | 位置 | 优先级 |
|---|---|---|
| Workspace Skills | ~/.nanobot/workspace/skills/{name}/SKILL.md |
高 |
| Built-in Skills | nanobot/skills/{name}/SKILL.md |
低 |
1.4 MemoryStore 两层记忆系统
class MemoryStore:
"""两层记忆: MEMORY.md + HISTORY.md"""
memory_file # 长期记忆(Agent 主动写入)
history_file # 事件日志(自动追加)
| 文件 | 用途 | 写入时机 |
|---|---|---|
MEMORY.md |
长期事实/用户偏好 | Agent 调用 write_file |
HISTORY.md |
可 grep 搜索的事件日志 | 自动追加 |
1.5 工具系统 (tools/)
| 工具 | 文件 | 功能 |
|---|---|---|
| ReadFileTool | filesystem.py | 读取文件 |
| WriteFileTool | filesystem.py | 写入文件 |
| EditFileTool | filesystem.py | 编辑文件 |
| ListDirTool | filesystem.py | 列出目录 |
| ExecTool | shell.py | 执行 Shell 命令 |
| WebSearchTool | web.py | Web 搜索 |
| WebFetchTool | web.py | Web 抓取 |
| MessageTool | message.py | 发送消息 |
| SpawnTool | spawn.py | 创建子代理 |
| CronTool | cron.py | 定时任务 |
2. Channels 模块 (nanobot/channels/)
2.1 BaseChannel 接口
class BaseChannel(ABC):
name: str = "base"
@abstractmethod
async def start(self) -> None: # 启动通道监听
pass
@abstractmethod
async def stop(self) -> None: # 停止通道
pass
@abstractmethod
async def send(self, msg: OutboundMessage) -> None: # 发送消息
pass
def _handle_message(self, sender_id, chat_id, content, metadata=None):
"""处理入站消息,检查权限后推送到总线"""
2.2 ChannelManager
- 根据配置初始化已启用的通道
- 启动/停止所有通道
- 路由出站消息到对应通道
3. Bus 模块 (nanobot/bus/)
消息总线,解耦通道和 agent 核心。
事件类型
@dataclass
class InboundMessage:
channel: str # TG, discord, feishu...
sender_id: str # 用户标识
chat_id: str # 聊天/频道标识
content: str # 消息内容
media: list[str] # 媒体 URL
metadata: dict # 通道特定数据
@property
def session_key(self) -> str:
return f"{self.channel}:{self.chat_id}"
@dataclass
class OutboundMessage:
channel: str
chat_id: str
content: str
reply_to: str | None
media: list[str]
metadata: dict
MessageBus
class MessageBus:
def __init__(self):
self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()
self._outbound_subscribers: dict[str, list[Callback]] = {}
主要方法:
publish_inbound(msg)- 发布入站消息consume_inbound()- 消费入站消息(阻塞等待)publish_outbound(msg)- 发布出站消息subscribe_outbound(channel, callback)- 订阅通道出站消息
4. Session 模块 (nanobot/session/)
会话管理,负责维护对话历史、会话持久化、会话缓存。
@dataclass
class Session:
key: str # channel:chat_id
messages: list[dict] # 消息列表
created_at: datetime
updated_at: datetime
metadata: dict
实现细节:
- 内存缓存:
_cache: dict[str, Session] - 文件持久化:
~/.nanobot/sessions/{key}.jsonl - 无锁机制(潜在并发问题)
5. Providers 模块 (nanobot/providers/)
支持 12+ LLM 提供商:
| 提供商 | 关键词 | LiteLLM前缀 |
|---|---|---|
| OpenRouter | openrouter | openrouter |
| Anthropic | claude | - |
| OpenAI | gpt | - |
| DeepSeek | deepseek | deepseek |
| Gemini | gemini | gemini |
| Zhipu | glm, zai | zai |
| DashScope | qwen | dashscope |
| Moonshot | kimi | moonshot |
| MiniMax | minimax | minimax |
| vLLM | vllm | hosted_vllm |
| Groq | groq | groq |
消息处理流程
入站消息流程
Agent Loop 内部流程
LLM Provider 体系
核心设计原则:注册表驱动模式
LiteLLMProvider 采用注册表驱动设计,通过 ProviderSpec 元数据描述所有 LLM 提供商:
# 添加新提供商只需修改 registry.py
# 1. 在 PROVIDERS 元组中添加 ProviderSpec
# 2. 在 config/schema.py 添加配置字段
# 完成!环境变量、前缀、状态显示全部自动生效
ProviderSpec 字段
@dataclass(frozen=True)
class ProviderSpec:
name: str # 配置字段名
keywords: tuple[str, ...] # 模型名关键词
env_key: str # LiteLLM 环境变量
litellm_prefix: str = "" # 前缀: model → provider/model
skip_prefixes: tuple = () # 跳过前缀的条件
env_extras: tuple = () # 额外环境变量
is_gateway: bool = False # 是否为网关
is_local: bool = False # 是否为本地部署
detect_by_key_prefix: str = "" # API Key 前缀匹配
detect_by_base_keyword: str = "" # API Base 关键词
default_api_base: str = "" # 默认 API 地址
strip_model_prefix: bool = False # 重新前缀前是否剥离
model_overrides: tuple = () # 模型特定参数覆盖
提供商类型
| 类型 | 匹配方式 | 示例 |
|---|---|---|
| Gateway | api_key 前缀 / api_base 关键词 | OpenRouter, AiHubMix |
| Standard | 模型名关键词 | Anthropic, MiniMax |
| Local | 配置键名 | vLLM |
模型名解析流程
API 调用链路
添加新 Provider 示例
# registry.py
ProviderSpec(
name="myprovider",
keywords=("myprovider", "mymodel"),
env_key="MYPROVIDER_API_KEY",
display_name="My Provider",
litellm_prefix="myprovider",
default_api_base="https://api.myprovider.com/v1",
model_overrides=(
("special-model", {"temperature": 1.0}),
),
)
Channel 详解(以飞书为例)
概述
飞书通道使用 WebSocket 长连接 接收事件,无需公网 IP 或 Webhook。
配置
class FeishuConfig(BaseModel):
enabled: bool = False
app_id: str = "" # 飞书开放平台 App ID
app_secret: str = "" # App Secret
encrypt_key: str = "" # 加密密钥(可选)
verification_token: str = "" # 验证 Token(可选)
allow_from: list[str] = [] # 允许的用户 open_ids
消息处理流程
消息格式转换
飞书通道支持 Markdown 转卡片消息:
- 标题转换:
# Heading→div元素(加粗) - 表格转换: Markdown 表格 → 飞书表格元素
- 代码块保护: 防止被错误解析
性能与并发分析
多通道消息处理机制
系统采用单消费者队列模型:
关键机制:
- 共享队列:所有 Channel 的消息进入同一个
asyncio.Queue - FIFO 顺序:消息按到达顺序依次处理
- Session 隔离:
session_key = f"{channel}:{chat_id}"
工具调用串行瓶颈
# 串行执行:必须等待上一个工具完成
for tool_call in response.tool_calls:
result = await self.tools.execute(tool_call.name, tool_call.arguments)
messages = self.context.add_tool_result(messages, ...)
性能影响:
| 场景 | 处理方式 | 延迟 |
|---|---|---|
| 5个工具串行调用 | 依次等待 | T1 + T2 + T3 + T4 + T5 |
| 消息积压 | 等待队列 | 队列长度 × 平均处理时间 |
性能问题与优化
| 问题 | 位置 | 优化方向 |
|---|---|---|
| 单线程串行处理 | loop.py:run() |
引入多消费者模式 |
| 工具串行执行 | loop.py:243-250 |
使用 asyncio.gather() |
| 无锁 Session 访问 | session/manager.py |
添加 asyncio.Lock |
| Context 持续增长 | context.py |
定期压缩/截断 |
配置系统
配置加载流程
配置示例
{
"agents": {
"defaults": {
"model": "MiniMax-M2.1",
"max_tool_iterations": 20,
"memory_window": 50
}
},
"channels": {
"feishu": {
"enabled": true,
"app_id": "cli_xxx",
"app_secret": "xxx"
}
},
"providers": {
"minimax": {
"api_key": "your-api-key",
"api_base": "https://api.minimax.io/v1"
}
}
}
总结
架构优势
- MessageBus - 解耦通道和 agent 核心
- ProviderRegistry - 通过注册表支持多提供商
- SessionManager - 会话持久化
- ToolRegistry - 可扩展的工具系统
适用场景
- 低频消息场景(每分钟 < 10 条)
- 简单的单用户对话
- 快速原型开发
不适用场景
- 高并发场景
- 需要实时响应的应用
- 需要并行执行多个工具的场景

浙公网安备 33010602011771号