news 2026/10/1 7:41:51

复刻一只 OpenClaw:从 Agent 到 Skills 的技术架构设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
复刻一只 OpenClaw:从 Agent 到 Skills 的技术架构设计

1. 从 OpenClaw 看 AI Native Agent 的架构分水岭

OpenClaw 这类项目最近在开发者圈子里讨论度很高,它本质上是一个能深度融入聊天软件、自主完成复杂任务的 AI Agent。很多人第一次看到它的能力清单会以为背后藏着一个庞大的工程体系,但真正拆开看,核心其实只有三件事:Agent 调度、Skills 扩展、统一 API 通道。这三件事决定了你的智能体是"能跑"还是"能长"。

我先把结论放在前面:OpenClaw 类架构的关键不在于你写了多少工具函数,而在于你有没有把"能力扩展"这件事从代码层下放到运行时。传统做法是开发者写死一个工具,AI 才能用;AI Native 的做法是给 AI 一个执行环境和一份技能描述,它自己决定怎么调、调什么、什么时候调。这个差别听起来抽象,落到代码上就是 config.toml 里一个 skills 目录的配置差异。

这篇文章面向三类人:一是想理解 OpenClaw 架构设计思路的开发者;二是正在用 Agent 框架但感觉越写越重的工程师;三是想用统一 API 通道支撑多工具协作、又不想自己维护一堆 Key 的实践者。我会给出可复制的 config.toml 骨架、TaoToken 接入配置,并演示一次完整的 Skills 注册与调用验证。你跟着做,能跑出一个最小可用的 AI Native Agent 骨架。

先说清楚一个概念区分,这是后面所有配置的基础。Tools 是框架层提供的、用 Python 或 Node.js 写死在代码库里的静态能力,每次扩展都要人类提交 PR。Skills 是纯文本描述或 AI 现场生成的动态脚本,AI 自己决定何时创建、如何修改,不需要进人类代码库。OpenClaw 的架构实验里有一个很极端的做法:不写任何业务代码,只约定一个启动协议,让 Agent 自己写 startup.sh 去拉取消息、自己决定用什么方式回复。这个思路的价值在于,它把"框架该做什么"和"AI 该做什么"的边界重新划了一遍。

那么复刻一只 OpenClaw,最小架构需要哪些模块?我的拆解是四层:第一层是 Agent Loop,负责接收输入、规划、调用、返回;第二层是 Skills Registry,负责技能的注册、发现和版本管理;第三层是统一 API 通道,负责把不同模型的请求收敛到一个入口;第四层是执行沙箱,负责给 AI 一个能跑 bash、能读写文件的受控环境。这四层里,第三层最容易被忽视,但它恰恰是多工具协作能不能跑通的关键。因为当你的 Agent 同时要调 Claude、GPT、本地模型时,如果每个模型一套 Key、一套 Base URL、一套鉴权逻辑,你的 config 会迅速膨胀到不可维护。

TaoToken 在这里的角色就是第三层的统一入口。它提供兼容 OpenAI 风格的 API 通道,你只需要一个 Key、一个 Base URL,就能在 Agent 里切换不同模型,而不需要改业务代码。下面我会从环境准备开始,一步步把配置写出来。

2. TaoToken 前置准备与统一 API 通道配置

在写 config.toml 之前,先把统一 API 通道这件事落地。很多人的 Agent 项目后期难维护,根源就是模型接入层没有抽象好。你一开始只用一个模型,Key 写在环境变量里,Base URL 写死在代码里,看起来没问题。等到你要加第二个模型做 fallback,或者要给不同 Skill 分配不同模型时,就会发现到处都要改。

TaoToken 的接入方式是把模型调用收敛到一个兼容 OpenAI 协议的端点。你需要准备的东西很少:一个 API Key,一个 Base URL,以及你要用的 Model ID。Base URL 是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 OpenAI 客户端的 base_url 使用。API Key 在控制台的 API Keys 页面创建,创建后只显示一次,记得立刻保存到环境变量或密钥管理工具里。

我建议你把 Key 放在环境变量里,而不是写进 config.toml。原因很简单:config.toml 大概率会进版本控制,Key 写进去等于泄露。正确的做法是 config.toml 里引用环境变量名,实际值通过 shell 或容器注入。下面是一个环境变量准备的示例,你可以直接复制到.env文件或 shell 配置里:

export TAOTOKEN_API_KEY="sk-你的实际Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL_ID="claude-sonnet-4-20250514"

