深入剖析 nanobot:轻量级 AI Agent 框架的架构之道

摘要 :nanobot 是一个极简主义的 AI Agent 框架,它用不到 4000 行代码,构建了一个包含 多端接入 (Channel)消息总线 (Bus)ReAct 循环多层记忆 (Memory) 以及 技能扩展 (Skills) 的完整系统。本文将从源码视角,剖析其核心设计理念,帮助开发者理解现代 AI Agent 的底层运作机制。

一、 项目概览

1.1 为什么关注 nanobot?

在 AI Agent 爆发的今天,像 OpenClaw 这样的大型项目虽然功能强大,但往往代码复杂,难以快速上手理解核心逻辑。nanobot 则提供了一个完美的"解剖样本"------它剥离了复杂的业务逻辑,只保留了 Agent 最核心的骨架。

理解了 nanobot,你就理解了大多数基于 ReAct 范式的 AI 助理是如何工作的。

仓库代码:https://github.com/HKUDS/nanobot

1.2 源码结构图解

nanobot 的项目结构非常扁平,核心模块一目了然:

text 复制代码
nanobot/
├── agent/                # [核心] 智能体大脑
│   ├── loop.py           #    ReAct 主循环 (引擎心脏)
│   ├── context.py        #    上下文组装 (Prompt 构建)
│   ├── memory.py         #    记忆系统 (三层存储)
│   ├── skills.py         #    技能管理
│   └── tools/            #    工具箱 (Shell, Web, File 等)
├── bus/                  # [通信] 消息总线
│   ├── queue.py          #    异步消息队列 (核心解耦机制)
│   └── events.py         #    事件定义
├── channels/             # [触角] 多平台接入
│   ├── base.py           #    标准接口定义
│   ├── manager.py        #    渠道管理器
│   └── feishu/           #    具体实现 (如飞书、微信等)
├── config/               # [配置] Pydantic 配置管理
├── session/              # [存储] 会话持久化 (JSONL)
└── cli/                  # [入口] 命令行启动器

二、 核心架构设计

nanobot 采用了一种经典的 分层架构,确保了各个模块的高内聚和低耦合。
graph TD User((用户)) <--> Channels["Access Layer: Channels (飞书/微信/CLI)"] %% 消息总线子图 subgraph "Message Bus (异步解耦)" InQueue["Inbound Queue"] OutQueue["Outbound Queue"] end Channels --> InQueue OutQueue --> Channels %% Agent核心引擎子图 subgraph "Agent Core (核心引擎)" Loop["Agent Loop<br/>(ReAct 循环)"] Context["Context Builder"] Mem["Memory System"] Skills["Skill Registry"] end InQueue --> Loop Loop --> OutQueue Loop <--> Context Context <--> Mem Loop <--> Skills %% 基础设施子图 subgraph "Infrastructure" LLM["LLM Provider"] Storage["File System"] end Loop <--> LLM Mem <--> Storage

架构分层解析

  1. 接入层 (Access):负责与外部世界交互,无论是飞书消息还是命令行输入,都统一封装为标准 Message 对象。
  2. 总线层 (Bus):全异步的消息高速公路,通过双向队列隔离了"通信"与"思考"。
  3. 核心层 (Agent Core):系统的"大脑",负责调度 LLM、管理记忆、执行工具。
  4. 基建层 (Infrastructure):提供模型能力 (Provider) 和数据持久化能力。

三、 亮点设计:虚拟工具 (Virtual Tools)

设计哲学 :如何让不可控的 LLM 稳定输出结构化数据?nanobot 给出的答案是------利用 Function Calling 协议,而不是依赖 Prompt 指令。

3.1 痛点:Prompt 的局限性

通常我们要求 LLM 输出 JSON 时,会使用如下 Prompt:

"请返回 JSON 格式,包含 action 和 reason 字段..."

但 LLM 经常会"自作聪明"地添加 Markdown 代码块,或者在 JSON 前后废话,导致解析失败。即使使用 JSON Mode,也难以严格约束字段类型(Schema)。

3.2 解决方案:幽灵工具

nanobot 引入了 "虚拟工具" 的概念。这是一种不注册到执行列表,但发送给 LLM 的工具定义

工作流程:

  1. 定义 Schema:构造一个 Function Definition,描述你想要的 JSON 结构。
  2. 欺骗 LLM:在 API 调用时传入这个 Tool,让 LLM 以为它需要调用这个函数。
  3. 截获参数 :当 LLM 返回 tool_calls 时,直接读取其 arguments 参数------这就是经过严格校验的结构化数据。
  4. 跳过执行:Agent 并不真的执行这个 Tool,而是直接使用数据。

