尧图精选

OpenClaw 工具调研决策核心逻辑:从 Agent Loop 到 Tool Use 的 TypeScript 实现

🕒 发布时间:2026/10/2 11:41:09 📁 来源:尧图网络
1. OpenClaw 工具调研决策核心逻辑从 Agent Loop 到 Tool Use 的 TypeScript 实现OpenClaw 是一个用 TypeScript 实现的 Agent 运行时它把「LLM 自动调用工具」这件事拆成了两个可独立调试的模块Agent Loop循环调度器和 Tool Use工具协议层。如果你正在评估多工具调用策略或者想搞清楚为什么 Agent 能自己决定「先读文件、再改代码、最后跑测试」那 OpenClaw 的源码结构值得逐层拆开看。它适合三类人需要给现有系统加 Agent 能力的后端开发者、想自建工具调用框架的架构师、以及正在对比不同 Agent 实现方案的技术选型者。我试过把 OpenClaw 的循环逻辑单独抽出来跑发现它的核心其实非常朴素——一个 while 循环每次把对话历史和工具定义一起发给 LLMLLM 返回「要不要调工具」的指令Agent 负责执行并把结果塞回历史直到 LLM 说「我说完了」。但真正让它在生产环境可用的是循环外面的那些约束最大迭代次数、工具权限控制、沙箱隔离、错误重试。这篇文章会从 Agent Loop 的 TypeScript 实现讲起给出可复制的配置片段和 Tool Use 决策表最后用一次完整的工具选择验证流程收尾。在开始之前你需要准备一个能访问 LLM API 的 Key。我用的是 TaoToken 的兼容接口它的 Base URL 是https://taotoken.net/api支持 Anthropic 和 OpenAI 两种消息格式这样在 OpenClaw 里切换 Provider 时不用改太多代码。下面所有示例都基于这个接口跑通。2. Agent Loop 的 TypeScript 实现与工具注册机制OpenClaw 的 Agent Loop 藏在src/agents/pi-embedded-runner/run/attempt.ts里但它的逻辑和教学项目 learn-claude-code 的 Python 版本几乎一一对应。理解了这个循环你就理解了所有 Agent 的骨架。先看最核心的循环体。OpenClaw 把循环封装在 Pi Agent SDK 内部上层只需要配置 session 和 streamFn// openclaw/src/agents/pi-embedded-runner/run/attempt.ts const { session } await createAgentSession({ tools: builtInTools, // 内置工具exec、read、write、edit customTools: allCustomTools, // 自定义工具通过插件注册 model: params.model, maxIterations: 32, // 默认最大迭代次数 }); activeSession.agent.streamFn streamSimple; // 绑定 LLM 调用函数这里的streamFn就是循环里「调用 LLM」那一步的具体实现。OpenClaw 默认用 Anthropic 的消息格式streamSimple会把 messages 和 tools 一起发给模型。SDK 内部的循环逻辑用伪代码表示是这样的async function agentLoop(messages: Message[], tools: ToolDefinition[]) { let iteration 0; while (iteration maxIterations) { iteration; const response await streamFn(messages, tools); messages.push({ role: assistant, content: response.content }); if (response.stop_reason ! tool_use) { return response.content; // 没有工具调用循环结束 } for (const block of response.content) { if (block.type tool_use) { const output await executeTool(block.name, block.input); messages.push({ role: user, content: [{ type: tool_result, tool_use_id: block.id, content: output, }], }); } } } throw new Error(Max iterations reached); }关键点在于stop_reason的判断。LLM 返回的 response 里如果stop_reason是tool_use说明模型要求调用工具如果是end_turn说明模型认为任务完成直接返回文本。Agent 本身不「理解」工具它只是把工具的 JSON Schema 描述传给 LLM由 LLM 决定调不调、调哪个、传什么参数。工具注册在 OpenClaw 里是通过pi-tools.ts完成的// openclaw/src/tools/pi-tools.ts export function createOpenClawCodingTools(options: ToolOptions) { return [ createExecTool({ ... }), // bash 执行 createProcessTool({ ... }), // 进程管理 createApplyPatchTool({ ... }), // 补丁应用 createOpenClawReadTool({ ... }), // 文件读取 createSandboxedWriteTool({ ... }), // 沙箱写入 createSandboxedEditTool({ ... }), // 沙箱编辑 ]; }每个工具都实现了统一的接口name、description、parametersJSON Schema、execute。SDK 在调用 LLM 前会把这些工具转成 Anthropic 的 tool 格式{ name: read_file, description: Read the contents of a file, input_schema: { type: object, properties: { path: { type: string, description: File path to read }, limit: { type: number, description: Max lines to read } }, required: [path] } }这个 Schema 就是 LLM 做决策的全部依据。描述写得越清楚LLM 选错工具的概率越低。我在实测中发现把description从「读取文件」改成「读取指定路径的文本文件内容支持限制行数用于查看代码或配置」工具选择准确率有明显提升。3. 可复制的 Agent Loop 配置与 Tool Use 决策表要让 OpenClaw 跑起来你需要一份完整的配置。下面这个agent-config.json可以直接复制路径放在项目根目录的config/下{ agent: { name: openclaw-research, model: claude-sonnet-4-20250514, maxIterations: 32, temperature: 0.2, systemPrompt: 你是一个工具调研助手。根据用户需求选择合适的工具优先使用只读工具收集信息确认后再使用写入工具。 }, provider: { baseUrl: https://taotoken.net/api, apiKey: ${TAOTOKEN_API_KEY}, format: anthropic }, tools: { enabled: [read_file, write_file, edit_file, exec, search], permissions: { exec: { allow: [ls, cat, grep, find], deny: [rm, curl] }, write_file: { sandbox: ./workspace } } }, loop: { maxIterations: 32, timeoutMs: 120000, retryOnError: 2 } }对应的 TypeScript 加载代码import { createAgentSession } from openclaw; import config from ./config/agent-config.json; const session await createAgentSession({ model: config.agent.model, maxIterations: config.loop.maxIterations, tools: createOpenClawCodingTools({ sandboxRoot: config.tools.permissions.write_file.sandbox, execAllowlist: config.tools.permissions.exec.allow, }), }); session.agent.streamFn createStreamFn({ baseUrl: config.provider.baseUrl, apiKey: process.env.TAOTOKEN_API_KEY, format: config.provider.format, });Tool Use 的决策逻辑可以用一张表来对照。这张表是我在调试多个 Agent 项目后总结的列出了 LLM 在不同 stop_reason 和 content 组合下的行为stop_reasoncontent 类型Agent 动作下一步end_turn纯文本返回文本循环结束输出最终回复tool_usetext tool_use执行 tool_use 块结果追加到 messages继续循环tool_use多个 tool_use并行执行所有工具所有结果一起追加继续循环max_tokens截断文本记录警告返回已有内容可选重新请求补全error空或错误信息触发重试逻辑重试次数内重新调用 LLM这张表的关键在于第三行当 LLM 一次返回多个 tool_use 时OpenClaw 会并行执行它们。比如用户说「帮我看看 src 目录下有哪些文件顺便读一下 package.json」LLM 可能同时返回execls src和read_filepackage.json两个工具调用。Agent 并行执行后把两个结果一起塞回 messagesLLM 下一轮就能同时看到目录列表和依赖信息。但并行执行有个坑如果两个工具都写同一个文件会产生竞态。OpenClaw 的解法是在工具层加锁write_file和edit_file共享一个文件锁同一路径的写入串行化。这个细节在配置里体现为tools.permissions.write_file.sandbox的路径隔离。4. 验证请求与成功结果一次完整的工具选择流程配置写好后怎么验证 Agent Loop 真的在工作我设计了一个最小验证流程让 Agent 完成「读取一个文件、修改它、再读回来确认」的任务观察每一轮 LLM 的 stop_reason 和工具调用。先准备测试文件mkdir -p workspace echo hello world workspace/test.txt然后写验证脚本import { createAgentSession } from openclaw; const session await createAgentSession({ model: claude-sonnet-4-20250514, maxIterations: 10, tools: createOpenClawCodingTools({ sandboxRoot: ./workspace }), }); session.agent.streamFn createStreamFn({ baseUrl: https://taotoken.net/api, apiKey: process.env.TAOTOKEN_API_KEY, format: anthropic, }); // 监听每一轮循环 session.agent.on(iteration, (data) { console.log([第 ${data.iteration} 轮] stop_reason${data.stopReason}); console.log( 工具调用: ${data.toolCalls.map(t t.name).join(, ) || 无}); }); const result await session.run( 读取 workspace/test.txt把内容改成 hello openclaw然后读回来确认修改成功。 ); console.log(最终结果:, result);运行后控制台会输出类似这样的日志[第 1 轮] stop_reasontool_use 工具调用: read_file [第 2 轮] stop_reasontool_use 工具调用: write_file [第 3 轮] stop_reasontool_use 工具调用: read_file [第 4 轮] stop_reasonend_turn 工具调用: 无 最终结果: 文件已修改为 hello openclaw读取确认成功。这个流程验证了四件事第一LLM 能根据用户意图选择正确的工具序列读→写→读第二Agent Loop 在每次 tool_use 后正确追加结果并继续循环第三stop_reasonend_turn时循环正常终止第四最终文本回复包含了任务完成的确认。如果你想更直观地看工具调用链路可以用 TaoToken 的模型对话功能手动发一轮请求把 tools 定义贴进去观察模型返回的 tool_use 块结构。这比读日志更直接能帮你理解 LLM 到底「看到」了什么。验证通过后你可以把maxIterations调小到 3再跑一次同样的任务。这时 Agent 会在第三轮被强制终止返回一个「达到最大迭代次数」的错误。这个实验能帮你理解循环边界的重要性——生产环境里没有迭代上限的 Agent 可能因为 LLM 陷入死循环而烧掉大量 token。5. 本篇常见错误排查401、local proxy failed 与 reading choices跑 OpenClaw 时最容易撞上的几个报错我按出现频率排了个序每个都给出定位方法和修复步骤。401 Unauthorized是最常见的。报错长这样Error: 401 {error:{type:authentication_error,message:invalid x-api-key}}原因通常是TAOTOKEN_API_KEY环境变量没设置或者 Key 复制时带了空格。检查步骤先在终端执行echo $TAOTOKEN_API_KEY确认输出非空且没有首尾空格。如果为空在.env文件里补上TAOTOKEN_API_KEYsk-你的实际key然后在代码里用dotenv加载。注意 OpenClaw 的createStreamFn读取的是process.env.TAOTOKEN_API_KEY如果你用的是其他变量名需要在配置里显式指定apiKey: process.env.YOUR_KEY_NAME。local proxy failed这个报错通常出现在你配置了自定义 Base URL 但地址写错的时候Error: local proxy failed: ECONNREFUSED 127.0.0.1:8080OpenClaw 默认会读HTTP_PROXY环境变量。如果你的终端里残留了代理设置而代理服务没启动就会报这个错。修复方法是清掉代理变量unset HTTP_PROXY HTTPS_PROXY ALL_PROXY然后在配置里显式指定baseUrl: https://taotoken.net/api确保请求直连。如果你确实需要走网络中间层把baseUrl改成对应的地址即可但 OpenClaw 的streamFn不会自动读取系统代理需要你在createStreamFn里传入fetch的自定义实现。reading choices这个报错来自 OpenAI 格式的响应解析TypeError: Cannot read properties of undefined (reading choices)原因是你的format配置和实际 API 返回的格式不匹配。如果你用的是 Anthropic 格式的接口但配置里写了format: openai解析器会去找response.choices[0]而 Anthropic 返回的是response.content。修复方法确认provider.format和接口实际格式一致。TaoToken 的/api端点同时支持两种格式Anthropic 格式用format: anthropicOpenAI 格式用format: openai。还有一个隐蔽的报错是OAuth token expired出现在你用 Claude Code 的 OAuth 凭证去调 API 时Error: OAuth token expired, please re-authenticateOpenClaw 本身不管理 OAuth它只认 API Key。如果你从 Claude Code 迁移过来需要把 OAuth 换成 API Key。在 TaoToken 控制台的 API Keys 页面生成一个新 Key替换掉配置里的apiKey字段即可。排查完这些错误后建议把maxIterations和timeoutMs都设一个保守值比如 20 和 60000。生产环境里一个卡住的 Agent 循环比一个报错的 Agent 更危险因为它会持续消耗 token 而不产出结果。6. 从调研到落地OpenClaw 工具决策的长期策略把 OpenClaw 的 Agent Loop 跑通只是第一步。真正决定工具调用质量的是 Tool Use 的决策策略而这取决于三个变量工具描述的精度、循环边界的设置、以及错误恢复的粒度。工具描述方面我建议每个工具的description都包含「做什么、什么时候用、参数含义、返回什么」四个要素。比如read_file的描述不要只写「读取文件」而是写「读取指定路径的文本文件内容适用于查看代码、配置或日志。path 为相对路径limit 为可选的最大行数默认读取全部。返回文件内容字符串文件不存在时返回错误信息。」这样 LLM 在多个相似工具之间做选择时有足够的区分依据。循环边界方面maxIterations不要设太大。32 次迭代意味着最多 32 轮 LLM 调用按每轮 2000 token 算一次任务可能消耗 6 万 token。对于大多数编码任务10 到 15 次迭代足够。如果任务复杂到需要更多轮次说明你应该把它拆成多个子任务而不是让一个 Agent 循环跑到底。错误恢复方面OpenClaw 的retryOnError只对网络错误生效工具执行失败不会自动重试。你需要在工具层自己处理比如exec返回非零退出码时把 stderr 作为 tool_result 返回给 LLM让 LLM 决定是修正命令还是换一个工具。这比 Agent 层盲目重试更有效因为 LLM 能看到具体的错误信息。如果你打算把 OpenClaw 用在长期运行的编码 Agent 场景建议关注 TaoToken 的 Coding Plan它针对高频工具调用做了配额优化比按量计费更适合 Agent 这种 token 消耗大户。接入文档里有完整的 Base URL 和 Model ID 对照表配置时直接复制即可。最后说一个我踩过的坑OpenClaw 的沙箱写入工具默认只允许写sandboxRoot下的路径但exec工具不受这个限制。如果你让 LLM 执行echo test /tmp/foo它会成功写入沙箱外的文件。修复方法是在createExecTool的配置里加上cwd: sandboxRoot并限制命令白名单。这个细节在官方文档里没有强调但生产环境必须处理。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →