OpenClaw Nanobot 架构拆解:从源码学会 AI Agent 的骨架设计(2026)

上周五在折腾一个自动化代码审查的 Agent,写到一半发现自己在重复造轮子------任务调度、工具注册、上下文管理,每个都自己糊一遍,代码丑得我自己都不想看。正好那几天 OpenClaw 冲到 GitHub 20 万 Star,热榜上全是它的入门教程,就想着与其瞎搞,不如把 OpenClaw 的 Nanobot 架构拆一遍,看看人家怎么设计的。

花了两天啃源码。说实话,OpenClaw 的 Nanobot 架构是我 2026 年见过的最干净的 Agent 设计模式------轻量、可组合、Skills 热插拔。这篇是我的学习笔记,把核心架构、关键代码、踩的坑全记下来,顺便用 Claude 4.6 跑一个完整的 Nanobot 示例。

先说结论

维度 说明
Nanobot 是什么 OpenClaw 的最小 Agent 执行单元,一个 Nanobot = 一个 System Prompt + 一组 Skills + 一个模型绑定
核心设计思想 单一职责 + 组合模式,多个 Nanobot 可以编排成复杂 Agent
Skills 是什么 类似 Function Calling 的工具注册机制,支持热插拔和版本管理
适合谁学 想自己搭 Agent 框架的开发者,或者想深入理解 OpenClaw 而不只是当用户的人
学习成本 有 Python 基础 + 了解 Function Calling 概念,2-3 小时能跑通

Nanobot 架构全景图

先上一张我梳理的架构图,后面所有内容围绕这张图展开:

graph TB User[用户输入] --> Orchestrator[编排器 Orchestrator] Orchestrator --> NB1[Nanobot: 代码分析] Orchestrator --> NB2[Nanobot: 文档生成] Orchestrator --> NB3[Nanobot: 测试编写] NB1 --> SK1[Skill: read_file] NB1 --> SK2[Skill: ast_parse] NB1 --> LLM[LLM API] NB2 --> SK3[Skill: markdown_render] NB2 --> LLM NB3 --> SK4[Skill: run_pytest] NB3 --> SK1 NB3 --> LLM LLM --> Provider[API Provider] Provider --> GPT5[GPT-5] Provider --> Claude[Claude 4.6] Provider --> GLM[GLM-5]

三层结构:编排器负责调度,Nanobot 负责执行,Skills 负责干活。模型调用是 Nanobot 的内部行为,外部只关心输入输出。

环境准备

bash 复制代码
# Python 3.11+
pip install openclaw>=0.9.0
pip install openai # OpenClaw 底层用 OpenAI 兼容协议调模型

OpenClaw 的模型调用层走 OpenAI 兼容协议,任何兼容 OpenAI API 格式的服务都能接。这一点后面会用到。

第一步:理解 Nanobot 的最小结构

一个 Nanobot 的定义文件长这样(YAML 格式):

yaml 复制代码
# nanobot.yaml
name: code_reviewer
version: "1.0"
description: "审查 Python 代码质量并给出改进建议"

system_prompt: |
 你是一个资深 Python 代码审查员。
 审查重点:代码可读性、潜在 bug、性能问题。
 输出格式:按严重程度排序,每条包含行号、问题描述、修复建议。

model:
 provider: openai_compatible
 model_name: claude-sonnet-4-20250514
 temperature: 0.3

skills:
 - read_file
 - ast_parse
 - search_codebase

就这些。没有复杂的继承关系,没有抽象工厂,纯声明式配置。我第一次看到的时候想:就这?

但仔细想想这个设计确实聪明------把 Agent 的行为完全用配置描述,运行时由框架装配。换模型改一行 model_name,加工具在 skills 列表追加一个。

第二步:用 Python 手撸一个 Nanobot

光看 YAML 没意思,从 Python 代码层面理解 Nanobot 的运行机制。

