ARTICLE DETAIL

资讯详情

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

从 OpenClaw 源码解析:如何构建一个 Agent(TaoToken 配置骨架版)

从 OpenClaw 源码解析:如何构建一个 Agent(TaoToken 配置骨架版) 1. 从 OpenClaw 源码看 Agent 到底由什么组成OpenClaw 是一个跑在生产环境里的 AI Agent 框架代码量不小但核心就四个模块——执行循环、工具系统、记忆系统、插件系统。这篇文章把每个模块拆开看看里面怎么写的最后整理一份自己做 Agent 时可以参考的清单并且给出一份可以直接复制的 TaoToken 配置骨架让你在本地把 Agent 跑起来。如果你熟悉 TypeScript 和 LLM API 的基本概念读起来会很顺。如果你还不了解 Function Calling建议先补一下工具调用Tool Use的基础概念再回来看源码结构会清晰很多。一句话概括 OpenClaw 的架构Gateway 接收消息 → Agent 循环调用 LLM 工具 → 记忆系统提供上下文 → 插件扩展一切。四个模块各管各的耦合度不高。先扫一眼目录结构目录一句话说明src/agents/Agent 执行循环、工具注册、模型管理src/memory/记忆索引、嵌入向量、混合检索src/gateway/WebSocket 网关、认证、RPCsrc/plugin-sdk/插件 SDK、Hook 系统src/channels/通道抽象层状态机、路由、线程绑定extensions/73 个插件通道 / LLM Provider / 工具扩展这篇的重点不是把每个文件都念一遍而是把「构建一个 Agent 需要哪些零件」讲清楚然后给你一份能直接跑的配置骨架。LLM 调用通道这块我用 TaoToken 做统一入口一个 Key 就能覆盖多种模型省得在多个 Provider 之间来回切。2. TaoToken 前置统一 Key 与 API 通道在动手写 Agent 之前先把 LLM 调用通道准备好。OpenClaw 的模型管理模块src/agents/ 下的 Provider 相关代码本质上就是维护一组「Provider Auth Profile 模型名」的映射然后按优先级做 Failover。你自己做 Agent 时如果每个 Provider 都单独配 Key、单独处理限流和重试代码会迅速膨胀。TaoToken 在这里扮演的角色是统一入口一个 API Key一个 Base URL就能调用多种模型。对 Agent 来说这意味着你的 LLM 调用层只需要维护一套认证逻辑模型切换只是改一个字符串。你需要准备的东西一个 TaoToken 账号登录后在控制台创建 API Key记下 API Base URLhttps://taotoken.net/api选一个默认模型名比如claude-sonnet-4-20250514或gpt-4o具体以控制台模型列表为准控制台入口在这里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Key 管理页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite拿到 Key 之后先别急着写 Agent用一条 curl 验证通道是否通curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -d { model: claude-sonnet-4-20250514, messages: [{role: user, content: 只回复两个字通了}], max_tokens: 32 }如果返回里能看到choices[0].message.content说明通道没问题。这一步很重要因为后面 Agent 报错时你需要快速判断是「通道问题」还是「Agent 逻辑问题」。把通道单独验证过排障范围就缩小了一半。注意API Key 不要硬编码进源码。用环境变量TAOTOKEN_API_KEY或者放进.env文件并加进.gitignore。3. 可复制配置settings.json 与 config.toml 骨架OpenClaw 的配置入口分散在几个地方但核心就两类一类是「运行时配置」模型、通道、记忆一类是「Agent 行为配置」System Prompt、工具开关、循环上限。下面给两份骨架你可以直接复制到自己的项目里改。3.1 settings.jsonAgent 运行时配置这份配置对应 OpenClaw 里src/agents/和src/memory/的初始化参数。我把它整理成一份扁平结构方便你对照源码理解每个字段的作用{ agent: { id: my-first-agent, name: 本地 Agent 雏形, maxToolRounds: 12, stream: true, systemPromptFile: ./prompts/system.md }, llm: { provider: taotoken, baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, model: claude-sonnet-4-20250514, fallbackModels: [gpt-4o, claude-haiku-4-20250514], timeoutMs: 60000, maxRetries: 3 }, tools: { enabled: [read_file, write_file, exec, web_search], requireConfirm: [write_file, exec], loopDetection: { windowSize: 30, warnAt: 10, stopAt: 20, abortAt: 30 } }, memory: { enabled: true, dbPath: ./data/memory.sqlite, embeddingProvider: taotoken, embeddingModel: text-embedding-3-small, hybridWeights: { vector: 0.7, bm25: 0.3 }, temporalDecayHalfLifeDays: 30 } }几个字段值得单独说maxToolRounds对应 OpenClaw 里工具循环的上限。设太小复杂任务跑不完设太大一旦逻辑出错会烧很多 token。12 是个比较稳的起步值。fallbackModels就是 Failover 的简化版。主模型限流或报错时按顺序切下一个。OpenClaw 的run.ts里做得更细会冷却出问题的 Auth Profile但核心思路一致。loopDetection直接抄了 OpenClaw 的三级熔断10 次警告、20 次强制提示、30 次终止。滑动窗口大小 30 是它的默认值。3.2 config.toml通道与网关配置如果你要接消息通道Discord、Slack、Web UI这部分对应src/gateway/和src/channels/[gateway] host 127.0.0.1 port 8787 auth_token_env GATEWAY_TOKEN [channels.web] enabled true path /chat [channels.discord] enabled false bot_token_env DISCORD_BOT_TOKEN [state_machine] idle_timeout_sec 300 max_concurrent_sessions 4 [logging] level info file ./logs/agent.logstate_machine这段对应 OpenClaw 的src/channels/run-state-machine.ts。每个会话有独立状态idle → running → drafting → completed保证同一会话不会被并发请求搞乱。你自己做的时候哪怕先不做完整状态机至少也要给每个会话加一把锁。3.3 System Prompt 骨架OpenClaw 的system-prompt.ts是动态拼装的——运行时信息、工具列表、通道能力、用户指令按需注入。你可以先从一个静态文件开始你是运行在本地环境中的 AI Agent。 ## 运行环境 - 操作系统{{os}} - 当前时间{{now}} - 工作目录{{cwd}} ## 可用工具 {{tool_list}} ## 行为准则 1. 需要读取文件时先调用 read_file不要凭记忆猜测内容。 2. 执行有副作用的操作写文件、跑命令前先说明你要做什么。 3. 如果连续两次工具调用没有进展停下来向用户确认。{{tool_list}}由代码在启动时注入格式就是工具名 描述。LLM 靠这段描述判断什么时候该调哪个工具所以描述要写清楚「做什么」和「什么时候用」。4. 验证请求本地启动并跑通一次 Agent 响应配置写好了接下来把它跑起来。下面给一个最小可运行的 TypeScript 入口对应 OpenClaw 的src/agents/pi-embedded-runner/run/attempt.ts——单次 LLM 调用加工具循环。4.1 安装依赖npm init -y npm install openai dotenv npm install -D typescript tsx types/node这里用openai这个 SDK 就行因为 TaoToken 的 API 兼容 OpenAI 的请求格式改一下baseURL就能用。4.2 核心循环代码// src/agent.ts import OpenAI from openai; import dotenv/config; const client new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY!, baseURL: https://taotoken.net/api/v1, }); const tools [ { type: function as const, function: { name: read_file, description: 读取指定路径的文件内容, parameters: { type: object, properties: { path: { type: string } }, required: [path], }, }, }, ]; async function runAgent(userInput: string) { const messages: OpenAI.Chat.ChatCompletionMessageParam[] [ { role: system, content: 你是一个本地 Agent需要读文件时调用 read_file。 }, { role: user, content: userInput }, ]; for (let round 0; round 12; round) { const res await client.chat.completions.create({ model: claude-sonnet-4-20250514, messages, tools, stream: false, }); const msg res.choices[0].message; messages.push(msg); if (!msg.tool_calls || msg.tool_calls.length 0) { console.log(最终回复, msg.content); return msg.content; } for (const call of msg.tool_calls) { const args JSON.parse(call.function.arguments); let result ; if (call.function.name read_file) { try { result await import(fs/promises).then((fs) fs.readFile(args.path, utf-8)); } catch (e) { result 读取失败${(e as Error).message}; } } messages.push({ role: tool, tool_call_id: call.id, content: result.slice(0, 4000), }); } } throw new Error(工具循环超过上限已终止); } runAgent(读一下 package.json告诉我项目名和依赖数量);4.3 运行与预期结果npx tsx src/agent.ts正常的话你会看到 Agent 先发起一次read_file工具调用拿到文件内容后再生成一段自然语言回复类似最终回复项目名是 my-agent-demodependencies 里有 2 个依赖openai 和 dotenv。这个过程就是 Agent 的最小骨架LLM 决定调工具 → 代码执行工具 → 结果喂回 LLM → LLM 生成最终回复。OpenClaw 的attempt.ts做的也是这件事只是外面包了流式处理、容错、上下文压缩。4.4 加上流式输出把stream: false改成true然后处理text_delta事件用户就能边生成边看到字。OpenClaw 的pi-embedded-subscribe.ts就是干这个的它还会在语义边界处切分文本块避免把半个句子推给用户。const stream await client.chat.completions.create({ model: claude-sonnet-4-20250514, messages, tools, stream: true, }); for await (const chunk of stream) { const delta chunk.choices[0]?.delta; if (delta?.content) process.stdout.write(delta.content); }流式模式下工具调用的参数是分片到达的需要自己拼接tool_calls的arguments字符串等finish_reason变成tool_calls再解析。这是新手最容易踩的坑之一。5. 本篇常见错排查5.1 401 / 403Key 没读到或格式不对最常见的原因是环境变量没加载。dotenv/config要在最顶部导入且.env文件里写的是TAOTOKEN_API_KEYsk-xxx不要加引号。如果用的是 shell 导出确认echo $TAOTOKEN_API_KEY有输出。另一个原因是 Base URL 写错。注意区分https://taotoken.net/api是根路径SDK 里通常要写https://taotoken.net/api/v1。如果你用的是原生 fetch 拼/v1/chat/completions那就用根路径。5.2 模型名报错 model_not_found模型名要以控制台模型列表为准不要凭记忆写。不同 Provider 的命名风格不一样有的带日期后缀有的不带。建议把模型名放进配置文件别散落在代码里。5.3 工具调用死循环表现是 Agent 反复调同一个工具token 哗哗烧。原因通常是工具返回的结果 LLM 看不懂或者 System Prompt 没告诉它「拿到结果后该干什么」。排查方法把每轮的工具调用名和参数打出来看是不是同一个调用重复出现。修复方法有两个一是把工具返回内容结构化别返回一坨乱码二是在循环里加计数超过阈值就强制让 LLM 生成最终回复。const callCount new Mapstring, number(); // 在每次工具调用前 const key ${call.function.name}:${call.function.arguments}; const n (callCount.get(key) ?? 0) 1; callCount.set(key, n); if (n 3) { messages.push({ role: user, content: 同一工具已重复调用多次请基于现有信息直接回答。 }); }5.4 上下文超长导致请求失败长对话跑到后面messages 数组会超过模型窗口。OpenClaw 的做法是用 Context Engine 对历史做摘要压缩compact.ts而不是简单砍掉前面的消息。你自己实现时可以先做一个简化版保留 System Prompt 和最近 N 轮对话把更早的内容用一次 LLM 调用总结成一段话。5.5 流式模式下工具调用解析失败前面提过流式返回的tool_calls是分片的。delta.tool_calls[0].function.arguments每次只给你一小段 JSON 字符串需要按index累积拼接等流结束后再JSON.parse。直接对每个 chunk 解析会报Unexpected end of JSON input。5.6 记忆检索返回空结果如果你接了记忆系统搜索时返回空先检查三件事嵌入模型是否配置正确、SQLite 里chunks表是否有数据、查询向量维度是否和存储时一致。维度不一致是最隐蔽的坑比如存储用 1536 维查询用了 1024 维的模型相似度计算会直接失效。6. 把 Agent 跑起来之后下一步做什么到这里你已经有了一个能跑的最小 Agent统一 Key 通道、可复制配置、工具循环、流式输出、基础排障。接下来按优先级补三件事。第一是容错。裸循环跑 demo 没问题上线必须加 Auth Failover 和上下文压缩。前者解决限流和 Key 过期后者解决长对话崩溃。这两块 OpenClaw 的run.ts和compact.ts都有现成思路可以抄。第二是记忆。让 Agent 从「一次性对话」变成「持续助手」最小实现就是 SQLite 加向量检索加 FTS5 全文兜底。嵌入模型选text-embedding-3-small就够用便宜且稳定。混合检索权重先用 0.7 向量加 0.3 BM25跑一段时间再调。第三是扩展。等你要接第二个通道、加第三个工具的时候再考虑插件化和 Hook 系统。不用一开始就做 25 个 Hook先从before_prompt_build和before_tool_call这两个高频的做起。如果你在接入过程中遇到通道或 Key 的问题可以直接去 API Keys 页面重新生成一个验证https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite想先验证模型响应是否符合预期可以用模型对话页面快速试一条https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite如果你打算长期跑编码类 Agent 或做多轮工具调用Coding Plan 会更省心额度和模型调度都帮你管好了https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite接入文档在这里配置字段和错误码都有说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite我自己的习惯是每加一个新工具先用模型对话页面手动构造一次工具调用请求确认返回格式对了再写进 Agent 代码。这样能把「模型问题」和「代码问题」分开排障快很多。
返回列表