1. Nanobot 框架概述:轻量级 AI Agent 的工程实践
Nanobot 是一个基于 Python asyncio 的轻量级 AI Agent 框架,其核心设计理念是在保持代码精简的同时实现生产环境所需的全套功能。这个框架最令人印象深刻的特点是:在仅约 8000 行 Python 代码中,它完整实现了 ReAct 推理循环、持久记忆系统、多渠道通信、工具调用、子 Agent 派生和定时任务等现代 AI Agent 的核心能力。
作为一个长期从事 AI 系统开发的工程师,我特别欣赏 Nanobot 的"轻量但不简陋"的设计哲学。很多所谓的轻量级框架往往在功能完整性上做出妥协,而 Nanobot 却通过精心的架构设计,在代码规模和功能完备性之间取得了很好的平衡。这让我想起了早期 Linux 内核的设计理念——用最少的代码做最多的事情。
1.1 核心架构解析
Nanobot 的架构围绕消息总线(MessageBus)展开,这种设计将"消息从哪来"和"消息怎么处理"彻底解耦。具体实现上,MessageBus 就是两个简单的 asyncio.Queue:
python复制class MessageBus:
def __init__(self):
self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()
这种设计带来了几个显著优势:
- 渠道(Channel)和 Agent 完全独立,新增通信渠道无需修改 Agent 代码
- 天然支持多渠道并行处理,不同渠道的消息可以同时进入 inbound 队列
- 基于 asyncio 的异步非阻塞特性,生产者和消费者自然解耦
在实际项目中,我采用过类似的架构设计,确实能大幅降低系统各组件间的耦合度。特别是在需要支持多种通信渠道(如同时接入 Slack、微信和企业微信)的场景下,这种设计模式的优势尤为明显。
1.2 项目结构与模块划分
Nanobot 的代码组织非常清晰,体现了良好的模块化设计思想:
code复制nanobot/
├── agent/ # 核心引擎
├── bus/ # 消息总线
├── channels/ # 通信渠道适配器
├── cli/ # 命令行界面
├── config/ # 配置系统
├── cron/ # 定时任务
├── heartbeat/ # 心跳服务
├── providers/ # LLM 适配层
├── security/ # 安全模块
├── session/ # 会话管理
├── skills/ # 技能包
├── templates/ # 引导文件模板
└── utils/ # 工具函数
这种模块划分方式值得借鉴,特别是将安全相关功能集中到 security 模块的做法。在我参与过的一些 AI 项目中,安全功能往往分散在各个角落,导致后期维护和升级困难。Nanobot 的这种集中化管理方式显然更加合理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心引擎:ReAct 循环的实现细节
2.1 Agent Loop 的工作流程
Nanobot 的核心是实现了经典的 ReAct(Reason + Act)模式。这种模式让 LLM 在"思考"和"行动"之间交替迭代,直到得出最终答案。以下是主循环的核心代码逻辑:
python复制async def _run_agent_loop(self, initial_messages, on_progress=None):
messages = initial_messages
iteration = 0
while iteration < self.max_iterations: # 默认 40 次迭代上限
iteration += 1
# 调用 LLM 获取响应
response = await self.provider.chat_with_retry(
messages=messages,
tools=self.tools.get_definitions(),
model=self.model,
)
if response.has_tool_calls:
# 执行工具调用
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, tool_call.id, tool_call.name, result)
else:
# 获取最终回复
final_content = self._strip_think(response.content)
break
return final_content, tools_used, mes