这里 Model ID 我填的是 Claude 系列,因为 OpenClaw 类 Agent 在代码生成和长上下文规划上对模型能力要求较高。你可以根据实际场景换成其他模型,TaoToken 的通道支持在请求里指定 model 字段,所以切换模型不需要改 Base URL,只需要改 model 参数。这一点对 Agent 架构很重要:你的 Skills Registry 里可以给不同 Skill 标注不同的推荐模型,运行时由调度器决定用哪个,而底层通道始终是同一个。

接下来验证通道是否通。在写完整 Agent 之前,先用一个最小请求确认 Key 和 Base URL 没问题。用 curl 发一个 chat completions 请求:

curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "'"$TAOTOKEN_MODEL_ID"'", "messages": [{"role": "user", "content": "回复 OK 两个字母"}], "max_tokens": 16 }'

如果返回的 JSON 里有choices[0].message.content且内容是 OK,说明通道正常。如果返回 401,说明 Key 有问题;如果返回 model not found,说明 Model ID 写错了。这两个错误后面排障章节会详细讲。

这里有一个容易被忽略的点:OpenAI 兼容协议里 base_url 的写法。有些客户端要求 base_url 包含/v1,有些要求不包含。TaoToken 的 Base URL 是https://taotoken.net/api,在 OpenAI Python SDK 里这样初始化:

from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", ) resp = client.chat.completions.create( model=os.environ["TAOTOKEN_MODEL_ID"], messages=[{"role": "user", "content": "ping"}], ) print(resp.choices[0].message.content)

注意 SDK 会自动在 base_url 后面拼/v1/chat/completions,所以你不要手动在 base_url 里再加/v1,否则会变成/api/v1/v1/chat/completions,直接 404。这个坑我在多个项目里都见过,配置的时候留意一下。

统一通道配好之后,你的 Agent 就有了一个稳定的模型调用入口。接下来才是架构的主体:Agent 调度和 Skills 扩展。这两块我会用 config.toml 骨架来承载,因为配置文件比散落在代码里的常量更容易审查和迁移。

3. 可复制的 config.toml 骨架与 Skills 注册

现在进入架构主体。我设计的 config.toml 分成四个区块:agent、api、skills、sandbox。每个区块对应前面说的四层架构。先给完整骨架,再逐段解释。

# config.toml - OpenClaw 类 AI Native Agent 最小骨架 [agent] name = "openclaw-mini" max_iterations = 12 planning_model = "claude-sonnet-4-20250514" execution_model = "claude-sonnet-4-20250514" system_prompt_path = "./prompts/agent_system.md" workspace = "./workspace" [api] provider = "taotoken" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_model = "claude-sonnet-4-20250514" timeout_seconds = 120 max_retries = 3 [skills] registry_dir = "./skills" auto_reload = true allow_runtime_creation = true allowed_languages = ["python", "bash"] [skills.entries.web_search] enabled = true description = "根据关键词检索网页并返回摘要" entrypoint = "web_search.py" model_override = "" [skills.entries.file_ops] enabled = true description = "读写工作区内的文件" entrypoint = "file_ops.py" model_override = "" [sandbox] type = "local" workdir = "./workspace" allow_network = true allowed_commands = ["python", "bash", "curl", "ls", "cat", "grep"]

逐段说明。[agent]区块里,max_iterations控制 Agent Loop 的最大轮数,防止死循环烧 token。planning_model和execution_model分开配置,是因为规划阶段和工具执行阶段对模型能力要求不同,你可以给规划用强模型、执行用快模型,成本会降不少。workspace是 Agent 的工作目录,所有文件读写都限制在这个目录内。

[api]区块是统一通道的配置。provider标记为 taotoken,base_url固定为https://taotoken.net/api,api_key_env指向环境变量名而不是 Key 本身。default_model是兜底模型,当 Skill 没有指定 model_override 时用它。max_retries设 3 次,因为 Agent 场景下网络抖动比单次对话更常见,重试能显著提升成功率。

[skills]区块是 Skills Registry 的核心。registry_dir指向技能目录,auto_reload开启后,你往目录里丢新技能文件,Agent 下一轮就能发现,不需要重启。allow_runtime_creation是 AI Native 的关键开关:允许 AI 在运行时自己创建新技能脚本。这个开关打开后,AI 遇到没有现成技能的任务,会自己写一个脚本放进 registry_dir,下次同类任务就直接复用。allowed_languages限制 AI 能写什么语言的脚本,生产环境建议只开 python 和 bash,不要开任意语言。

[skills.entries.*]是显式注册的技能。每个技能有enabled、description、entrypoint、model_override四个字段。description很重要,Agent 调度时靠它判断该不该用这个技能,所以描述要写清楚输入输出。model_override留空表示用 default_model,如果某个技能需要特定模型,在这里覆盖。