代码示意:

python 复制代码
# 定义一个并不存在的工具,仅用于约束输出格式
VIRTUAL_TOOL_SCHEMA = [{
    "type": "function",
    "function": {
        "name": "submit_decision",
        "parameters": {
            "type": "object",
            "properties": {
                "decision": {"type": "string", "enum": ["ignore", "reply"]},
                "reason": {"type": "string"}
            },
            "required": ["decision", "reason"]
        }
    }
}]

# 调用 LLM
response = await llm.chat(messages, tools=VIRTUAL_TOOL_SCHEMA)

# 直接获取结构化结果,无需正则解析
result = response.tool_calls[0].arguments 
# result = {"decision": "reply", "reason": "User is asking for help"}

这种模式在 nanobot 的 记忆归档心跳检测 模块中被广泛使用,极大地提高了系统的稳定性。


四、 核心模块深度拆解

4.1 Message Bus:45 行代码的解耦艺术

nanobot 的总线设计极度精简,却实现了完美的异步解耦。

  • Inbound Queue:所有 Channel 接收到的消息,经过标准化封装后,扔进这个队列。
  • Outbound Queue:Agent 思考产生的回复,扔进这个队列,由 Channel Manager 派发回对应的渠道。

sequenceDiagram participant User participant FeishuChannel participant Bus participant Agent User->>FeishuChannel: 发送消息 "你好" FeishuChannel->>Bus: put(InboundMessage) Note over FeishuChannel, Agent: Channel 此时可以继续处理其他请求,无需等待 loop 异步监听 Agent->>Bus: get(InboundQueue) Bus-->>Agent: 收到消息 end Agent->>Agent: 思考 (ReAct Loop) Agent->>Bus: put(OutboundMessage) loop 异步监听 FeishuChannel->>Bus: get(OutboundQueue) Bus-->>FeishuChannel: 获取回复 end FeishuChannel->>User: 回复消息

4.2 Agent Loop:ReAct 引擎

agent/loop.py 是整个系统的主循环。它并不复杂,本质上是一个状态机:

  1. Observe (观察):获取当前上下文(Context)。
  2. Reason (推理):将上下文和工具列表发送给 LLM。
  3. Act (行动)
    • 如果 LLM 决定调用工具:执行工具 -> 获得结果 -> 将结果追加到上下文 -> 回到步骤 2
    • 如果 LLM 决定回复用户:生成最终文本 -> 结束循环。

4.3 Memory:三层记忆体系

为了解决 LLM 上下文窗口限制(Context Window)的问题,nanobot 设计了一套精巧的 三层记忆模型

第一层:Session (原始会话)

  • 形式.jsonl 文件。
  • 内容:原汁原味的对话日志,包含所有的 Tool Call 和详细参数。
  • 作用:作为"短期记忆"的物理载体,用于保持对话的连贯性。
  • 策略永不删除,但为了节省 Token,超长工具输出会被截断,图片会被替换为占位符。

第二层:History (事件日志)

  • 形式HISTORY.md
  • 内容 :带时间戳的关键事件摘要。 [2024-03-20 10:00] 用户询问了 nanobot 的部署方式。
  • 作用 :作为"中长期记忆",供 Agent 在需要时通过 grep 工具主动搜索查阅。
  • 特点只追加 (Append Only)

第三层:Memory (认知快照)

  • 形式MEMORY.md
  • 内容 :去时间化的事实性知识。 用户偏好使用 Python 语言。

    项目的部署环境是 Ubuntu 22.04。

  • 作用 :作为"长期记忆",全量注入 System Prompt,让 Agent 始终"记得"这些关键信息。
  • 特点全量覆写 (Rewrite)。每次记忆整理时,LLM 会重新生成一份完整的认知快照覆盖旧文件。

五、 总结

nanobot 并没有发明新的算法,它的高明之处在于工程实现的克制与优雅

  1. 极简的接口设计:让接入一个新的 IM 渠道变得异常简单。
  2. 巧妙的 Prompt 工程:利用虚拟工具解决了结构化输出的难题。
  3. 务实的记忆管理:通过分层存储,在 Token 消耗和记忆持久性之间找到了平衡。

对于想要从零构建 AI Agent 的开发者来说,nanobot 绝对是教科书级别的最佳实践。