1. 为什么值得花时间拆解 OpenClaw 的 Agent 架构如果你用过 OpenClaw大概率经历过这种时刻一句“帮我把 src/utils.ts 里的类型错误修掉”它自己找文件、读内容、改代码、跑校验全程不用你插手。表面看是“魔法”拆开看其实就三样东西在配合工具注册表负责“能做什么”模型适配层负责“用哪个大脑想”ReAct 循环负责“想一步做一步再想一步”。这篇不聊虚的目标很明确给想快速建立全局认知的开发者一张可复制的组件关系图再配一套能跑起来的配置骨架最后带你逐层验证 Agent 调用链路。读完你应该能回答三个问题——工具是怎么被注册和选中的、模型适配层怎么把不同厂商的 API 抹平成统一接口、ReAct 循环在代码里到底长什么样。适合谁看正在用 OpenClaw 但遇到“工具没被调用”“模型返回格式不对”“循环停不下来”这类问题的开发者想给 OpenClaw 写自定义工具或接自研模型的人以及单纯想把 Agent 架构搞明白、迁移到自己项目里的工程师。下面所有配置和验证步骤都可以直接跟做模型侧我用 TaoToken 做统一接入省去多厂商 Key 来回切换的麻烦。2. 前置准备用 TaoToken 统一模型适配层的入口OpenClaw 的模型适配层设计上支持多 provider但每接一家就要配一套 Key、一套 base_url、一套鉴权头调试阶段很碎。我的做法是先用 TaoToken 把模型入口统一掉适配层里只保留一个 OpenAI 兼容的 provider验证链路时心智负担小很多。TaoToken 在这里扮演的角色就是“模型适配层的外部统一入口”它提供 OpenAI 兼容的接口OpenClaw 的适配器只要按标准 OpenAI 协议发请求即可不用为每家厂商写分支。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 这个不加 UTM。拿 Key 的路径进控制台 → API Keys 页面创建复制出来的 Key 形如sk-开头的一串。控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你后面要长期跑编码类 Agent 任务可以顺带看下 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 额度模型和按量计费不太一样。注意Key 只放环境变量别写进仓库。OpenClaw 的配置里用${TAOTOKEN_API_KEY}这种占位引用适配层读取时再展开。环境变量这样设Linux/macOSexport TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api验证环境变量生效echo $TAOTOKEN_BASE_URL # 期望输出https://taotoken.net/api这一步做完模型适配层的外部依赖就绪接下来看 OpenClaw 内部怎么把请求接进去。3. 组件关系图与配置骨架工具注册表、适配层、ReAct 循环先把三条主线的职责对齐不然后面配置容易配串。工具注册表Tool Registry是“能力清单”。每个工具声明 name、description、parametersJSON Schema和 execute 函数。Agent 决策时系统提示里塞的就是这份清单的摘要模型据此决定调哪个工具、传什么参数。注册表还管权限标记比如 Bash 标requiresConfirmation: true执行前弹确认。模型适配层Model Adapter是“翻译官”。它把 OpenClaw 内部统一的消息格式翻译成各家 API 要的格式再把响应翻译回来。核心接口就四个方法complete、stream、countTokens、getContextWindow。接 TaoToken 时你只需要一个 OpenAI 兼容适配器。ReAct 循环是“调度器”。它把“模型思考 → 产出工具调用 → 执行工具 → 把结果塞回消息历史 → 再问模型”串成 while 循环直到模型不再请求工具、直接给出最终回答。三者关系用一张图表示用户输入 │ ▼ [消息解析器] ──► [ReAct 循环] │ ┌───────────┼───────────┐ ▼ ▼ ▼ [模型适配层] [工具注册表] [执行监控] │ │ ▼ ▼ TaoToken API 文件/终端/LSP配置骨架openclaw.config.ts风格字段名按你实际版本调整// openclaw.config.ts export default { // 模型适配层统一走 OpenAI 兼容协议 providers: [ { id: taotoken, type: openai-compatible, baseURL: process.env.TAOTOKEN_BASE_URL, apiKey: process.env.TAOTOKEN_API_KEY, models: [claude-sonnet-4-5, gpt-4o, deepseek-chat], defaultModel: claude-sonnet-4-5, }, ], // 工具注册表声明启用哪些工具 tools: { Read: { enabled: true, permissions: [read] }, Write: { enabled: true, permissions: [write], requiresConfirmation: true }, Edit: { enabled: true, permissions: [write], requiresConfirmation: true }, Glob: { enabled: true, permissions: [read] }, Grep: { enabled: true, permissions: [read] }, Bash: { enabled: true, permissions: [exec], requiresConfirmation: true }, }, // ReAct 循环参数 agent: { maxIterations: 25, // 防止死循环 parallelToolCalls: true, // 允许并行工具调用 contextBudget: { systemPrompt: 4000, projectContext: 8000, activeFiles: 50000, conversationHistory: 40000, }, }, };自定义工具注册示例注意 description 要写清楚“什么时候用”模型选工具全靠它import type { Tool } from openclaw/tools; const analyzeDeps: Tool { name: analyze_dependencies, description: 分析项目依赖关系并生成依赖图。当用户询问依赖冲突、循环依赖或包体积来源时使用。, parameters: { type: object, properties: { packageJson: { type: string, description: package.json 的路径 }, depth: { type: number, description: 分析深度默认 2 }, }, required: [packageJson], }, permissions: [read], execute: async (params, ctx) { const { packageJson, depth 2 } params; // 你的分析逻辑 return { success: true, data: { graph: {}, depth } }; }, }; registry.register(analyzeDeps);ReAct 循环的伪代码理解这段就理解了整个调度async function reactLoop(userInput: string) { const messages buildInitialMessages(userInput); // 含系统提示工具清单 let iteration 0; while (iteration config.agent.maxIterations) { const response await adapter.complete(messages, { model: config.providers[0].defaultModel, tools: registry.toSchemaList(), // 工具注册表转成 API 要的 schema }); messages.push(response.message); // 模型不再请求工具说明任务结束 if (!response.toolCalls?.length) { return response.content; } // 执行工具结果塞回消息历史 const results await Promise.all( response.toolCalls.map((call) registry.execute(call.name, call.input)) ); messages.push(...results.map(toToolResultMessage)); iteration; } throw new Error(ReAct 循环超过最大迭代次数疑似死循环); }到这里三条主线就串起来了适配层负责“问模型”注册表负责“给工具”循环负责“来回倒腾”。下面逐层验证。4. 逐层验证 Agent 调用链路验证顺序建议从外到内先确认模型适配层通再确认工具注册表被正确暴露最后看 ReAct 循环是否完整跑通。这样出问题能快速定位是哪一层。4.1 验证模型适配层先用 curl 直接打 TaoToken 的接口排除 OpenClaw 配置干扰curl -s https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5, messages: [{role: user, content: 只回复两个字通了}], max_tokens: 20 }期望返回里choices[0].message.content是“通了”。如果返回 401检查 Key返回 404检查 base_url 是不是漏了/api返回模型不存在去模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认当前可用模型名。4.2 验证工具注册表OpenClaw 一般有命令能 dump 当前注册的工具清单类似openclaw tools list期望输出包含 Read、Edit、Bash 以及你自定义的 analyze_dependencies。如果自定义工具没出现检查注册代码是否在启动时执行、name 是否重复。再验证工具 schema 是否正确暴露给模型openclaw tools schema --json | head -40看输出的 JSON Schema 里 parameters 字段是否完整。模型选错工具、参数传错八成是这里 description 写得太模糊。4.3 验证 ReAct 循环跑一个最小任务观察日志里的循环轨迹openclaw run 读取 package.json告诉我项目名 --verbose--verbose下你应该看到类似轨迹[iter 1] model - tool_use: Read { file_path: package.json } [iter 1] tool_result: { name: my-app, ... } [iter 2] model - final answer: 项目名是 my-app如果只看到 iter 1 就结束、没有 tool_result说明工具执行层没接上如果 iter 一直涨到 maxIterations说明模型没正确产出最终回答通常是系统提示里没告诉它“任务完成时直接回答不要再调工具”。4.4 验证并行工具调用让 Agent 同时读多个文件看是否并行openclaw run 同时读取 src/a.ts、src/b.ts、src/c.ts 的首行 --verbose日志里三个 Read 的 tool_use 应该在同一轮出现而不是串行三轮。如果串行检查配置里parallelToolCalls是否为 true以及适配层是否正确解析了多个 tool_calls。5. 本篇常见错排查报错一401 Unauthorized或invalid api key先确认环境变量在当前 shell 生效echo $TAOTOKEN_API_KEY有值再确认 Key 没被复制时带空格。OpenClaw 配置里如果用了${TAOTOKEN_API_KEY}确认加载配置的进程能读到该变量systemd 或 Docker 场景常漏。报错二模型返回的工具调用格式解析失败典型日志是failed to parse tool_use block。原因通常是适配层把非 OpenAI 格式的响应直接透传了。接 TaoToken 时统一用 OpenAI 兼容适配器别混用 Anthropic 原生适配器。检查type字段是否为openai-compatible。报错三工具没被调用模型直接瞎答模型不知道有哪些工具可用。检查系统提示里是否注入了registry.toSchemaList()的结果。另一个常见原因是工具 description 写成了“读取文件”这种泛泛描述模型判断不出何时该用改成“当需要查看文件内容时使用参数 file_path 为绝对路径”这类具体描述。报错四ReAct 循环停不下来maxIterations是兜底但根因通常是工具执行结果没塞回消息历史模型看不到 observation只能反复请求同一个工具。检查messages.push(...results.map(toToolResultMessage))这步是否执行以及 tool_result 的tool_use_id是否和请求的 id 对上。报错五上下文超限context length exceededOpenClaw 的 contextBudget 分配不合理或者 activeFiles 塞了太多大文件。先把activeFiles调小确认对话历史是否无限增长——长会话要加历史裁剪策略只保留最近 N 轮加关键决策记录。报错六Bash 工具执行被拒requiresConfirmation: true的工具在非交互模式下会直接拒绝。CI 或脚本场景要么加--yes类参数要么把该工具的确认关掉但生产环境不建议关。6. 下一步把链路跑顺之后做什么链路验证通过后建议做三件事。第一把自定义工具按业务场景补齐每个工具的 description 当成给模型看的“使用说明书”来写这直接决定 Agent 选工具的准确率。第二给 ReAct 循环加可观测性把每轮的 thinking、tool_use、tool_result 结构化打日志出问题能回放。第三模型侧如果要做长期编码或 Agent 任务去 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 看下额度方案按量计费在长循环场景下成本波动比较大。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有 OpenAI 兼容接口的完整参数说明配适配层时对着看能少踩不少格式坑。如果你更想先手动感受下模型返回的工具调用长什么样模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以直接发带 tools 参数的请求观察原始响应比在 OpenClaw 里加日志更快。