[sandbox]区块定义执行环境。type = "local"表示本地执行,生产环境建议换成容器。allowed_commands是白名单,只允许列出的命令执行。这里有个安全原则:白名单要尽量窄,不要图省事写["*"],否则 AI 生成的脚本可能执行危险操作。

配置写好后,目录结构应该是这样:

openclaw-mini/ ├── config.toml ├── prompts/ │ └── agent_system.md ├── skills/ │ ├── web_search.py │ └── file_ops.py └── workspace/

agent_system.md是系统提示词,告诉 Agent 它的角色、可用技能、输出格式。一个最小版本:

你是一个 AI Native Agent。你可以使用 skills 目录下的技能完成任务。 每次需要调用技能时,输出 JSON:{"skill": "技能名", "args": {...}} 如果现有技能无法完成任务,你可以创建新技能脚本写入 skills 目录。 所有文件操作限制在 workspace 目录内。

这个提示词的关键是最后两句:允许创建新技能、限制操作范围。前者是 AI Native 的能力来源,后者是安全边界。两者缺一不可。

到这里,config.toml 骨架和目录结构就齐了。接下来演示一次完整的 Skills 注册与调用验证,把静态配置跑成动态行为。

4. 一次 Skills 注册与调用验证的完整过程

这一节我带你跑通一次从技能注册到实际调用的完整链路。目标是验证三件事:技能能被 Agent 发现、Agent 能正确选择技能、技能执行结果能回到 Agent Loop。

先写一个最小技能skills/web_search.py。为了不依赖外部搜索 API,我用一个模拟实现,重点是展示技能的结构约定:

# skills/web_search.py import json import sys def run(args: dict) -> dict: query = args.get("query", "") # 实际项目中这里调用搜索 API # 这里返回模拟结果,验证链路 return { "query": query, "results": [ {"title": f"关于 {query} 的结果一", "url": "https://example.com/1"}, {"title": f"关于 {query} 的结果二", "url": "https://example.com/2"}, ], } if __name__ == "__main__": payload = json.loads(sys.stdin.read()) result = run(payload) print(json.dumps(result, ensure_ascii=False))

技能的结构约定是:从 stdin 读 JSON 参数,向 stdout 写 JSON 结果。这个约定让技能和 Agent 之间解耦,技能可以用任何语言写,只要遵守这个 IO 协议。file_ops.py同理,实现 read 和 write 两个动作。

技能写好后,Agent 怎么发现它?在auto_reload = true的情况下,Agent 每轮开始时会扫描registry_dir,把每个.py文件的文件名和[skills.entries.*]里的 description 关联起来。如果某个技能文件存在但没有在 config.toml 里显式注册,Agent 会读取文件头部的 docstring 作为描述。所以更规范的做法是在技能文件顶部加 docstring:

""" web_search: 根据关键词检索网页并返回摘要。 参数: {"query": "搜索词"} 返回: {"query": str, "results": [{"title": str, "url": str}]} """

现在启动 Agent 主循环。一个最小实现:

# agent.py import json import os import subprocess from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", ) def load_skills(skills_dir): skills = {} for fname in os.listdir(skills_dir): if fname.endswith(".py"): name = fname[:-3] skills[name] = os.path.join(skills_dir, fname) return skills def call_skill(path, args): proc = subprocess.run( ["python", path], input=json.dumps(args), capture_output=True, text=True, timeout=30, ) if proc.returncode != 0: return {"error": proc.stderr} return json.loads(proc.stdout) def agent_loop(user_input, skills, max_iter=12): messages = [ {"role": "system", "content": open("prompts/agent_system.md").read()}, {"role": "user", "content": user_input}, ] for i in range(max_iter): resp = client.chat.completions.create( model=os.environ["TAOTOKEN_MODEL_ID"], messages=messages, ) content = resp.choices[0].message.content messages.append({"role": "assistant", "content": content}) try: action = json.loads(content) except json.JSONDecodeError: return content skill_name = action.get("skill") if skill_name not in skills: messages.append({"role": "user", "content": f"技能 {skill_name} 不存在"}) continue result = call_skill(skills[skill_name], action.get("args", {})) messages.append({"role": "user", "content": json.dumps(result, ensure_ascii=False)}) return "达到最大迭代次数" if __name__ == "__main__": skills = load_skills("./skills") print(agent_loop("帮我搜索一下 OpenClaw 架构设计", skills))

