ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenClaw 源码解析:从架构设计到扩展开发的工程实践

OpenClaw 源码解析:从架构设计到扩展开发的工程实践 1. 从一次扩展失败说起OpenClaw 源码解析到底难在哪如果你已经用 OpenClaw 跑通过几个自动化任务接下来大概率会冒出同一个念头能不能自己加个工具、加个技能甚至改改它的执行流程我当初也是这么想的结果第一次动手就卡住了——工具类写完了注册也写了运行起来却报ToolNotFoundError翻遍文档也没说清楚加载顺序到底是怎么走的。这就是 OpenClaw 源码解析这件事的真实门槛它不是 API 调用层面的问题而是你得先搞清楚它的架构分层和扩展机制知道一个工具从「写出来」到「被 Planner 调用」中间经过了哪些环节。OpenClaw 本身是一个面向任务自动化的智能体框架核心能力是把用户的自然语言请求拆解成可执行步骤再调度文件操作、浏览器控制、消息发送、系统命令、AI 模型调用等工具去完成。它适合两类人一类是想拿它当生产力工具、但发现内置工具不够用的工程师另一类是想基于它做二次开发、深度定制的开发者。这篇文章不打算泛泛讲「架构很优雅」这种话而是带你走一遍真实的源码路径目录结构长什么样、核心模块的调用链怎么串、扩展点在哪里配置、本地怎么跑通验证。读完你应该能自己动手加一个工具并让它被正确加载。过程中如果涉及模型调用我会用 TaoToken 作为统一接入层来演示因为它兼容 OpenAI 协议配置起来比较直接。先说清楚一个前提OpenClaw 的源码结构在不同版本间会有微调但分层逻辑是稳定的。你只要抓住「接口层 → 核心引擎层 → 工具执行层 → 基础设施层」这四层后面看任何模块都能对号入座。2. OpenClaw 目录结构与核心模块调用链拆解2.1 四层架构与目录映射OpenClaw 的源码目录基本是按分层来组织的你打开仓库根目录会看到类似这样的结构openclaw/ ├── core/ # 核心引擎层 │ ├── planner.py # 任务规划器 │ ├── context.py # 上下文管理 │ ├── executor.py # 执行调度 │ └── memory.py # 记忆管理 ├── tools/ # 工具执行层 │ ├── registry.py # 工具注册中心 │ ├── file_ops.py # 文件操作 │ ├── browser.py # 浏览器控制 │ ├── shell.py # 系统命令 │ └── llm.py # 模型调用 ├── skill/ # 技能层多步骤工作流 │ └── base.py ├── plugin/ # 插件层 │ └── base.py ├── infra/ # 基础设施层 │ ├── config.py │ ├── logger.py │ └── storage.py ├── cli.py # 命令行入口 └── api.py # HTTP 接口入口接口层cli.py/api.py负责接收用户输入核心引擎层core/负责规划和调度工具执行层tools/负责真正干活基础设施层infra/提供配置、日志、存储这些支撑能力。技能层和插件层是扩展机制的两个不同粒度Skill 是「多步骤工作流的封装」Plugin 是「带生命周期钩子的功能模块」。2.2 一次请求的完整调用链假设你在命令行输入「帮我把 report.md 的内容发到消息通道」这条请求在源码里的流转路径是这样的# 1. cli.py 接收输入 user_input 帮我把 report.md 的内容发到消息通道 # 2. core/planner.py 拆解任务 planner TaskPlanner(llm) steps planner.plan(user_input) # 返回类似[Step(read_file, pathreport.md), Step(send_message, content...)] # 3. core/context.py 维护执行状态 context ExecutionContext() context.add_message(user, user_input) # 4. core/executor.py 逐步调度 for step in steps: result registry.execute(step.tool_name, **step.params) context.add_message(tool, result) # 5. tools/registry.py 找到对应工具并执行关键点在于 Planner 的输出格式。它返回的是一个Step列表每个 Step 包含工具名和参数。Executor 拿到 Step 后去 ToolRegistry 里查工具查不到就抛ToolNotFoundError。我第一次失败就是因为工具类写好了但没在配置里注册Registry 里根本没有这个 key。2.3 Planner 与 Context 的协作细节Planner 的核心逻辑其实不复杂它把用户输入拼进一个 prompt让模型输出结构化的步骤列表class TaskPlanner: def __init__(self, llm): self.llm llm def plan(self, user_input: str) - list: prompt f 用户请求{user_input} 可用工具{self.available_tools()} 请拆解为具体步骤每步格式为 工具名 | 参数JSON response self.llm.generate(prompt) return self.parse_steps(response)这里有个容易踩的坑available_tools()返回的工具列表直接决定了模型能规划出哪些步骤。如果你新加的工具没出现在这个列表里模型压根不会去调用它。所以扩展工具时「注册到 Registry」和「让 Planner 感知到」是两件事后者往往被忽略。Context 则负责维护对话历史和变量。它的get_context(max_tokens)方法会在每次调用模型前做一次裁剪保留最近的和标记为重要的消息。这个裁剪策略直接影响多轮任务的稳定性后面排障部分会细说。3. 扩展点配置工具、技能与插件的可复制写法3.1 自定义工具的最小闭环先写一个工具类。OpenClaw 的工具基类要求你定义name、description、params和run方法# my_tools/weather.py from openclaw.tools import Tool class WeatherTool(Tool): name get_weather description 查询指定城市的天气 params { city: { type: string, required: True, description: 城市名称如 Beijing } } def run(self, city: str) - str: import requests url fhttp://wttr.in/{city}?format3 return requests.get(url, timeout10).text然后在包的__init__.py里暴露注册函数# my_tools/__init__.py from .weather import WeatherTool def register_tools(registry): registry.register(WeatherTool())最后是配置文件。OpenClaw 用 YAML 管理工具加载路径通常在config/config.yamltools: - openclaw.tools.file_ops - openclaw.tools.shell - my_tools.weather llm: provider: openai_compatible base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} model: claude-sonnet-4-20250514注意base_url这里填的是 TaoToken 的 API 地址api_key从环境变量读取不要硬编码进配置文件。模型 ID 按你实际订阅的填这里只是示例。3.2 技能Skill的封装方式Skill 适合把一串固定步骤打包成一个可复用单元。比如「发布博客到多个平台」这种流程# skills/blog_publisher.py from openclaw.skill import Skill class BlogPublisher(Skill): name publish_blog description 将文章发布到多个内容平台 def execute(self, article_path: str, platforms: list): content self.read_file(article_path) results {} for platform in platforms: if platform csdn: results[csdn] self.publish_to_csdn(content) elif platform juejin: results[juejin] self.publish_to_juejin(content) return resultsSkill 和 Tool 的区别在于Tool 是原子操作Skill 是编排。Planner 在规划时会把 Skill 当成一个整体步骤而不是拆开它的内部逻辑。3.3 插件Plugin的生命周期钩子Plugin 比 Skill 更重它带on_load和on_unload钩子适合做需要初始化资源的扩展# my_plugin/plugin.py from openclaw.plugin import Plugin class MyPlugin(Plugin): name my_plugin version 1.0.0 def on_load(self): self.logger.info(MyPlugin loaded) self.register_commands() def on_unload(self): self.cleanup() def register_commands(self): self.command(hello) def hello_command(args): return Hello from MyPlugin!插件目录结构建议保持固定my_plugin/ ├── __init__.py ├── plugin.py ├── handlers.py └── config.yamlconfig.yaml里可以声明插件依赖的工具和技能加载时会按顺序初始化。4. 本地跑通验证从配置到成功请求4.1 环境准备与依赖安装先克隆源码并装依赖git clone https://github.com/your-org/openclaw.git cd openclaw python -m venv venv source venv/bin/activate pip install -r requirements.txt设置 API Key 环境变量export TAOTOKEN_API_KEY你的keyKey 可以在 TaoToken 的 API Keys 页面生成地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。生成后复制到环境变量里不要写进代码。4.2 验证工具是否被正确加载写一个最小验证脚本确认 Registry 里能查到你的工具# verify_tools.py from openclaw.infra.config import load_config from openclaw.tools.registry import ToolRegistry config load_config(config/config.yaml) registry ToolRegistry() registry.load_from_config(config) print(已注册工具, list(registry.tools.keys())) assert get_weather in registry.tools, WeatherTool 未加载 print(WeatherTool 加载成功)运行python verify_tools.py如果输出里包含get_weather说明注册链路是通的。如果报ToolNotFoundError先检查config.yaml里的模块路径拼写再确认register_tools函数名没写错。4.3 发起一次真实请求工具加载成功后跑一次端到端请求# run_task.py from openclaw import OpenClaw agent OpenClaw(config_pathconfig/config.yaml) result agent.run(查询 Beijing 的天气并告诉我) print(result)预期输出类似Beijing: ⛅ 12°C如果模型调用返回 401说明 API Key 没读到或者 base_url 写错了。如果返回reading choices相关报错通常是响应格式和解析逻辑不匹配检查模型 ID 是否填对。4.4 验证扩展点是否生效最后确认 Planner 能感知到新工具。在run_task.py里加一行打印print(Planner 可见工具, agent.planner.available_tools())如果get_weather出现在列表里说明从注册到规划感知的完整链路都通了。这一步很多人会漏掉导致工具注册了但模型永远不调用。5. 常见报错排查401、ToolNotFound、reading choices 与 OAuth5.1 401 Unauthorized最常见的报错原因基本是三类环境变量没导出、Key 复制时带了空格、base_url 写成了带路径的完整地址。排查顺序echo $TAOTOKEN_API_KEY # 确认非空 curl -s https://taotoken.net/api/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | head如果 curl 也返回 401说明 Key 本身有问题去控制台重新生成一个。如果 curl 正常但 OpenClaw 报 401检查配置文件里是不是把api_key写成了字面量${TAOTOKEN_API_KEY}而没做变量替换。5.2 ToolNotFoundError这个报错说明 Executor 在 Registry 里没找到对应工具。三个检查点工具类的name属性是否和 Planner 输出的工具名一致config.yaml里是否包含了工具所在模块register_tools是否真的被调用了。我踩过的坑是模块路径写成了my_tools.weather.WeatherTool而配置里只需要写到模块级my_tools.weather。5.3 reading choices 解析失败这个报错通常出现在模型返回格式和解析器预期不一致时。OpenClaw 的 LLM 工具默认按 OpenAI 的choices[0].message.content结构解析。如果你换了一个返回格式不同的模型就会在这里挂掉。解决办法是在tools/llm.py里加一层适配def parse_response(self, raw): if choices in raw: return raw[choices][0][message][content] if content in raw: return raw[content] raise ValueError(f未知响应格式: {raw.keys()})5.4 OAuth 与本地代理相关报错如果你在配置里用了需要 OAuth 的模型服务可能会遇到 token 过期或回调失败。OpenClaw 本身不处理 OAuth 流程它只认最终的 API Key。所以遇到 OAuth 相关报错先确认你的接入层是否已经把 OAuth 换成了稳定的 Key。用 TaoToken 这类兼容 OpenAI 协议的接入层时直接填 Key 就行不需要走 OAuth 回调。另外如果你在本地开发时配了代理可能会看到local proxy failed之类的报错。检查config.yaml里有没有残留的proxy字段OpenClaw 默认不走代理多余的配置反而会干扰请求。5.5 工具执行超时天气查询这类外部请求如果没设 timeout网络抖动时会一直挂着。建议所有工具类都在run方法里显式设置超时def run(self, city: str) - str: import requests return requests.get(url, timeout10).text超时后 Executor 会捕获异常并记录到 Context不会导致整个任务崩溃。6. 从读源码到动手扩展的下一步走到这里你应该已经能独立完成「写工具 → 注册 → 配置 → 验证」这个闭环了。回头看OpenClaw 源码解析的核心不是记住每个文件在哪而是理解那条从用户输入到工具执行的调用链以及扩展点在这条链上的位置。如果你接下来想继续深入我建议从两个方向入手一是把core/executor.py的调度逻辑读透特别是它怎么处理步骤之间的依赖和失败重试二是试着写一个带on_load钩子的插件体验一下比工具更重的扩展粒度。这两个方向都能帮你在真实项目里少走弯路。模型调用这块如果你还没配好接入层可以先用 TaoToken 的模型对话页面快速验证一下模型 ID 和 Key 是否可用地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。验证通过后再写进config.yaml能省掉不少排查时间。长期做编码和 Agent 开发的话Coding Plan 的额度模型会更适合高频调用场景具体可以看 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。扩展开发最怕的不是写不出代码而是写完不知道为什么不生效。把验证脚本留好每次改完配置跑一遍比对着日志猜要快得多。
返回列表