1. 从零跑通第一个 OpenClaw Skill:为什么统一 Key 是调试链路的关键
如果你有 Node.js 或 TypeScript 背景,第一次接触 OpenClaw Skill 开发,最容易卡住的地方往往不是写业务逻辑,而是调试链路本身。Skill 是一个独立的 Node.js 模块,它需要被 OpenClaw CLI 动态加载、执行、输出日志,而在这个过程中,只要模型调用这一环的鉴权配置没打通,你看到的就只会是401、local proxy failed或者干脆没有任何返回。所以这一章我不打算只讲概念,而是带你从项目骨架开始,一步步把 Skill 跑起来,并且用 TaoToken 的统一 Key 把模型调用这条链路固定下来,让后续每一次claw run都能稳定复现。
先明确一下这一章适合谁:你最好已经装好了 Node.js 22+,会用 npm,能看懂 TypeScript 的 interface 和 class,知道 CLI 是什么。如果你之前只写过前端或者纯脚本,也没关系,我会把每一步命令和配置都写全。Skill 本质上就是一个遵循 OpenClaw 规范的 Node.js 包,它通过handleCommand接收命令和参数,返回结构化结果。你可以把它理解成给智能体装的一个“技能包”,每个包负责一类能力,比如计算、文件处理、HTTP 请求。OpenClaw 负责调度,Skill 负责执行。
那为什么要在 Skill 开发里引入 TaoToken?因为 Skill 在本地调试时,经常需要调用大模型来做意图解析、参数补全或者结果润色。如果你每个 Skill 都单独配一套模型 Key,调试时会非常混乱:这个 Skill 用 A Key,那个用 B Key,日志里根本分不清是谁在请求。TaoToken 提供的是统一 Key 和统一 Base URL,你只需要在 OpenClaw 的config.toml里配置一次,所有 Skill 共享同一个入口。这样你在排查问题时,只需要看一个地方的日志,链路清晰很多。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数,配置时直接写这个就行。
我试过在多个 Skill 之间来回切换调试,最痛苦的就是 Key 散落在各个.env文件里,改一个忘一个。统一到 TaoToken 之后,config.toml里只有一份api_key和base_url,Skill 代码里通过context.env读取,既安全又方便。接下来我会先给你一个可复制的 Skill 项目骨架,然后给出config.toml的完整片段,最后用 CLI 命令验证请求是否真的打通。整个过程你都可以跟着做,不需要额外申请一堆账号。
2. TaoToken 前置准备:统一 Key 与 config.toml 配置片段
在开始写 Skill 之前,先把 TaoToken 的 Key 准备好。打开 https://taotoken.net/api-keys ,登录后创建一个 API Key。这个 Key 就是你后面所有 Skill 共用的凭证。创建时建议起一个能识别的名字,比如openclaw-skill-dev,方便以后在控制台里区分。拿到 Key 之后不要直接写进代码,而是放进 OpenClaw 的配置文件里。OpenClaw CLI 默认会读取用户目录下的~/.openclaw/config.toml,你也可以在项目目录里放一个config.toml做局部覆盖。下面是一个完整的配置片段,你可以直接复制,把sk-开头的那串换成你自己的 Key。
# file: ~/.openclaw/config.toml # description: OpenClaw CLI 全局配置,统一模型入口 [model] provider = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" default_model = "claude-sonnet-4-20250514" timeout_ms = 60000 [model.fallback] enabled = true models = ["gpt-4o-mini", "claude-3-5-haiku-20241022"] [skill] dev_mode = true log_level = "debug" hot_reload = true [skill.sandbox] allow_network = true allow_file_read = ["./data", "./assets"] allow_file_write = ["./dist", "./logs"]这里有几个点需要说明。base_url写https://taotoken.net/api,不要在后面加斜杠或者多余路径,OpenClaw 会自动拼接/v1/chat/completions这类端点。default_model我填的是 Claude 系列,你也可以换成其他支持的模型 ID,具体以模型对话页面里列出的为准。fallback是可选的,当主模型超时或者返回错误时,会自动切到备用模型,这在调试阶段很有用,避免因为单次网络抖动就中断整个 Skill 流程。
配置写好后,用一条命令验证 OpenClaw 能不能读到:
claw config get model.base_url # 期望输出:https://taotoken.net/api claw config get model.api_key # 期望输出:sk-****(脱敏显示)如果这两条命令能正常返回,说明配置已经生效。接下来在 Skill 代码里,你不需要再硬编码任何 Key,直接通过context.env或者 OpenClaw 注入的模型客户端来调用。比如在SkillContext里,context.env会包含TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL这两个变量,这是 OpenClaw 在加载 Skill 时自动注入的。你可以在代码里这样读取:
// file: src/utils/model.ts // description: 从上下文读取统一模型配置 import { SkillContext } from '@openclaw/types'; export function getModelConfig(context: SkillContext) { const apiKey = context.env.TAOTOKEN_API_KEY; const baseUrl = context.env.TAOTOKEN_BASE_URL || 'https://taotoken.net/api'; if (!apiKey) { throw new Error('TAOTOKEN_API_KEY 未注入,请检查 config.toml 中的 model.api_key'); } return { apiKey, baseUrl }; }这样写的好处是,Skill 本身不关心 Key 从哪来,只关心上下文里有没有。如果你以后要把 Skill 分享给别人,别人只需要在自己的config.toml里填自己的 Key,代码完全不用改。这就是统一 Key 的价值:配置和代码解耦,调试链路可复现。
3. 可复制配置:Skill 项目骨架与 package.json 完整片段
现在开始搭项目骨架。你可以用 OpenClaw CLI 自带的模板生成,也可以手动创建。我建议先用 CLI 生成,再按自己的需求改,这样不容易漏掉规范要求的字段。命令如下:
# 安装 OpenClaw CLI(如果还没装) npm install -g @openclaw/cli # 验证版本,建议 2026.3.x 以上 claw --version # 创建 Skill 项目 claw create skill-demo # 交互式选择:基础模板 -> TypeScript -> 需要示例代码 cd skill-demo npm install生成后的目录结构大致是这样:
skill-demo/ ├── src/ │ ├── index.ts # Skill 主入口 │ ├── types.ts # 类型定义 │ ├── utils/ │ │ └── model.ts # 模型调用封装 │ └── services/ │ └── demo.ts # 业务逻辑 ├── tests/ │ └── index.test.ts ├── docs/ │ └── README.md ├── assets/ │ └── icon.png ├── package.json ├── tsconfig.json ├── jest.config.js └── .clawignore重点看package.json里的openclaw字段,这是 Skill 的元信息核心。下面是一个可以直接复制的完整片段,我加上了模型调用相关的环境变量声明:
{ "name": "skill-demo", "version": "1.0.0", "description": "OpenClaw Skill 开发调试示例", "main": "dist/index.js", "types": "dist/index.d.ts", "files": ["dist", "assets"], "keywords": ["openclaw-skill", "demo", "tool"], "author": "Your Name", "license": "MIT", "openclaw": { "skillId": "demo", "skillName": "调试示例", "version": "1.0.0", "description": "用于验证 TaoToken 统一 Key 调用链路的示例 Skill", "category": "开发类", "icon": "assets/icon.png", "permissions": ["network:access", "file:read"], "env": [ { "name": "TAOTOKEN_API_KEY", "description": "TaoToken 统一 API Key", "required": true, "secret": true }, { "name": "TAOTOKEN_BASE_URL", "description": "TaoToken API 入口地址", "required": false, "default": "https://taotoken.net/api" } ], "commands": [ { "name": "ask", "description": "向模型发送一条消息并返回结果", "parameters": [ { "name": "prompt", "type": "string", "description": "用户输入的问题", "required": true } ] } ] }, "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "jest", "lint": "eslint src/**/*.ts", "package": "claw package" }, "dependencies": { "@openclaw/types": "^2026.3.0", "mathjs": "^13.0.0" }, "devDependencies": { "typescript": "^5.0.0", "jest": "^29.0.0", "ts-jest": "^29.0.0", "@types/jest": "^29.0.0", "eslint": "^9.0.0" } }这里env数组里声明了TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL,OpenClaw 在加载 Skill 时会自动从全局config.toml里读取对应的值并注入。注意secret: true表示这个变量在日志里会被脱敏,不会明文打印。permissions里我加了network:access,因为要调用模型 API;file:read是为了读取本地配置或数据文件。如果你不需要文件操作,可以去掉。
接下来写src/index.ts,实现一个最小的ask命令,把用户输入转发给 TaoToken 的模型接口,并返回结果。代码里用fetch直接请求,这样你能清楚看到请求链路:
// file: src/index.ts // description: 最小可调试 Skill,验证 TaoToken 统一 Key 调用 import { Skill, SkillContext, CommandResult } from '@openclaw/types'; class DemoSKill implements Skill { metadata = { id: 'demo', name: '调试示例', version: '1.0.0', description: '验证 TaoToken 统一 Key 调用链路', }; async handleCommand( command: string, params: Record<string, any>, context: SkillContext ): Promise<CommandResult> { switch (command) { case 'ask': return this.ask(params.prompt, context); default: return { success: false, error: `未知命令: ${command}` }; } } private async ask(prompt: string, context: SkillContext): Promise<CommandResult> { if (!prompt || typeof prompt !== 'string') { return { success: false, error: '请提供有效的 prompt 参数' }; } const apiKey = context.env.TAOTOKEN_API_KEY; const baseUrl = context.env.TAOTOKEN_BASE_URL || 'https://taotoken.net/api'; if (!apiKey) { return { success: false, error: 'TAOTOKEN_API_KEY 未注入,请检查 config.toml' }; } try { const response = await fetch(`${baseUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', messages: [{ role: 'user', content: prompt }], max_tokens: 512, }), }); if (!response.ok) { const errText = await response.text(); context.logger.error(`模型请求失败: ${response.status} ${errText}`); return { success: false, error: `模型请求失败: ${response.status}` }; } const data = await response.json(); const content = data.choices?.[0]?.message?.content ?? ''; context.logger.info(`模型返回: ${content.slice(0, 80)}...`); return { success: true, data: { prompt, content }, message: content, }; } catch (error: any) { context.logger.error(`请求异常: ${error.message}`); return { success: false, error: `请求异常: ${error.message}` }; } } } export default new DemoSKill();这段代码里,context.env.TAOTOKEN_API_KEY就是 OpenClaw 从config.toml注入进来的。你不需要在 Skill 里写任何 Key,也不需要.env文件。请求地址是${baseUrl}/v1/chat/completions,其中baseUrl默认就是https://taotoken.net/api。如果你在config.toml里改了base_url,这里会自动跟着变。
4. 验证请求与成功结果:CLI 调用与日志排查
代码写完后,先构建再安装到本地 OpenClaw:
npm run build claw install .安装成功后,用claw list确认 Skill 已经被识别:
claw list # 期望输出中包含: # demo 调试示例 1.0.0 开发类 enabled然后运行ask命令:
claw run demo ask --prompt "用一句话解释什么是 OpenClaw Skill"如果一切正常,你会看到类似这样的输出:
[INFO] 模型返回: OpenClaw Skill 是一个可被智能体动态加载的 Node.js 模块... 计算结果:OpenClaw Skill 是一个可被智能体动态加载的 Node.js 模块,用于扩展智能体的能力。同时,日志里会记录请求的完整链路。你可以用claw logs demo -f实时查看:
claw logs demo -f # 输出示例: # [2026-03-09 10:12:01] [INFO] Skill demo 加载完成 # [2026-03-09 10:12:03] [DEBUG] 请求 URL: https://taotoken.net/api/v1/chat/completions # [2026-03-09 10:12:03] [DEBUG] 使用模型: claude-sonnet-4-20250514 # [2026-03-09 10:12:05] [INFO] 模型返回: OpenClaw Skill 是一个...这里的关键验证点是:日志里打印的请求 URL 必须是https://taotoken.net/api/v1/chat/completions,而不是其他地址。如果 URL 不对,说明config.toml里的base_url写错了。另外,日志里不应该出现明文 Key,因为secret: true已经做了脱敏。如果你看到sk-开头的完整字符串,说明脱敏没生效,需要检查env声明里的secret字段。
再做一个异常验证:故意把config.toml里的api_key改错,然后重新运行:
claw run demo ask --prompt "测试错误 Key" # 期望输出: # [ERROR] 模型请求失败: 401 {"error":{"message":"Invalid API key"}} # 返回:模型请求失败: 401这个 401 是预期内的,说明鉴权链路是通的,只是 Key 不对。改回正确的 Key 后,再次运行就能恢复正常。通过这种“先成功再失败再成功”的验证方式,你可以确认整条链路是可观测、可复现的。
如果你在日志里看到local proxy failed,通常是因为 OpenClaw 在本地起了代理但没连上上游。这时候先检查config.toml里的base_url是否可达,可以用curl直接测:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的Key" \ -d '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"ping"}],"max_tokens":10}'如果curl能返回正常 JSON,说明网络和 Key 都没问题,问题出在 OpenClaw 的配置加载上。这时候用claw config list看一下实际生效的配置,确认model.base_url和model.api_key是不是你期望的值。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
在 Skill 调试过程中,有几个报错几乎每个人都会遇到。我把它们整理成对照表,方便你快速定位。
| 报错信息 | 常见原因 | 排查动作 |
|---|---|---|
401 Invalid API key | Key 错误、过期或未注入 | 检查config.toml的model.api_key,用claw config get model.api_key确认 |
local proxy failed | Base URL 不可达或代理配置冲突 | 用curl直接测https://taotoken.net/api/v1/chat/completions,检查base_url是否有多余路径 |
Cannot read properties of undefined (reading 'choices') | 响应结构不符合预期,通常是请求被拦截或返回了错误页 | 打印完整响应体,检查response.ok和response.status,确认返回的是 JSON 而不是 HTML |
OAuth token expired | 使用了 OAuth 方式鉴权但 token 过期 | 改用 API Key 方式,在config.toml里配置api_key而不是 OAuth |
Skill not found | Skill 未安装或未启用 | 运行claw list确认状态,用claw install .重新安装 |
Command not found | 命令名拼写错误或未在package.json中声明 | 检查openclaw.commands数组里的name字段 |
重点说两个。第一个是reading 'choices',这个报错的意思是代码里访问了data.choices[0],但data里没有choices字段。最常见的情况是请求返回了 401 或者 429,但代码没有先判断response.ok就直接response.json()。所以我在上面的示例代码里先判断了response.ok,如果不是 2xx 就直接返回错误,避免继续解析。你如果自己写的时候漏了这一步,就会看到这个报错。
第二个是OAuth token expired。有些开发者习惯用 OAuth 登录 OpenClaw 开发平台,但 Skill 运行时调用模型 API 用的是另一套鉴权。如果你在config.toml里没有显式配置api_key,OpenClaw 可能会尝试用 OAuth token 去请求模型接口,而 TaoToken 的 API 入口需要的是 API Key。解决办法很简单:确保config.toml的[model]段里有api_key字段,并且值是以sk-开头的 Key。如果你同时配置了 OAuth 和 API Key,OpenClaw 会优先使用 API Key。
还有一个容易忽略的点:如果你在 Skill 代码里用了context.env.TAOTOKEN_API_KEY,但package.json的openclaw.env数组里没有声明这个变量,OpenClaw 就不会注入它。这时候context.env.TAOTOKEN_API_KEY会是undefined,代码里如果没做判空,就会直接抛错。所以每次新增环境变量,都要同步更新package.json里的env声明。
排查完之后,如果你需要重新生成 Key 或者查看调用量,可以去 https://taotoken.net/api-keys 管理。如果是要看模型列表和可用性,去模型对话页面 https://taotoken.net/models 确认。这两个入口在调试阶段会经常用到。
6. 语义一致 CTA:把调试链路固定下来,继续下一步
走到这里,你已经完成了一个可运行、可调试的 OpenClaw Skill:项目骨架建好了,config.toml里配了 TaoToken 统一 Key,ask命令能正常请求模型并返回结果,日志里能看到完整的请求 URL 和模型返回。更重要的是,你知道 401、local proxy failed、reading choices 这些报错分别对应哪一层的问题,下次再遇到就不会卡住。
接下来你可以做两件事。第一,把这个 Skill 扩展成真正有用的工具,比如加一个summarize命令,读取本地文件并调用模型做摘要;或者加一个translate命令,做中英互译。每次扩展都复用同一套config.toml和context.env,不需要重新配 Key。第二,如果你打算长期做 Skill 开发,建议把 Coding Plan 用起来,它适合需要持续调用模型、跑 Agent 流程的场景,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。如果你只是想先验证模型调用是否稳定,可以到模型对话页面直接测试 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。需要管理 Key 和查看用量,就去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有更完整的参数说明和示例。
最后留一个实用技巧:在config.toml里把log_level设成debug,然后在 Skill 代码里用context.logger.debug打印关键变量,比如请求 URL、模型 ID、响应状态码。这样当你换模型或者换环境时,一眼就能看出是哪一层变了。调试链路一旦固定下来,后面写再多 Skill 都只是在这个基础上叠加业务逻辑,不会再被鉴权和配置问题打断。