01|为什么需要轻量级的 Agent 框架?
Nanobot是香港大学数据科学实验室(HKUDS)开源的一个项目,号称是 OpenClaw 的精简版实现。整个框架核心代码只有约 4000 行,比 OpenClaw 小了 99%,但功能一点都不含糊:工具调用、定时任务、记忆系统、多模型兼容、多平台支持,该有的都有。
更关键的是,这 4000 行代码写得非常清爽,几乎没有什么过度抽象,读源码就像读业务代码一样直白。对我这种喜欢折腾底层、想真正理解 Agent 是怎么运转的人来说,简直就是宝藏。
今天这篇教程,我就带你理解 Nanobot 的设计理念、核心架构,最后手把手教你从零搭建一个 Text-to-SQL 的 Agent。读完这篇文章,你不仅能跑通案例,还能理解每一行代码背后的原理,甚至能根据自己的需求改框架。
02|Nanobot 是什么?
Nanobot 的定位很明确:超轻量级个人 AI 助手框架。
它的 GitHub 地址是 https://github.com/HKUDS/nanobot,
目前已经在开源社区获得了不少关注。
核心特点
极致轻量:核心代码约 4000 行,没有臃肿的抽象层,资源占用极低,启动飞快。我自己的笔记本上,从启动到能接收第一条消息,只需要 2-3 秒。
研究友好:代码简洁易读,方便修改和扩展。如果你想研究 Agent 是怎么做工具调用、怎么管理上下文的,直接读源码就行,不需要在层层叠叠的抽象里迷路。
开箱即用:支持 pip、uv 或源码安装,一个config.json就能定义完整的 Agent(模型、工具、提供商),5 分钟就能跑起来。
多平台支持:内置 WhatsApp、Telegram、Discord、Matrix、Slack、飞书、QQ 等聊天渠道,你想在哪个平台部署都能搞定。
多模型兼容:支持 OpenRouter、本地 vLLM 等任意 OpenAI 兼容接口,也原生支持通义千问(DashScope)、DeepSeek、OpenAI、Anthropic 等 20 多个提供商。
主要功能
- 工具调用:支持 MCP 工具服务器,可执行代码、搜索网络、操作文件等
- 定时任务:内置 Cron 支持,可设置周期性提醒和自动化工作流
- 记忆系统:支持持久化记忆,长期保存重要上下文
- 多实例运行:可同时运行多个针对不同平台或不同用途的机器人实例
与其他 Agent 框架的对比
| 维度 | Nanobot | DeepAgents |
|---|---|---|
| 框架体量 | 轻量,纯 Python | 重型,依赖 LangGraph + LangChain |
| 配置方式 | config.json 声明式 | Python 代码构建 |
| Skills 机制 | 原生渐进式加载(SKILL.md 自动发现) | SkillsMiddleware 中间件注入 |
| 工具定义 | 继承Tool基类,显式注册 | @tool装饰器 + 中间件管理 |
| 上下文管理 | ContextBuilder + MemoryConsolidator | SummarizationMiddleware |
| 入口方式 | CLI (nanobot) / Python SDK | 纯 Python 脚本 |
Nanobot 的核心理念可以用一句话概括:config.json(配置) + AGENTS.md(身份) + skills/(技能) + Tool 类(工具) = 组合即 Agent。
03|核心架构揭秘:四个核心组件
Nanobot 的架构设计非常清晰,主要由四个核心组件构成:AgentLoop、ContextBuilder、ToolRegistry 和 AgentHook。理解了这四个组件,你就理解了 Nanobot 的全部工作原理。
AgentLoop —— 核心运行时
AgentLoop 是整个框架的心脏,所有逻辑的入口。它负责接收消息、构建上下文、调用 LLM、执行工具、返回响应,形成一个完整的处理闭环。
它的初始化代码非常直观:
class AgentLoop: def __init__(self, bus, provider, workspace, model, ...): self.context = ContextBuilder(workspace) # System Prompt 组装 self.tools = ToolRegistry() # 工具注册表 self.runner = AgentRunner(provider) # LLM+工具循环 self.subagents = SubagentManager(...) # 子 Agent 管理 self._register_default_tools() # 注册内置工具可以看到,AgentLoop 就像一个指挥官,把各个模块组装在一起,然后按照固定的流程运转。
ContextBuilder —— System Prompt 组装
这是 Nanobot 最精妙的设计之一。ContextBuilder 负责从多个来源组装完整的 System Prompt,让 Agent 知道自己是谁、能做什么、该怎么做。
它的组装逻辑是这样的:
class ContextBuilder: def build_system_prompt(self): parts = [self._get_identity()] # 1. 内置身份描述 parts.append(self._load_bootstrap_files()) # 2. AGENTS.md / SOUL.md parts.append(self.memory.get_memory_context()) # 3. memory/MEMORY.md parts.append(always_skills) # 4. always=true 的技能 parts.append(self.skills.build_skills_summary()) # 5. 技能 XML 摘要 return "\n\n".join(parts)这里的关键是第 5 步:ContextBuilder 只将技能的名称和描述(XML 摘要)注入 prompt,而不是把每个技能的完整内容都塞进去。当 Agent 真正需要使用某个技能时,它会通过read_file工具去读取完整的 SKILL.md 内容。
这种"渐进式加载"的设计非常聪明。想象一下,如果你有 10 个技能,每个技能 2000 字,全部塞进 prompt 就要 20000 字(约 5000 tokens),成本高不说,还容易让模型分心。而 XML 摘要只有几百字,Agent 按需读取,既省钱又高效。
ToolRegistry —— 工具注册
ToolRegistry 管理所有可用工具,提供 JSON Schema 格式的工具定义给 LLM。Nanobot 内置了以下工具:
| 工具 | 功能 |
|---|---|
| ReadFileTool | 读取文件 |
| WriteFileTool | 写入文件 |
| EditFileTool | 编辑文件 |
| ListDirTool | 列出目录 |
| ExecTool | 执行 Shell 命令 |
| WebSearchTool | 网络搜索(支持 5 种搜索引擎) |
| WebFetchTool | 获取网页内容 |
| SpawnTool | 生成子 Agent |
| MessageTool | 发送消息 |
| CronTool | 定时任务 |
| MCP Tool | MCP 协议工具桥接 |
自定义工具也很简单,继承 Tool 基类即可:
class MyCustomTool(Tool): @property defname(self) -> str: return"my_tool" @property defdescription(self) -> str: return"工具描述,告诉模型这个工具能做什么" @property defparameters(self) -> dict: return { "type": "object", "properties": { "param": {"type": "string", "description": "参数描述"} }, "required": ["param"] } asyncdefexecute(self, **kwargs) -> Any: # 执行逻辑 passAgentHook —— 生命周期钩子
AgentHook 是 Nanobot 提供的扩展机制,类似 DeepAgents 的中间件,但更轻量。它允许你在 Agent 运行周期的关键节点插入自定义逻辑:
class AgentHook: asyncdefbefore_iteration(self, ctx) # 每轮 LLM 调用前 asyncdefon_stream(self, ctx, delta) # 流式输出每个 token asyncdefbefore_execute_tools(self, ctx) # 工具执行前(可拦截/修改) asyncdefafter_iteration(self, ctx) # 每轮结束后 deffinalize_content(self, ctx, content) # 最终输出后处理每个方法默认都是空的(pass),不挂钩就不执行任何逻辑。这种"非侵入式"的设计让框架保持简洁,同时又提供了足够的扩展性。
AgentLoop 的每一轮迭代都会按顺序调用这些钩子:
(1) hook.before_iteration(ctx) # LLM 调用前(2) 调用 LLM 模型 ├─ hook.on_stream(ctx, delta) # 每个 token 流出时 └─ hook.on_stream_end(ctx) # 流式结束(3) 如果有 tool_calls: ├─ hook.before_execute_tools(ctx) # 工具执行前 ├─ 执行工具 └─ hook.after_iteration(ctx) # 本轮结束(4) 如果返回最终文本: ├─ hook.finalize_content(ctx, text) # 后处理 └─ hook.after_iteration(ctx) # 本轮结束04|Skills 渐进式加载机制
为什么需要渐进式加载?
假设你搭建了一个投研 Agent,有 8 个技能:股票查询、财报分析、新闻监控、估值计算、风险评估、组合优化、报告生成、邮件发送。每个 SKILL.md 平均 2000+ 字符,如果全部塞进 prompt,仅技能指导就要占用 16000+ 字符(约 4000 tokens),成本爆炸不说,模型还要在这些信息里找重点,容易分心。
Nanobot 的解决方案是三级加载系统:
第 1 级:元数据(永远在 context 中,约 100 words/skill)
SkillsLoader.build_skills_summary()把所有技能生成 XML 摘要注入 system prompt:
<skills> <skill available="true"> <name>web-search</name> <description>联网搜索实时市场信息</description> <location>/path/to/web-search/SKILL.md</location></skill><skill available="false"> <name>database-query</name> <description>查询本地数据库</description> <location>/path/to/database-query/SKILL.md</location></skill></skills>Agent 看到的是一个技能清单,知道自己"有什么工具可以用",但不需要知道"具体怎么用"。
第 2 级:always 技能(全文自动注入)
SKILL.md 的 frontmatter 中可以标记always: true,这类技能的完整内容会自动塞进 system prompt:
---name: memorydescription: Two-layer memory system with grep-based recall.always: true---目前内置的只有 memory 技能标记了 always: true,因为记忆系统需要随时可用。
第 3 级:按需加载(模型自主 read_file)
System prompt 中会注入提示,告诉模型需要时去 read_file:
# SkillsThe following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.当 Agent 判断需要使用某个技能时,它会主动调用read_file工具读取完整的 SKILL.md,然后按照里面的指导执行。
SKILL.md 文件格式
一个标准的 SKILL.md 长这样:
---name: web-searchdescription: "联网搜索实时市场信息,获取最新股价、行业动态等"keywords: 联网, 搜索, 最新, 实时, 行情---# web-search 技能指南## 适用场景- 查询某只股票的最新行情- 获取最新的行业政策## 工作流1. 确定搜索关键词2. 调用 web_search 工具3. 分析搜索结果4. 整理输出渐进式披露设计原则(Progressive Disclosure Design Principle)在这里体现得淋漓尽致:只给模型当前需要的信息,不多不少。
05|实战案例:从零搭建 Text-to-SQL Agent
光讲理论没意思,我们来实战一个案例:搭建一个 Text-to-SQL Agent,让用户可以用自然语言查询数据库。
项目结构
nanobot-examples/text-to-sql/├── config.json # 模型配置├── AGENTS.md # Agent 身份和规则├── agent.py # 入口 + QueryDBTool 自定义工具├── chinook.db # 示例数据库(自动下载)└── skills/ ├── schema-exploration/ # 数据库结构探索技能 └── query-writing/ # SQL 编写与错误恢复技能第一步:自定义 QueryDBTool
这是整个案例的核心。我们需要创建一个能执行只读 SQL 查询的工具:
from nanobot.agent.tools.base import ToolclassQueryDBTool(Tool): """SQL 查询工具 - 在 Chinook 数据库上执行只读 SQL""" def__init__(self, db_path: Path): self._db_path = db_path @property defname(self) -> str: return"query_db" @property defdescription(self) -> str: return ( "Execute a read-only SQL query against the Chinook database. " "Returns query results as formatted text. Only SELECT and " "PRAGMA statements are allowed." ) @property defparameters(self) -> dict[str, Any]: return { "type": "object", "properties": { "sql": { "type": "string", "description": "The SQL query to execute (SELECT only)" } }, "required": ["sql"] } @property defread_only(self) -> bool: returnTrue# 标记为只读,可安全并行执行 asyncdefexecute(self, **kwargs: Any) -> str: sql = kwargs.get("sql", "").strip() ifnot sql: return"Error: empty SQL query" # 安全校验:只允许 SELECT 和 PRAGMA upper = sql.upper().lstrip() ifnot (upper.startswith("SELECT") or upper.startswith("PRAGMA")): return"Error: only SELECT and PRAGMA statements are allowed" try: conn = sqlite3.connect(str(self._db_path)) cursor = conn.cursor() cursor.execute(sql) columns = [desc[0] for desc in cursor.description] if cursor.description else [] rows = cursor.fetchall() conn.close() ifnot rows: return"Query returned 0 rows." # 格式化为文本表格 lines = [" | ".join(columns)] lines.append("-" * len(lines[0])) for row in rows[:50]: # 最多返回 50 行 lines.append(" | ".join(str(v) for v in row)) iflen(rows) > 50: lines.append(f"... ({len(rows)} total rows, showing first 50)") return"\n".join(lines) except Exception as e: returnf"SQL Error: {e}"这个工具的设计有四个层次:
- 工具元数据:
name、description、parameters会被 ToolRegistry 收集,转换成 LLM function calling 的 schema 发给模型 - 安全校验:代码层面强制只允许 SELECT 和 PRAGMA,防止误操作
- 执行 SQL:连接数据库、执行查询、获取结果
- 结果限制:最多返回 50 行,防止大表查询撑爆上下文窗口
第二步:组装整个 Agent
def build_bot() -> Nanobot: """创建 Nanobot 实例,使用 config.json 配置""" bot = Nanobot.from_config( config_path=WORKSPACE / "config.json", workspace=WORKSPACE, ) # 注册自定义 SQL 查询工具 bot._loop.tools.register(QueryDBTool(DB_PATH)) return bot组装完成后,Agent 可用的工具集包括:
read_file(框架内置):读文件(加载 SKILL.md)write_file(框架内置):写文件exec(框架内置):执行 shell 命令query_db(自定义):执行只读 SQL
第三步:配置 config.json
{ "providers":{ "custom":{ "apiKey":"your-key-or-dummy", "apiBase":"https://api.your-provider.com/v1" }},"agents":{ "defaults":{ "provider":"custom", "model":"your-model-name" }}}第四步:定义 AGENTS.md
# SQL Query AgentYou are a SQL database analyst. You help users query databases using natural language.## Rules- Always explore the schema before writing queries- Use query_db tool to execute SQL; NEVER use exec for SQL- Only use SELECT statements (read-only access)- Format results clearly for the user- If a query fails, analyze the error and try a different approach这里的关键是明确约束:用query_db执行 SQL,不要用 exec。因为 exec 是通用 shell,如果模型用 exec 去跑 sqlite3,就绕过了 QueryDBTool 的安全限制。
第五步:编写 Skills
schema-exploration SKILL.md:
## Workflow1. Use `query_db(sql="SELECT name FROM sqlite_master WHERE type='table'")` to list all tables2. For each relevant table, run `query_db(sql="PRAGMA table_info(TableName)")` to see columns3. Identify primary keys, foreign keys, and relationships4. Summarize the schema for the userquery-writing SKILL.md:
## Workflow### Step 1: Understand the Question- Identify what tables and columns are needed- If unsure about schema, read the `schema-exploration` skill first### Step 2: Write the Query- Start simple, then add complexity- Use JOINs when data spans multiple tables- Use GROUP BY for aggregations- Use ORDER BY + LIMIT for "top N" questions### Step 3: Execute and Verify- Run with `query_db(sql="YOUR QUERY")`- If error: analyze the message, fix the query, retry- If results look wrong: check JOINs and WHERE clauses## Error Recovery- "no such table" -> re-list tables with schema-exploration- "no such column" -> re-check table_info- "ambiguous column" -> add table alias prefix第六步:运行效果
执行命令:
python agent.py "How many customers are from Canada?"输出:
Text-to-SQL Agent (nanobot)Question: How many customers are from Canada?=> 模型加载 schema-exploration SKILL.md=> query_db("SELECT name FROM sqlite_master WHERE type='table'")=> query_db("PRAGMA table_info(Customer)")=> 模型加载 query-writing SKILL.md=> query_db("SELECT COUNT(*) FROM Customer WHERE Country = 'Canada'")Answer: There are 8 customers from Canada in the database.exit_code: 0 | elapsed: 35s整个过程完全自主:Agent 先探索数据库结构,再编写并执行查询,最后给出答案。如果上一次运行已经缓存了 schema 信息,下次查询会直接命中,无需重新探索。
如果查询出错,比如表名大小写不对,Agent 会根据 Error Recovery 的指导自动修正。这就是 Skills 的价值:把领域知识(如何探索 schema、如何写 SQL、如何纠错)封装在 SKILL.md 里,框架负责调度,两者各司其职。
06|给开发者的建议
总结
Nanobot 的核心价值在于 “轻量但不简陋” 。4000 行代码的背后,是对 Agent 开发本质的深刻理解:
- 配置优先:一个 config.json 就能定义完整 Agent,不需要写大量胶水代码
- Skills 渐进式加载:用三级加载系统高效管理上下文,避免 token 浪费
- 显式工具注册:继承 Tool 基类的方式虽然比装饰器多写几行代码,但参数验证、只读标记等元数据一目了然
- 非侵入式扩展:AgentHook 提供生命周期钩子,不改框架源码就能插入自定义逻辑
适用场景
Nanobot 特别适合以下场景:
- 个人项目:想快速搭一个能用的 Agent,不想引入重型框架
- 学习研究:想深入理解 Agent 的工作原理,读源码不迷路
- 定制化需求:框架的某个行为不符合预期,直接改源码
- 资源受限环境:边缘设备、低配置服务器,需要轻量级方案
不适用场景
如果你需要:
- 复杂的 多 Agent 协作流程(虽然 nanobot 支持 spawn 子 Agent,但不如 LangGraph 的图编排强大)
- 企业级的监控、日志、权限管理
- 可视化的 Workflow 编排界面
那可能还是 LangChain/LangGraph 更适合你。
最后的话
我一直觉得,好的工具应该像一把顺手的瑞士军刀——功能齐全、体积小巧、开箱即用。Nanobot 就是这样一把刀。
它不是万能的,但在它擅长的领域,它做得足够好。更重要的是,当你需要调整它时,你能看懂它在做什么,而不是对着一堆抽象层发懵。
如果你也对轻量级 Agent 开发感兴趣,不妨试试 Nanobot。4000 行代码,也许就是你理解 AI Agent 的最佳入口。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~