OpenClaw 的核心类是 Nanobot,我把源码里的关键逻辑简化了一版:

python 复制代码
from openai import OpenAI
import json

class Nanobot:
 """OpenClaw Nanobot 核心逻辑简化版"""
 
 def __init__(self, name: str, system_prompt: str, model: str, skills: list = None):
 self.name = name
 self.system_prompt = system_prompt
 self.model = model
 self.skills = skills or []
 self.skill_registry = {}
 
 # 初始化 LLM 客户端
 self.client = OpenAI(
 api_key="your-key",
 base_url="https://api.ofox.ai/v1" # 聚合接口,一个 Key 调所有模型
 )
 
 # 注册 skills
 for skill in self.skills:
 self._register_skill(skill)
 
 def _register_skill(self, skill_func):
 """把 Python 函数注册为 Skill(本质是 Function Calling 的 tool)"""
 tool_def = {
 "type": "function",
 "function": {
 "name": skill_func.__name__,
 "description": skill_func.__doc__ or "",
 "parameters": getattr(skill_func, '_parameters', {"type": "object", "properties": {}})
 }
 }
 self.skill_registry[skill_func.__name__] = {
 "definition": tool_def,
 "handler": skill_func
 }
 
 def run(self, user_input: str, max_rounds: int = 5) -> str:
 """执行 Nanobot 的核心循环:LLM 推理 → 调用 Skill → 再推理"""
 messages = [
 {"role": "system", "content": self.system_prompt},
 {"role": "user", "content": user_input}
 ]
 tools = [s["definition"] for s in self.skill_registry.values()]
 
 for round_num in range(max_rounds):
 response = self.client.chat.completions.create(
 model=self.model,
 messages=messages,
 tools=tools if tools else None,
 tool_choice="auto" if tools else None
 )
 
 msg = response.choices[0].message
 messages.append(msg)
 
 # 如果模型没有调用工具,说明推理结束
 if not msg.tool_calls:
 return msg.content
 
 # 执行所有 tool calls
 for tool_call in msg.tool_calls:
 func_name = tool_call.function.name
 func_args = json.loads(tool_call.function.arguments)
 
 handler = self.skill_registry[func_name]["handler"]
 result = handler(**func_args)
 
 messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": str(result)
 })
 
 print(f" [Round {round_num+1}] Skill 调用: {func_name}({func_args}) → {str(result)[:100]}...")
 
 return messages[-1].content if messages else "达到最大轮次"

这段代码就是 Nanobot 的核心------一个带 tool 循环的 Chat Completion 调用。看起来简单,但 OpenClaw 在这个基础上加了几个关键设计:

  1. Skill 版本管理:同一个 Skill 可以有多个版本,Nanobot 可以锁定特定版本
  2. 上下文窗口管理:自动 truncate 超长对话,保留 system prompt 和最近 N 轮
  3. 错误重试:Skill 执行失败会把错误信息喂回模型,让它自己修正

第三步:定义 Skills 并跑起来

Skills 就是普通的 Python 函数,加点元信息装饰就行:

python 复制代码
import os
import ast

def skill(parameters: dict):
 """装饰器:给函数附加 JSON Schema 参数定义"""
 def decorator(func):
 func._parameters = parameters
 return func
 return decorator

@skill(parameters={
 "type": "object",
 "properties": {
 "file_path": {"type": "string", "description": "要读取的文件路径"}
 },
 "required": ["file_path"]
})
def read_file(file_path: str) -> str:
 """读取指定路径的文件内容"""
 if not os.path.exists(file_path):
 return f"错误:文件 {file_path} 不存在"
 with open(file_path, 'r', encoding='utf-8') as f:
 content = f.read()
 # 限制返回长度,防止撑爆上下文
 if len(content) > 10000:
 return content[:10000] + f"\n...[文件过长,已截断,总长度 {len(content)} 字符]"
 return content