运行python agent.py,你会看到 Agent 先输出一个 JSON 指定调用 web_search,主循环解析后执行技能,把结果塞回 messages,再请求模型生成最终回复。整个过程里,模型只负责决策,技能负责执行,通道负责通信,三层职责清晰。

验证成功的标志是:终端打印出包含"关于 OpenClaw 架构设计的结果一"的最终回复。如果卡在某一步,看下一节的排障对照。

这里有一个实测经验:max_iterations不要设太大,12 轮足够覆盖绝大多数任务。设太大反而容易让 Agent 在失败时反复重试同一个技能,烧 token 还不出结果。另外,技能执行一定要加 timeout,我设的是 30 秒,防止某个技能卡死拖垮整个循环。

5. 常见报错排查:401、local proxy failed 与 reading choices

配置跑起来之后,报错是难免的。这一节我把最常见的几类错误和排查路径列出来,你对照着看。

第一类:401 Unauthorized。这个错误几乎都是 Key 的问题。排查顺序是:先确认环境变量有没有真正注入,在 shell 里执行echo $TAOTOKEN_API_KEY,如果输出为空,说明 export 没生效或者在新开的终端里没加载。再确认 Key 有没有多余空格或换行,从控制台复制时容易带上尾部空白。最后确认 Key 有没有过期或被删除,去控制台的 API Keys 页面核对。如果 curl 直接请求也返回 401,那就是 Key 本身的问题,重新创建一个。

第二类:local proxy failed 或 connection refused。这个错误通常出现在你本地有代理设置、但代理没有运行的情况下。排查方法是检查环境变量HTTP_PROXY和HTTPS_PROXY,如果设置了但代理服务没开,请求会直接失败。临时解决是unset HTTP_PROXY HTTPS_PROXY,然后重试。如果你确实需要走代理,确保代理服务在运行且端口正确。注意,这里说的是本地开发环境的网络配置问题,不是让你去搭什么特殊通道,只是排查环境变量残留。

第三类:reading 'choices' 或 KeyError 'choices'。这个错误说明你拿到的响应里没有 choices 字段,通常是响应体是一个错误对象而不是正常 completion。排查方法是把原始响应打印出来:

resp = client.chat.completions.create(...) print(resp.model_dump_json(indent=2))

如果看到error字段,里面的 message 会告诉你具体原因。常见的有 model not found(Model ID 写错)、context length exceeded(上下文超长)、rate limit(限流)。Model ID 一定要和控制台里列出的完全一致,大小写和日期后缀都不能错。

第四类:OAuth 相关错误。如果你用的是 Claude Code 或类似工具,可能会遇到 OAuth token 过期的问题。这类工具通常有自己的鉴权流程,和 API Key 是两套机制。排查方法是确认你用的是 API Key 模式而不是 OAuth 模式,在工具的配置里把鉴权方式切到 API Key,Base URL 填https://taotoken.net/api,Key 填环境变量。如果工具同时支持两种模式,优先用 API Key,因为它的排查路径更短。

第五类:技能执行超时或返回非 JSON。这个错误不在 API 层,在技能层。排查方法是单独跑技能脚本:

echo '{"query": "test"}' | python skills/web_search.py

如果脚本本身报错,先修脚本。如果脚本正常但 Agent 调用失败,检查call_skill里的 timeout 设置和 stdin 传参格式。技能脚本必须从 stdin 读、向 stdout 写,任何 print 调试信息都会污染 JSON 输出,调试信息要写到 stderr。

为了让你更快定位,我把这几类错误和对应动作整理成对照:

报错关键词最可能原因第一步动作
401 UnauthorizedKey 未注入或失效echo 环境变量,核对控制台
local proxy failed代理环境变量残留unset HTTP_PROXY HTTPS_PROXY
reading 'choices'响应是错误对象打印原始响应看 error.message
model not foundModel ID 不匹配核对控制台模型列表
OAuth token expired鉴权模式用错切换到 API Key 模式
技能返回非 JSON脚本有 print 污染调试信息改写到 stderr

排查的核心思路是分层定位:先确认通道通不通(curl 直连),再确认 SDK 配置对不对(打印原始响应),最后确认技能本身跑不跑得通(单独执行脚本)。三层都过了,Agent Loop 就没有理由失败。

6. 用统一通道支撑多工具协作的下一步

走到这里,你已经有了一个能跑的最小 AI Native Agent:config.toml 定义了架构骨架,TaoToken 统一通道解决了模型接入,Skills Registry 解决了能力扩展,Agent Loop 把三者串起来。接下来我想聊聊这套架构在真实项目里怎么继续演进。

