0.引言
最近找工作真实感受到了后端已死。。。遂打算转向Agent开发方向,准备多学习一些Agent相关的内容,今天准备从Nanobot的源码开始阅读,借助AI阅读项目,更好的理解Agent的流程。
Nanobot项目地址:nanobot
1.项目概述与架构介绍
在阅读一个项目之前,我们需要先知道这个项目是做什么的,以及其整体架构。Nanobot 是一个轻量级个人 AI 助手框架,其核心特点包括:
- 支持多通道接入(Telegram、Discord、Slack、飞书、钉钉、微信等 15+ 平台)
- 支持多 LLM Provider(OpenAI、Anthropic、Azure、GitHub Copilot 等)
- 实现 AI Agent 工具调用循环(Tool Use Loop)
- 具备记忆系统和会话管理
下图是一个简易的项目架构图,该项目一共分为5层,从上到下分别为:
- UI层:支持多通道,包括飞书、微信、Telegram等;
- Gateway层:消息网关,负责收发消息;
- Core Agent:Agent核心部分,Agent Loop中又包含了上下文处理、记忆模块、子Agent等;
- LLM Provider:大模型接口,支持国内外多种大模型,包括Chat GPT、Deepseek等。
- Tool Use :工具层,负责工具注册与执行。

2.核心模块详解
2.1 Agent Loop(智能体循环)------核心推理引擎
AgentLoop 是 Nanobot 的"心脏",它负责:
- 从 MessageBus 的 Inbound Queue 消费消息
- 管理会话级别的串行执行(会话锁)
- 控制全局并发数(并发闸门)
- 创建 AgentRunner 来处理每个消息
- 保存每轮对话(_save_turn)
bash
┌─────────────────────────────────────────────────────────┐
│ AgentLoop 工作机制 │
│ │
│ MessageBus (Inbound Queue) │
│ ┌──────────────────────────────┐ │
│ │ msg1 │ msg2 │ msg3 │ ... │ │
│ └──┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ run() ─── 持续消费消息 │
│ │ │
│ ├── 获取会话锁 (_session_locks) │
│ │ └── 确保同一会话的消息串行处理 │
│ │ │
│ ├── 获取并发闸门 (_concurrency_gate) │
│ │ └── 默认最多 3 个并发会话 │
│ │ │
│ ├── 创建 AgentRunner │
│ │ └── 传入 provider, workspace, tools 等 │
│ │ │
│ ├── AgentRunner.run() │
│ │ └── ReAct 循环 │
│ │ │
│ ├── _save_turn() 持久化 │
│ │ └── 保存到 HISTORY.md │
│ │ │
│ └── 发送响应到 Outbound Queue │
│ │
└─────────────────────────────────────────────────────────┘
2.2 AgentRunner(ReAct循环)------迭代执行器
AgentRunner 实现了完整的 ReAct 循环,具体流程如下:
bash
┌─────────────────────────────────────────────────────────┐
│ AgentRunner 执行流程 │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ for i in range(max_iterations): │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ before_iteration() │ ← Lifecycle Hook │ │
│ │ └──────────┬───────────┘ │ │
│ │ ↓ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ provider.chat_with_ │ │ │
│ │ │ retry(messages,tools)│ ← 调用 LLM │ │
│ │ └──────────┬───────────┘ │ │
│ │ ↓ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ 有 tool_calls? │ │ │
│ │ └──────┬──────┬────────┘ │ │
│ │ Yes │ │ No │ │
│ │ ↓ ↓ │ │
│ │ ┌──────────┐ ┌────────────┐ │ │
│ │ │before_ │ │ 任务完成 │ │ │
│ │ │execute_ │ │ break 退出 │ │ │
│ │ │tools() │ └────────────┘ │ │
│ │ └────┬─────┘ │ │
│ │ ↓ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ ToolRegistry.execute │ │ │
│ │ │ (逐一执行工具调用) │ │ │
│ │ └──────────┬───────────┘ │ │
│ │ ↓ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ 截断工具结果 │ │ │
│ │ │ (>16000字符时截断) │ │ │
│ │ └──────────┬───────────┘ │ │
│ │ ↓ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ after_iteration() │ ← Lifecycle Hook │ │
│ │ └──────────┬───────────┘ │ │
│ │ │ │ │
│ │ └── 继续循环 ──────────→ 下一轮 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
2.3 MemoryStore(记忆系统)
MemoryStore 管理 Nanobot 的双层记忆系统。
bash
┌─────────────────────────────────────────────────────────┐
│ 记忆系统架构 │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ MemoryStore │ │
│ │ │ │
│ │ ┌────────────────┐ ┌────────────────┐ │ │
│ │ │ read_memory() │ │ save_memory() │ │ │
│ │ │ 读取MEMORY.md │ │ 写入MEMORY.md │ │ │
│ │ └────────────────┘ └────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────┐ ┌──────────────────┐ │ │
│ │ │ append_history()│ │MemoryConsolidator│ │ │
│ │ │ 追加HISTORY.md │ │ 压缩旧历史记录 │ │
│ │ └────────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ MEMORY.md │ │ HISTORY.jsonl │ │
│ │ │ │ │ │
│ │ 结构化长期记忆 │ │ 完整交互历史 │ │
│ │ │ │ │ │
│ │ ·用户偏好 │ │ ·时间戳 │ │
│ │ ·项目信息 │ │ ·用户消息摘要 │ │
│ │ ·重要决策 │ │ ·Agent回复摘要 │ │
│ │ ·学到的教训 │ │ ·工具调用记录 │ │
│ │ │ │ │ │
│ │ 由 save_memory │ │ 自动追加 │ │
│ │ 虚拟工具触发写入 │ │ 定期压缩 │ │
│ └────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
2.4 MessageBus(消息总线)------双队列架构
MessageBus 是 Nanobot 的消息中枢,使用经典的生产者-消费者模式。
bash
┌─────────────────────────────────────────────────────────┐
│ MessageBus 架构 │
│ │
│ 生产者 (Channels) 消费者 (AgentLoop) │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Telegram │──┐ │ │ │
│ │ Channel │ │ ┌──────────┐ │ AgentLoop │ │
│ └──────────┘ ├─→│ Inbound │─→│ .run() │ │
│ ┌──────────┐ │ │ Queue │ │ │ │
│ │ Discord │──┤ └──────────┘ └──────┬───────┘ │
│ │ Channel │ │ │ │
│ └──────────┘ │ │ │
│ ┌──────────┐ │ ↓ │
│ │ 飞书 │──┘ ┌──────────────┐ │
│ │ Channel │ │ AgentRunner │ │
│ └──────────┘ │ (处理消息) │ │
│ ↑ └──────┬───────┘ │
│ │ │ │
│ │ ┌──────────┐ │ │
│ └───────│ Outbound │←────────────┘ │
│ │ Queue │ │
│ └──────────┘ │
│ │
│ 消息类型: │
│ · InboundMessage = 用户发来的消息 │
│ · OutboundMessage = Agent 要回复的消息 │
│ │
└─────────────────────────────────────────────────────────┘
3.数据流图
完整消息处理流程
bash
步骤 1: 用户发送消息
┌──────────────────────────────────────────────────────────┐
│ 用户在 Telegram 发送: "帮我查一下北京天气" │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 2: Channel 接收并转换
┌──────────────────────────────────────────────────────────┐
│ TelegramChannel.on_message() │
│ → 将 Telegram 消息转换为 InboundMessage │
│ → InboundMessage { │
│ session_id: "tg_12345", │
│ user_id: "user_001", │
│ text: "帮我查一下北京天气", │
│ channel: "telegram", │
│ timestamp: 1711958400 │
│ } │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 3: 进入 Inbound Queue
┌──────────────────────────────────────────────────────────┐
│ MessageBus.inbound.put(inbound_message) │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 4: AgentLoop 消费消息
┌──────────────────────────────────────────────────────────┐
│ AgentLoop.run(): │
│ → msg = await bus.inbound.get() │
│ → 获取会话锁: _session_locks["tg_12345"] │
│ → 获取并发闸门: _concurrency_gate.acquire() │
│ → 加载 workspace 上下文 │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 5: ContextBuilder 构建 Prompt
┌──────────────────────────────────────────────────────────┐
│ ContextBuilder.build_system_prompt(): │
│ → Identity: "你是一个 AI 助手..." │
│ → Bootstrap: 基础行为规范 │
│ → Memory: 读取 MEMORY.md 的内容 │
│ → Skills: 加载匹配的 SKILL.md │
│ │
│ ContextBuilder.build_messages(): │
│ → [system_prompt] + [history] + [user_message] │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 6: AgentRunner ReAct 循环
┌──────────────────────────────────────────────────────────┐
│ 第 1 轮迭代: │
│ → LLM: "用户想查北京天气,我需要调用天气工具" │
│ → tool_call: weather_api(city="北京") │
│ → 工具返回: {"temp": 22, "weather": "晴"} │
│ │
│ 第 2 轮迭代: │
│ → LLM: "我已经得到天气数据,可以回复用户了" │
│ → text: "北京今天天气晴朗,气温 22°C,适合外出。" │
│ → 没有 tool_calls,循环结束 │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 7: 保存记忆和历史
┌──────────────────────────────────────────────────────────┐
│ MemoryStore.append_history(): │
│ → 将本轮对话摘要追加到 HISTORY.md │
│ │
│ AgentLoop._save_turn(): │
│ → 持久化本轮对话数据 │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 8: 发送响应
┌──────────────────────────────────────────────────────────┐
│ MessageBus.outbound.put(outbound_message) │
│ → OutboundMessage { │
│ session_id: "tg_12345", │
│ text: "北京今天天气晴朗,气温 22°C,适合外出。", │
│ channel: "telegram" │
│ } │
└──────────────────────────┬───────────────────────────────┘
↓
步骤 9: Channel 发送回复
┌──────────────────────────────────────────────────────────┐
│ TelegramChannel.send(): │
│ → 将 OutboundMessage 转换为 Telegram API 请求 │
│ → 发送到 Telegram 服务器 │
│ → 用户在 Telegram 收到回复 │
└──────────────────────────────────────────────────────────┘