@skill(parameters={
 "type": "object",
 "properties": {
 "code": {"type": "string", "description": "要解析的 Python 代码"}
 },
 "required": ["code"]
})
def ast_parse(code: str) -> str:
 """解析 Python 代码的 AST 结构,返回函数和类的列表"""
 try:
 tree = ast.parse(code)
 result = []
 for node in ast.walk(tree):
 if isinstance(node, ast.FunctionDef):
 args = [a.arg for a in node.args.args]
 result.append(f"函数: {node.name}({', '.join(args)}) @ 行{node.lineno}")
 elif isinstance(node, ast.ClassDef):
 result.append(f"类: {node.name} @ 行{node.lineno}")
 return "\n".join(result) if result else "未发现函数或类定义"
 except SyntaxError as e:
 return f"语法错误:{e}"


# 组装 Nanobot 并运行
reviewer = Nanobot(
 name="code_reviewer",
 system_prompt="""你是一个资深 Python 代码审查员。
审查流程:
1. 先用 read_file 读取目标文件
2. 用 ast_parse 分析代码结构
3. 基于分析结果给出审查意见
输出格式:按严重程度排序,每条包含行号、问题描述、修复建议。""",
 model="claude-sonnet-4-20250514",
 skills=[read_file, ast_parse]
)

# 跑一下
result = reviewer.run("请审查 ./src/utils.py 这个文件的代码质量")
print(result)

跑起来的输出大概是这样:

css 复制代码
 [Round 1] Skill 调用: read_file({"file_path": "./src/utils.py"}) → import os\nimport sys\n\ndef ...
 [Round 1] Skill 调用: ast_parse({"code": "import os\nimport sys..."}) → 函数: load_config(path) @ 行5...
 [Round 2] 推理完成,返回审查结果

模型先读文件,再解析 AST,最后综合两个 Skill 的结果给出审查意见。这就是 Nanobot 的 ReAct 循环------推理、行动、观察、再推理。

第四步:多 Nanobot 编排

真正有意思的是把多个 Nanobot 组合起来。OpenClaw 的编排器(Orchestrator)支持串行、并行、条件分支三种模式:

python 复制代码
from dataclasses import dataclass
from typing import Callable
from concurrent.futures import ThreadPoolExecutor

@dataclass
class NanobotTask:
 nanobot: Nanobot
 input_transform: Callable = None # 从上一步结果提取输入

class Orchestrator:
 """简化版编排器"""
 
 def run_sequential(self, tasks: list[NanobotTask], initial_input: str) -> list[str]:
 """串行执行:上一个的输出是下一个的输入"""
 results = []
 current_input = initial_input
 for task in tasks:
 if task.input_transform:
 current_input = task.input_transform(current_input, results)
 result = task.nanobot.run(current_input)
 results.append(result)
 current_input = result
 return results
 
 def run_parallel(self, tasks: list[NanobotTask], shared_input: str) -> list[str]:
 """并行执行:所有 Nanobot 拿到同一个输入"""
 with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
 futures = [executor.submit(t.nanobot.run, shared_input) for t in tasks]
 return [f.result() for f in futures]


# 实际使用:代码审查 → 生成修复方案 → 编写测试
orchestrator = Orchestrator()

review_bot = Nanobot(name="reviewer", system_prompt="审查代码...", model="claude-sonnet-4-20250514", skills=[read_file, ast_parse])
fix_bot = Nanobot(name="fixer", system_prompt="根据审查意见生成修复代码...", model="claude-sonnet-4-20250514", skills=[read_file])
test_bot = Nanobot(name="tester", system_prompt="为修复后的代码编写 pytest 测试...", model="claude-sonnet-4-20250514", skills=[])

results = orchestrator.run_sequential(
 tasks=[
 NanobotTask(nanobot=review_bot),
 NanobotTask(nanobot=fix_bot),
 NanobotTask(nanobot=test_bot),
 ],
 initial_input="审查并修复 ./src/utils.py"
)

