nanobot 源码架构分析

目录

  1. 概述与核心概念
  2. 系统启动与初始化
  3. 核心模块详解
  4. 消息处理流程
  5. LLM Provider 体系
  6. Channel 详解(以飞书为例)
  7. 性能与并发分析
  8. 配置系统
  9. 总结

概述与核心概念

项目简介

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 异步编程

整体架构

flowchart TB subgraph 用户层 User["用户"] end subgraph 通道层["Channels"] TG["TG"] Feishu["Feishu"] Discord["Discord"] WhatsApp["WhatsApp"] end subgraph 消息总线["Message Bus"] Queue["asyncio.Queue"] end subgraph 核心引擎["Agent Core"] AL["AgentLoop"] CB["ContextBuilder"] SM["SessionManager"] TR["ToolRegistry"] MS["MemoryStore"] end subgraph LLM层["Providers"] LP["LiteLLMProvider"] Registry["ProviderRegistry"] end User --> TG User --> Feishu TG --> Queue Feishu --> Queue Discord --> Queue WhatsApp --> Queue Queue --> AL AL --> CB AL --> SM AL --> TR AL --> MS AL --> LP LP --> Registry LP --> |"API调用"| LLM["MiniMax / Claude / GPT..."]

系统启动与初始化

启动入口

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
    )

初始化流程

flowchart TD A["nanobot gateway"] --> B["load_config()"] B --> C["读取 ~/.nanobot/config.json"] C --> D["Pydantic 验证"] D --> E["创建组件"] E --> F["MessageBus"] E --> G["LiteLLMProvider"] E --> H["SessionManager"] E --> I["AgentLoop"] E --> J["ChannelManager"] J --> K["start_all()"] K --> L["遍历配置中的 enabled=true 的通道"] L --> M["每个 Channel.start()"] I --> N["agent.run()"]

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)   # 技能加载器

系统提示词组成:

  1. 核心身份信息 - 当前时间、运行时环境、Workspace 路径
  2. Bootstrap 文件 - AGENTS.md, SOUL.md, USER.md, TOOLS.md, IDENTITY.md
  3. 长期记忆上下文 - 从 MEMORY.md 读取
  4. 技能系统 - 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

消息处理流程

入站消息流程

sequenceDiagram participant User as 用户 participant Channel as FeishuChannel participant Bus as MessageBus participant Agent as AgentLoop participant LLM as LiteLLMProvider participant LLM_API as LLM API User->>Channel: 发送消息 Channel->>Channel: 解析消息 Channel->>Channel: 权限检查 Channel->>Bus: publish_inbound(InboundMessage) Bus->>Agent: consume_inbound() Agent->>Agent: _process_message() Note over Agent: 1. 获取/创建会话<br/>2. 构建上下文<br/>3. Agent Loop Agent->>LLM: chat(messages, tools) LLM->>LLM_API: API 调用 LLM_API-->>LLM: 响应 LLM-->>Agent: LLMResponse alt 有工具调用 Agent->>Agent: 执行工具 Agent->>LLM: 继续对话 end Agent->>Bus: publish_outbound(OutboundMessage) Bus->>Channel: 发送回调 Channel->>User: 显示响应

Agent Loop 内部流程

flowchart TD A[接收 InboundMessage] --> B{检查斜杠命令} B -->|"/new"| C[清空会话 + 记忆整合] B -->|"/help"| D[返回帮助信息] B -->|无命令| E[获取/创建会话] E --> F{会话消息数 > memory_window?} F -->|是| G[记忆整合] F -->|否| H[构建系统提示词] G --> H H --> I[构建消息列表] I --> J[调用 LLM] J --> K{有工具调用?} K -->|是| L[执行工具] L --> M[添加反思提示] M --> J K -->|否| N[返回响应] N --> O[保存到会话] O --> P[返回 OutboundMessage]

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

模型名解析流程

flowchart TD A["chat(model)"] --> B{"Gateway?"} B -->|Yes| C["应用 Gateway 前缀"] B -->|No| D["find_by_model 匹配"] C --> E{"strip_model_prefix?"} E -->|true| F["剥离 → 添加前缀"] E -->|false| G["直接添加前缀"] D --> H{"匹配成功?"} H -->|Yes| I{"已有 skip_prefix?"} H -->|No| J["保持原名"] I -->|Yes| K["跳过前缀"] I -->|No| G F --> L[返回解析后的模型名] G --> L J --> L K --> L

API 调用链路

sequenceDiagram participant Agent participant LP as LiteLLMProvider participant Registry participant LiteLLM participant API Agent->>LP: chat(messages, tools, "MiniMax-M2.1") LP->>Registry: find_by_model("MiniMax-M2.1") Registry-->>LP: ProviderSpec LP->>LP: _resolve_model() Note over LP: "MiniMax-M2.1" → "minimax/MiniMax-M2.1" LP->>LiteLLM: acompletion(model="minimax/MiniMax-M2.1", ...) LiteLLM->>API: POST /v1/chat/completions API-->>LiteLLM: OpenAI-compatible JSON LiteLLM-->>LP: Response LP->>LP: _parse_response() LP-->>Agent: LLMResponse

添加新 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

消息处理流程

flowchart LR subgraph 接收端 WS[WebSocket] --> EH[EventHandler] EH --> MP[消息解析] MP --> DC[去重检查] DC --> P[权限检查] end subgraph 处理端 P --> PC[解析内容] PC --> FM[_handle_message] FM --> MB[推送到MessageBus] end subgraph 发送端 MB --> CE[_build_card_elements] CE --> CM[发送卡片消息] end

消息格式转换

飞书通道支持 Markdown 转卡片消息:

  • 标题转换: # Headingdiv 元素(加粗)
  • 表格转换: Markdown 表格 → 飞书表格元素
  • 代码块保护: 防止被错误解析

性能与并发分析

多通道消息处理机制

系统采用单消费者队列模型

flowchart TB subgraph 通道层 TG["TG"] WS["WhatsApp"] FS["Feishu"] end subgraph MessageBus Q["inbound Queue<br/>(FIFO)"] end subgraph AgentCore AL["AgentLoop.run()<br/>(串行)"] end TG --> Q WS --> Q FS --> Q Q --> AL

关键机制:

  • 共享队列:所有 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 定期压缩/截断

配置系统

配置加载流程

flowchart TD A["CLI: nanobot gateway"] --> B["load_config()"] B --> C["Read config.json"] C --> D["Pydantic validation"] D --> E["Return Config object"] E --> F["Create components"]

配置示例

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

总结

架构优势

  1. MessageBus - 解耦通道和 agent 核心
  2. ProviderRegistry - 通过注册表支持多提供商
  3. SessionManager - 会话持久化
  4. ToolRegistry - 可扩展的工具系统

适用场景

  • 低频消息场景(每分钟 < 10 条)
  • 简单的单用户对话
  • 快速原型开发

不适用场景

  • 高并发场景
  • 需要实时响应的应用
  • 需要并行执行多个工具的场景
posted @ 2026-02-14 19:32  wlu  阅读(1594)  评论(0)    收藏  举报