1.2 源码结构图解

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

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 采用了一种经典的 分层架构,确保了各个模块的高内聚和低耦合。

Infrastructure

Agent Core (核心引擎)

Message Bus (异步解耦)

用户

Access Layer: Channels (飞书/微信/CLI)

Inbound Queue

Outbound Queue

Agent Loop
(ReAct 循环)

Context Builder

Memory System

Skill Registry

LLM Provider

File System

架构分层解析

  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,而是直接使用数据。

代码示意:

# 定义一个并不存在的工具,仅用于约束输出格式
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 派发回对应的渠道。

AgentBusFeishuChannelUserAgentBusFeishuChannelUserChannel 此时可以继续处理其他请求,无需等待loop[异步监听]loop[异步监听]发送消息 "你好"put(InboundMessage)get(InboundQueue)收到消息思考 (ReAct Loop)put(OutboundMessage)get(OutboundQueue)获取回复回复消息

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 消耗和记忆持久性之间找到了平衡。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