三个 Nanobot 串起来就是一个完整的代码审查 + 修复 + 测试 pipeline。每个 Nanobot 只管自己那一步,职责清晰。

踩坑记录

两天踩了不少坑,记几个印象深的。

坑 1:Skill 返回值太长直接撑爆上下文

一开始没做 read_file 的截断,读了一个 3 万行的文件,直接超了 Claude 4.6 的上下文窗口。报错信息还挺模糊的,排查了半小时才定位到。解决方案就是上面代码里的截断逻辑。另外 OpenClaw 源码里有个 context_window_manager 模块专门处理这个,建议直接用它的。

坑 2:并行执行时的 Rate Limit

三个 Nanobot 并行跑,瞬间打了三个请求,直接触发 429。这个不是 OpenClaw 的问题,是 API 那边的限流。后来换了 ofox.ai 的聚合接口,底层做了多供应商负载均衡,并发能力好很多,三个并行请求没再被限流过。ofox.ai 是一个 AI 模型聚合平台,支持 GPT-5、Claude 4.6、GLM-5 等 50+ 模型,低延迟直连,改个 base_url 就能用。

坑 3:tool_calls 的 arguments 偶尔不是合法 JSON

某些模型(尤其是小模型)返回的 function.arguments 偶尔会带多余的逗号或者缺引号。OpenClaw 源码里用了一个 repair_json 的工具函数做容错,这个思路值得学。自己写的话可以用 json-repair 这个库。

坑 4:system_prompt 里不写清楚调用顺序,模型会乱来

一开始 system_prompt 只写了「你可以用 read_file 和 ast_parse」,结果模型有时候不读文件就直接开始分析,纯靠幻觉编。后来改成明确写「第一步先用 read_file,第二步用 ast_parse,第三步再给出意见」,效果好了很多。Agent 的 prompt 必须写执行流程,光列工具没用。

小结

Nanobot 架构本质不复杂,核心就三个东西:

  • Nanobot = System Prompt + Model + Skills,声明式定义,运行时装配
  • Skills = Function Calling 的封装,加了版本管理和错误重试
  • Orchestrator = 多 Nanobot 编排,支持串行/并行/条件分支

看完源码之后觉得自己之前造的轮子也不是完全白费------思路是对的,只是没有 OpenClaw 这么工程化。如果你也在搞 Agent 开发,建议花半天读一遍 OpenClaw 的 nanobot/core.pyskills/registry.py 这两个文件,比看十篇教程有用。

下一篇打算写 OpenClaw 的 Skills 生态怎么玩,怎么自己发布一个 Skill 包到社区。有兴趣的可以先 star 一下,更新了不迷路。

相关推荐
圣殿骑士-Khtangc4 小时前
JetBrains AI Assistant 超全使用教程|从安装到实战,解锁 AI 编程高效体验
ai编程
s1mple“”4 小时前
互联网大厂Java面试实录:谢飞机的AIGC求职之旅 - JVM并发编程到Spring Cloud微服务
spring boot·aigc·微服务架构·java面试·分布式系统·rag技术·redis数据库
小虎AI生活4 小时前
用 WorkBuddy 多角色,我帮团队省下了 50%的重复劳动
ai编程
西陵4 小时前
别再写 Prompt 了Spec Mode 才是下一代 AI 编程范式
前端·人工智能·ai编程
小溪彼岸5 小时前
重新认识10年未被重视的Git原生功能:Git Worktree
aigc
ascarl20105 小时前
Ai路由--如何运行 9Router
ai·ai编程
AI攻城狮5 小时前
OpenClaw 本地内存检索与 node-llama-cpp 的依赖关系深度解析
人工智能·云原生·aigc
Awu12275 小时前
⚡精通Claude第3课:学会用Skills让Claude变身为专属专家
aigc·ai编程·claude