nanobot项目的深度架构与功能分析报告
nanobot 是一个极简、超轻量级的个人 AI 助手框架(基于 Python 开发,灵感来自 OpenClaw)。它的核心设计哲学是**“用最少的代码实现完整的 Agent 能力”**。
一、 整体架构设计 (Architecture)
从代码结构和交互逻辑来看,nanobot 采用了经典的 事件驱动(Event-Driven) 与 总线架构(Bus Architecture)。整个系统被清晰地划分为三个主要层级:
- 接入层 (Channels Layer):负责与外部聊天平台(如 Telegram, Discord, Feishu, 微信/WhatsApp 等)通信。
- 总线层 (Message Bus):一个异步的内存消息队列,彻底解耦了“网络 I/O(聊天软件)”和“CPU/GPU 密集型任务(LLM 推理与工具执行)”。
- 核心代理层 (Agent Core):系统的“大脑”,负责上下文组装、LLM 路由调用、记忆管理和工具执行(ReAct 循环)。
此外,还有两个重要的后台守护进程:
- Cron Service:负责定时任务的触发。
- Heartbeat Service:主动唤醒机制(定期让 LLM 检查工作区是否有待办任务)。
二、 核心功能解析 (Core Features)
- 全平台多端接入 (Multi-Channel)
- 支持通过轮询(Polling)、WebSocket 长连接等无公网 IP 要求的协议接入。
- 支持通过
OutboundMessage/InboundMessage标准化不同平台的消息格式(文本、图片、文件、语音)。
- 多模型无缝切换 (Multi-Provider via LiteLLM)
- 底层深度集成
litellm,并通过极简的ProviderSpec注册表模式,支持 OpenAI, Anthropic, Gemini, DeepSeek 以及本地模型 (Ollama, vLLM)。
- 底层深度集成
- 双层记忆系统 (Dual-layer Memory)
- 短期记忆(上下文):由
SessionManager管理,存放在.jsonl文件中。 - 长期记忆(持久化):通过大模型进行记忆压缩(Consolidation),将关键事实提炼到
MEMORY.md,将对话流水账追加到HISTORY.md。
- 短期记忆(上下文):由
- 强大的工具生态与 MCP 支持 (Tools & MCP)
- 内置:文件读写、沙盒 Shell (
exec)、Web 搜索 (brave/tavily/duckduckgo等)、网页抓取、定时器 (cron)。 - MCP (Model Context Protocol):原生支持接入标准 MCP Server,意味着它可以无缝复用 Claude 桌面版的工具链。
- 内置:文件读写、沙盒 Shell (
- 子代理与并发执行 (Subagents)
- 通过
spawn工具,主 Agent 可以孵化出后台 Subagent 异步执行耗时任务(如:分析大型代码库),完成后通过系统消息回调通知主 Agent。
- 通过
三、 核心文件与模块解读 (Core Files Breakdown)
1. nanobot/agent/loop.py (核心决策大脑)
- 作用:控制 Agent 的生命周期和 ReAct 循环。
- 核心方法
_run_agent_loop:- 向 LLM 发送包含历史、上下文和可用 Tools 的请求。
- 检查返回是否有
tool_calls。 - 如果有,解析并调用
self.tools.execute(),将结果附加到消息历史中,再次请求 LLM,直至任务完成或达到max_iterations上限。
- 分发机制
_dispatch:通过asyncio.create_task为每个用户请求创建独立协程,保证了处理并发请求时不阻塞主循环。
2. nanobot/bus/queue.py (消息总线)
- 作用:基于
asyncio.Queue实现的MessageBus。 - 机制:包含
inbound和outbound两个队列。Channel 把消息塞进inbound,AgentLoop 从inbound取消息处理,处理完把回复塞进outbound,ChannelManager 再从outbound取出并路由给具体的 Channel发送。这种设计使得 Channel 挂掉或重启不会影响正在思考的 LLM。
3. nanobot/agent/memory.py (记忆压缩与持久化)
- 作用:防止上下文溢出(Context Window Overflow)。
- 机制
MemoryConsolidator.maybe_consolidate_by_tokens:- 每次对话后,计算当前 Session 的 Token 数量。
- 如果超过阈值(
context_window_tokens / 2),则截取旧的对话,将其放入一段特殊的 prompt 中,强制调用save_memoryTool。 - LLM 会将这段旧对话提取为“事件摘要”追加到
HISTORY.md,并将新学到的事实更新到MEMORY.md的 Markdown 文本中。旧对话随后被移出短时上下文。
4. nanobot/providers/registry.py (极简的模型注册表)
- 作用:管理几十种 LLM 厂商的路由。
- 亮点:这是一个非常优雅的设计。不同于写一长串的
if provider == 'openai': ... elif ...,作者使用了声明式的ProviderSpec数据类。只需要定义name,litellm_prefix,env_key,系统就能自动处理环境变量注入、模型名称前缀补全、本地/网关判断等逻辑。
5. nanobot/agent/context.py (上下文构建器)
- 作用:负责把杂乱的信息“拼装”成 LLM 能懂的 Prompt。
- 机制:它会将系统的基础设定、运行时环境信息(系统类型、时间)、长期记忆 (
MEMORY.md)、用户自定义行为准则 (USER.md,SOUL.md) 以及激活的技能 (skills/) 拼接成一个超级system_prompt。
四、 数据流向分析 (Data Flow Lifecycle)
以用户在 Telegram 上对机器人说:“帮我搜索一下最近的 AI 新闻”为例,完整的数据流向如下:
阶段 1:接收消息 (Inbound)
TelegramChannel的长轮询捕捉到消息。- 经过
is_allowed()鉴权校验通过。 - 封装为标准化对象
InboundMessage(channel="telegram", chat_id="123", content="帮我搜索...")。 - 调用
bus.publish_inbound(msg)将消息推入总线。
阶段 2:预处理与思考 (Processing & Loop)
AgentLoop.run()一直在阻塞等待bus.consume_inbound(),拿到消息。SessionManager根据telegram:123提取该用户的历史对话。ContextBuilder组装 Prompt,生成messages列表。- 进入
_run_agent_loop:LLMProvider将消息发给模型(如 Claude 或 GPT-4o)。- 模型判定需要搜索,返回 Tool Call:
web_search(query="latest AI news")。
阶段 3:工具执行与进度反馈 (Tool Execution & Progress)
AgentLoop拦截到 Tool Call,通过ToolRegistry调用WebSearchTool.execute()。- (可选) 如果配置允许,
AgentLoop会向MessageBus发布一条元数据带_progress=True和_tool_hint=True的状态消息(如:web_search("latest AI news")),前端 Channel 收到后会显示“机器人正在搜索...”。 WebSearchTool调用外部 API 返回搜索结果。AgentLoop将结果封装为{"role": "tool", "content": "..."}附加到消息列表,进行第二轮 LLM 调用。- 模型阅读搜索结果,生成最终总结文本。
阶段 4:回复与收尾 (Outbound & Consolidation)
AgentLoop拿到最终文本,构建OutboundMessage推入总线。ChannelManager._dispatch_outbound()从队列拿到消息,识别出channel="telegram"。- 调用
TelegramChannel.send(),最终用户在手机上看到回复。 AgentLoop将这一轮对话存入Session。- 触发
maybe_consolidate_by_tokens检查,如果历史太长,后台静默启动压缩流程。
五、 总结与评价
nanobot 展现了极高的工程素养,其架构设计有以下几个显著优点:
- 极致解耦:通过
MessageBus,Channel 和 Agent Core 互不感知。这意味着接入一个新平台(比如飞书或 Slack),完全不需要修改核心 Agent 逻辑,只需要实现start(),stop(),send()三个方法即可(参考CHANNEL_PLUGIN_GUIDE.md)。 - 轻量但完备:它剔除了 LangChain 等重型框架的冗余抽象,直接用原生的字典列表
[{"role": "...", "content": "..."}]和LiteLLM进行交互。这种“去层级化”让代码可读性极高,易于二次开发。 - 原生现代能力:内置了 MCP 支持(允许接入庞大的外部工具生态)和 Subagent 并发孵化机制,使其不仅仅是一个“聊天机器人”,而是一个真正的“全栈 AI 工作流引擎”。
- 健壮性:
AgentLoop中使用了asyncio.Lock防止并发写入同一 Session 导致上下文错乱;LLM 调用使用了重试退避机制(chat_with_retry)应对厂商 API 的 429 和 502 错误。
总的来说,这是一个非常适合研究、学习以及作为个人/团队私有化部署底座的 Modern AI Agent 框架。
语音消息是如何处理的?
在 nanobot 中,企业微信(WeCom)频道的接入使用了官方推荐的 WebSocket 长连接模式(基于 wecom_aibot_sdk),这意味着不需要公网 IP,也不需要配置复杂的 Webhook 接收服务器。
企业微信消息的完整数据流向可以分为三个主要阶段:接收阶段(Inbound)、代理处理阶段(Agent Processing)和发送/回复阶段(Outbound)。
以下是详细的数据流向解析:
第一阶段:接收与预处理(Inbound Data Flow)
- 建立长连接:
在WecomChannel.start()中,系统会使用配置文件中的bot_id和secret初始化一个WSClient,并与企业微信服务器建立 WebSocket 长连接,持续监听事件。 - 事件触发与分发:
当用户在企业微信端发送消息时,WebSocket 会收到帧数据。根据消息类型,SDK 会触发对应的回调函数,例如_on_text_message(文本)、_on_image_message(图片)、_on_voice_message(语音)等。 - 消息标准化与解析 (
_process_message):- 提取标识:解析出
msgid(消息ID)、userid(发送者)、chatid(会话/群组ID)和chattype(单聊/群聊)。 - 消息去重:将
msgid存入_processed_message_ids缓存(LRU/OrderedDict 机制,最大 1000 条),防止因网络抖动导致的企业微信服务器重发重复处理。 - 多模态提取:
- 如果是文本,直接提取内容。
- 如果是图片/文件,提取
url和aeskey,调用_download_and_save_media将文件下载到本地,然后转换为[image: xxx.jpg]的格式。 - 如果是语音,直接提取企业微信自带的语音转文字结果(
voice.content)。
- 暂存底层 Frame:⭐ 非常关键的一步。系统会将原始的 WebSocket 数据帧存入
self._chat_frames[chat_id] = frame。这是因为企业微信的 WebSocket 回复机制要求带着原始的请求帧结构去响应。
- 提取标识:解析出
- 推入总线:
调用基类的_handle_message方法,进行allow_from白名单权限校验。校验通过后,将消息封装为标准的InboundMessage对象,调用await self.bus.publish_inbound(msg)推入系统的统一内存消息队列中。
第二阶段:大脑处理(Agent Core Processing)
这部分与具体的 Channel 解耦,所有平台的流向都是一致的:
AgentLoop异步监控InboundMessage队列。- 取出企业微信发来的消息,根据
session_key(如wecom:userid)加载历史上下文。 - 结合系统提示词(System Prompt)、可用工具(Tools)将用户意图发送给 LLM(如 DeepSeek/Claude 等)。
- 如果 LLM 决定调用工具(例如搜索网页、查天气),则执行工具并再次发给 LLM。
- LLM 生成最终回复文本。
- Agent 将最终回复封装成
OutboundMessage(channel="wecom", chat_id=...)对象,并推入Outbound发送队列。
第三阶段:回复发送(Outbound Data Flow)
- 路由分发:
ChannelManager监控Outbound队列,发现这条消息的channel是"wecom",于是把它派发给WecomChannel.send(msg)。 - 提取上下文 Frame:
在WecomChannel.send中,机器人首先通过msg.chat_id从之前暂存的字典中取出原始数据帧:frame = self._chat_frames.get(msg.chat_id)。 - 构造回复参数并发送:
- 生成一个流式回复的请求 ID:
stream_id = self._generate_req_id("stream")。 - 调用
self._client.reply_stream(frame, stream_id, content, finish=True)。
- 生成一个流式回复的请求 ID:
- 触达用户:
wecom_aibot_sdk会通过已经建立好的 WebSocket 通道,把包含回复内容的数据帧发送回企业微信服务器。企业微信服务器最终将消息渲染到用户的客户端(PC/手机)上。
💡 企业微信数据流的核心特点(Highlight)
- 安全与解密:企业微信所有的多媒体文件(图片、文件)传输都是加密的。在下载阶段,
WecomChannel必须同时使用提取到的url和aeskey才能成功解密并保存到机器人的本地空间。 - 无状态响应设计:因为采用了底层帧(Frame)响应模型(必须持有用户提问时的 Frame 才能通过 WS 返回答案),目前
nanobot的企微实现将 Frame 暂存在内存字典中。这意味着它主要适用于标准的“一问一答”或者“多问一答”模式;如果是定时任务(Cron)主动发起向用户的推送,在这个具体实现下会有局限(因为没有活跃的提问 Frame 触发它)。 - 多模态直接集成:巧妙利用了企业微信自带的语音转文本能力,当收到
message.voice时,不依赖外部 Whisper 模型,直接提取企微已经转换好的voice_content传递给 LLM。