第一个方向是技能的市场化。当allow_runtime_creation = true打开后,AI 会自己往 skills 目录里写脚本。这些脚本积累多了,就形成了一个技能库。你可以给技能加版本号、加依赖声明、加测试用例,让它从"AI 随手写的脚本"变成"可复用的能力单元"。这一步的关键是给技能文件加元数据头,比如# version: 1.0、# requires: requests,Agent 在加载时解析这些元数据,就能做依赖检查和版本管理。

第二个方向是调度策略的细化。现在的 Agent Loop 是单模型串行,规划模型和执行模型分开配置但用的是同一个通道。你可以进一步做模型路由:根据技能类型选模型,搜索类技能用快模型,代码生成类技能用强模型。TaoToken 的通道支持在请求里指定 model,所以路由逻辑只需要在call_skill之前加一层判断,不需要改通道配置。

第三个方向是沙箱的强化。type = "local"只适合开发阶段,生产环境要换成容器沙箱。容器沙箱的配置思路是把[sandbox]区块扩展成镜像地址、资源限制、网络策略三部分。技能执行时在容器里跑,Agent 主循环在宿主机跑,两者通过标准 IO 通信。这样即使 AI 生成的脚本有问题,也影响不到宿主机。

第四个方向是可观测性。Agent 的决策链路比普通应用长得多,一次任务可能经过十几轮模型调用和技能执行。你需要记录每一轮的输入输出、耗时、token 消耗,才能定位性能瓶颈。最简单的做法是在agent_loop里加结构化日志,每轮记录iteration、skill_name、duration_ms、tokens_used。这些数据积累起来,你就能看出哪些技能调用频繁、哪些模型响应慢、哪些任务容易失败。

如果你想把 Coding Plan 用在长期编码任务上,或者想用模型对话快速验证不同模型在 Agent 场景下的表现,可以按下面的路径走:需要创建和管理 Key 的去 API Keys 页面,需要查接入细节的看接入文档,想直接对话验证模型的用模型对话,长期跑编码和 Agent 任务的看 Coding Plan。这几个入口覆盖了从验证到生产的完整链路。

最后说一个我踩过的坑:不要一开始就把架构设计得太复杂。我见过有人上来就搞多 Agent 协作、搞向量记忆、搞复杂路由,结果连一个技能都调不通。正确的顺序是先跑通单 Agent 单技能,再逐步加技能、加模型、加沙箱。OpenClaw 的架构实验之所以有价值,恰恰是因为它做了极致的减法,把框架该做的事压到最少,把 AI 能做的事放到最大。你复刻的时候也遵循这个原则,先让最小闭环跑起来,再谈扩展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 7:41:04

Qt拼图游戏源码全解析:从工程结构到答辩避坑

简介:适用于C期末大作业与课程设计场景的压缩包,内含一套基于Qt界面库开发的拼图游戏完整工程,适合具备C基础、希望快速完成GUI项目的在校学生参考。游戏覆盖图片分割、碎片拖动、成功判定等核心交互逻辑,以VS工程形式组织&#x…

作者头像 李华
网站建设 2026/10/1 7:41:02

专访亨得利售后技师:2026年上海腕表保养,哪些细节决定腕表长期状态?

导语上海国内腕表消费氛围浓厚,大量表主长期佩戴机械、石英腕表,很多人对于腕表保养、机芯养护的认知,大多停留在“走时不准再送修”。上海本地的亨得利服务中心常年接待各类腕表养护咨询,日常接触大量本土表主遇到的养护难题。带…

作者头像 李华
网站建设 2026/10/1 7:41:01

MCP实战:用 Python + FastMCP 从零写一个可调试的 MCP 服务(附源码)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 7:41:01

Codex CLI 实战指南:Node.js 版本、tmux 代理与调用链深度解析

1. OpenRig 是什么:一个被误读的开源项目名与真实技术定位OpenRig 这个词在当前中文技术社区里,正经历一场典型的“语义漂移”——它既不是官方发布的知名开源项目,也不是某个主流框架的代号,而更像是一组高频共现关键词在搜索引擎…

作者头像 李华
网站建设 2026/10/1 7:40:17

2026年大模型本地部署指南:硬件选型、推理框架与工具实操深度评测

1. 为什么要在 2026 年重新聊本地部署聊大模型本地部署,其实不需要再讲“隐私有多重要”“数据不能出内网”这类大道理了——2026 年还纠结这些问题的人,大概率已经被企业内部的知识库项目、代码助手私有化、或者个人折腾 AI 写作折腾到头皮发麻&#xf…

作者头像 李华