OpenClaw Skill 开发避坑指南:从 Hello World 到接入企业级 API,Token 到底花哪了?
摘要: 最近傅盛在朋友圈力荐 OpenClaw,让这个 24 万 Star 的项目又火了一把。但这东西光跑个 Hello World 没意义,真正的价值在于接入你自己的业务 API。周末花了两天时间,给 OpenClaw 写了个对接私有 CRM 的插件,顺便扒了下它的源码,发现 Skill 开发里全是坑——尤其是 Token 消耗逻辑,如果不做截断,一晚上能跑掉你半个月工资。这篇文章不聊虚的,直接上代码,教你怎么写 Skill,以及怎么省钱。
前言
OpenClaw 最近的风很大,号称是 AI Agent 的 iPhone 4 时刻。
作为一个跟 API 打交道的开发者,我更关心的是:它能不能接管我的业务逻辑?
比如,我能不能让它“查询这周销售额低于 5000 的客户,并给他们发一张优惠券”?
这需要两个能力:
- 查询 CRM 数据(Read)。
- 调用发券接口(Write)。
OpenClaw 的核心架构是 Brain (LLM) + Body (Tools)。我们要做的,就是给 Body 装上一双“手”——也就是 Custom Skill。
01. 动手写一个 Skill:比你想象的简单
OpenClaw 的插件系统设计得非常像 NestJS,用了大量的 TypeScript 装饰器。这比那堆要把 JSON Schema 写在 YAML 里的 Agent 框架强太多了。
假设我们要写一个 CRMSkill。
第一步:定义 Class
在 src/skills 下新建 crm-skill.ts:
import { Skill, Action, Parameter } from '@openclaw/core';
export class CrmSkill extends Skill {
// Skill 的元数据,LLM 会读取这个描述来决定是否调用这个 Skill
name = "crm_system";
description = "Access enterprise CRM to manage customers and orders.";
// ... Actions go here
}
第二步:定义 Action
这是最关键的一步。你需要用 @Action 告诉 LLM 这个函数是干嘛的。
@Action({
name: 'get_low_sales_customers',
description: 'Find customers with sales amount lower than a threshold in the current week.'
})
async getLowSalesCustomers(
@Parameter({ description: 'Sales threshold amount', required: true })
threshold: number
) {
// 模拟调用你的后端 API
const response = await fetch(`https://api.my-company.com/v1/customers?sales_lt=${threshold}`, {
headers: { 'Authorization': process.env.CRM_API_KEY }
});
const data = await response.json();
return data;
}
第三步:注册
在 config.json 的 skills 数组里加上它。重启 OpenClaw,LLM 就能看到这个工具了。
02. 踩坑实录:Token 是怎么爆炸的?
跑通上面的代码只需要 10 分钟。但接下来的问题,花了我 5 个小时排查。
当我问:“帮我找销售额低的用户”时,OpenClaw 居然卡死了,然后后台报 Token Limit Exceeded。
原因分析
我扒了一下 OpenClaw 处理 Tool Output 的源码(在 packages/core/src/agent/thinking.ts 里)。
它会把 Action 返回的 所有 JSON 数据,原封不动地塞回给 LLM 的 Context,作为 Observation。
我的 CRM 接口返回了 50 个客户,每个客户对象里包含了 address, history_orders, logs 等一大堆冗余字段。
这一坨 JSON 加起来有 15k Tokens。
而我用的 Claude 3.5 Sonnet,虽然 Context Window 很大,但一次回复的 Output Token 是有限制的。LLM 看到这么多数据,直接懵了,或者在总结时超长。
优化方案:手动截断
永远不要相信 LLM 的自我筛选能力,在代码层就把垃圾数据过滤掉。
修改 getLowSalesCustomers 方法:
const data = await response.json();
// 只返回 LLM 决策需要的最小数据集!
return data.map(customer => ({
id: customer.id,
name: customer.name,
sales: customer.total_sales,
email: customer.email // 发优惠券只需要 ID 和 Email
})).slice(0, 20); // 强制限制条数,防止分页爆炸
改完之后,Token 消耗从 15k 降到了 300,响应速度从 40 秒变成了 3 秒。
03. 进阶:如何处理异步任务?
如果你的 API 很慢(比如视频生成,或者复杂报表),OpenClaw 会傻等导致超时。
我看了一下源码,OpenClaw 目前对 Async 的支持还比较原始。
最佳实践是:由 Skill 返回一个 Task ID,然后让 OpenClaw 轮询。
@Action({ description: 'Start a report generation task' })
async startReport() {
const { taskId } = await api.start();
return { status: 'pending', taskId, message: 'Task started. Please check status in 1 minute.' };
}
@Action({ description: 'Check report status' })
async checkStatus(@Parameter(...) taskId: string) {
// ...
}
你需要要在 Prompt 里(或者 system prompt)教导 LLM:“如果收到 pending 状态,请等待一会再调用 checkStatus”。
总结
OpenClaw 的代码质量确实不错,TypeScript 的类型系统让二次开发很舒服。
但它终究是个通用的 Agent 框架。落地到企业级场景,你必须通过 Skill 做好两件事:
- 数据清洗:别把原始数据直接喂给 LLM,那是烧钱。
- 错误边界:API 挂了要返回人类可读的 Error Message,而不是 Stack Trace。
浙公网安备 33010602011771号