写一个OpenClaw自定义Skill——从设计到落地的完整指南
前言
上个月有读者问我:”13个技能不够用怎么办?我想让 AI 查一下公司内部的数据库,但 OpenClaw 没有这个技能。”
答案是:自己写一个。
OpenClaw 的 Skill 体系设计得很克制——它不内置所有功能,而是给你一个扩展接口。任何一个能力,只要你能写成 Skill,AI 就能用。
这篇从设计理念讲到实现细节,再讲到发布和踩坑。目标是让你看完之后,能自己写出第一个 Skill。
(文章末尾附一个可以直接用的 MySQL 查询 Skill 模板。)
一、Skill 的设计哲学
在看代码之前,先理解 Skill 是什么、不是什么。
Skill 是什么
Skill 是 AI 能力的”最小可控单元”。
不是函数(Function),不是工具(Tool),不是插件(Plugin)。它的核心设计是:给 AI 一个明确的、可独立调用的能力边界。
每个 Skill 做一件事,且只做一件事。比如:
-
file skill → 读写文件 -
exec skill → 执行命令 -
web-search skill → 联网搜索
不能有一个”全能 Skill”既读文件又执行命令——那样 AI 的行为边界就模糊了。
为什么不直接用 Function Calling
你可能会问:OpenAI 的 Function Calling 也能做类似的事,为什么 OpenClaw 要搞一套自己的?
| 维度 | OpenAI Function Calling | OpenClaw Skill |
|---|---|---|
| 定义方式 | JSON Schema | Markdown 文档 |
| AI 如何调用 | 解析 JSON Schema | 理解 Markdown 描述 |
| 能力边界 | 每个 function 是一个 API | 每个 skill 是一个独立模块 |
| 配置复杂度 | 中 | 低 |
| 是否需要写代码 | 需要6 | 可选 |
核心区别:Function Calling 要求你写严格的 JSON Schema(字段类型、枚举值、必填/可选),而 Skill 只需要写一份 Markdown 文档告诉 AI”你能做什么、怎么用”。
OpenClaw 选择的路径是:让 AI 理解你的意图,而不是让你去适应 AI 的接口。
什么时候该写一个 Skill
-
OpenClaw 内置的 13 个技能覆盖不了你的需求 -
你想让 AI 操作一个特定的外部系统(数据库、企业内部 API) -
你想把重复操作封装成一个可复用的能力
如果只是简单问几个问题,不需要写 Skill——直接用对话就行。
二、Skill 的结构
一个 Skill 本质上是一个文件夹,包含以下文件:
my-skill/
├── SKILL.md # 技能描述(必须)
├── README.md # 使用说明(推荐)
├── config.json # 配置参数(可选)
└── 其他资源文件 # 按需
核心文件是 SKILL.md。AI 通过读这个文件来理解 Skill 能做什么、怎么调用。
三、写一个 MySQL 查询 Skill
从需求出发:我想让 AI 能查我的 MySQL 数据库。
Step 1:确定能力边界
这个 Skill 能做什么、不能做什么:
能做:
-
执行 SELECT 查询 -
查看表结构(DESCRIBE) -
返回查询结果(最多 100 行)
不能做:
-
执行 INSERT/UPDATE/DELETE -
修改数据库结构 -
删除数据 -
访问环境变量中的密码以外的文件
这些约束写在 SKILL.md 里。如果 AI 试图执行不允许的操作,Skill 会拒绝。
Step 2:写 SKILL.md
---
name: mysql-query
description: 查询 MySQL 数据库
---
# MySQL 查询工具
## 能力范围
- 执行 SELECT 语句读取数据
- 查看表结构
- 返回结果最多 100 行
- 查询超时 10 秒
## 不允许的操作
- INSERT / UPDATE / DELETE
- DROP / ALTER / CREATE
- 跨库查询
- 执行存储过程
## 使用方式
当用户需要查询数据库时,生成对应的 SQL 并调用此工具。
## 示例
用户:"查一下 users 表有多少人"
AI:SELECT COUNT(*) FROM users
这份文档就是 AI 理解这个 Skill 的说明书。越详细,AI 调用越准确。
Step 3:实现执行逻辑
SKILL.md 描述了”AI 能做什么”,还需要一个执行引擎来”真正去做”。
Skill 的执行逻辑用 Node.js 写:
// index.js
export default async function execute({ query, params }) {
const mysql = require('mysql2/promise')
const connection = await mysql.createConnection({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
})
try {
const [rows] = await connection.execute(query, params || [])
return { success: true, data: rows.slice(0, 100), rowCount: rows.length }
} catch (error) {
return { success: false, error: error.message }
} finally {
await connection.end()
}
}
关键设计:返回结构化数据而非原始输出。AI 读结构化数据比读原始控制台输出快得多。
Step 4:配置
{
"skills": {
"custom": "~/my-skill"
}
}
重启 Gateway 后生效:
supervisorctl restart openclaw-gateway
Step 5:测试
"查一下 users 表有几个用户"
→ AI 应该调用 mysql-query skill
→ 返回用户总数
→ AI 用自然语言回复你
四、踩坑记录
踩坑1:SKILL.md 写得太简单,AI 调用不准确
第一次只写了 3 行描述,结果 AI 把 MySQL 查询技能用成了文件读取——它以为”查询”可以是任何东西。
解决: SKILL.md 必须写清楚”能做什么”和”不能做什么”。能力范围越精确,AI 调用越准确。
踩坑2:环境变量泄漏
在代码里写死了数据库密码,差点提交到 GitHub。
解决: 用环境变量引用,密码写在 .env 里,不进入git。
password: process.env.MYSQL_PASSWORD
踩坑3:没有限制查询时间
某次 AI 跑了一个全表 JOIN,查询跑了 3 分钟才超时。
解决: 在代码里加上查询超时:
const connection = await mysql.createConnection({ ... })
await connection.query('SET max_execution_time = 10000') // 10秒超时
踩坑4:多个 Skill 的上下文冲突
AI 同时调用了 mysql-query 和 file skill,结果把查询结果写到了文件里——不是我想要的。
解决: 在 SKILL.md 里明确”这个 Skill 不负责写文件”,或者在 Agent 配置里让这两个 Skill 分给不同的 Agent:
{
"agents": {
"analyst": { "skills": ["mysql-query"] },
"operator": { "skills": ["file", "exec"] }
}
}
五、对比:自定义 Skill vs 其他扩展方式
| 维度 | 自定义 Skill | 写一个插件 | 用 MCP Server |
|---|---|---|---|
| 复杂度 | 低(写一个文件) | 中(npm包结构) | 中(要写Server) |
| 灵活性 | 高 | 中 | 高 |
| 可复用 | 当前实例 | 可发布到社区 | 跨平台兼容 |
| 适用场景 | 快速验证一个能力 | 分享给其他人用 | 多个平台共用 |
如果只是自己用,写 Skill 最快。如果要分享给别人,封装成插件。如果要兼容 OpenAI/Anthropic 等多个平台,走 MCP。
六、什么时候不该写 Skill
不是所有能力都值得封装成 Skill。
两条判断标准:
1. AI 直接做比写 Skill 更好
有些事 AI 直接用对话就能完成——”写一封邮件”不需要 Skill,让 AI 在对话里生成邮件内容就行。
2. 需要 AI 独立操作外部系统时,才值得写 Skill
-
操作数据库 → ✅ 写 Skill -
调用企业内部 API → ✅ 写 Skill -
读文件 → ❌ 内置 file skill 就够了 -
写邮件 → ❌ 对话生成内容就够了
七、锐度:Skill 体系设计得好不好
OpenClaw 的 Skill 体系有一个值得讨论的取舍——它的设计哲学是”AI 理解人类语言描述”,而不是”人适应机器接口”。
这个选择的好处是上手快(不需要学 JSON Schema),代价是长尾场景的精确性不如 Function Calling。
对于 80% 的场景,Skill 的 Markdown 描述足够精确了。如果你刚好在另外 20%,可以在 SKILL.md 里写更详细的约束,或者考虑用 MCP 协议做精确控制。
彩蛋:一个可直接使用的 Skill 模板
说好的彩蛋。一份可以直接用的 Skill 项目结构:
mkdir -p ~/.agents/skills/mysql-query
cd ~/.agents/skills/mysql-query
# 创建 package.json
cat > package.json << 'EOF'
{
"name": "mysql-query",
"version": "1.0.0",
"main": "index.js"
}
EOF
# 创建 .env
cat > .env << 'EOF'
MYSQL_HOST=localhost
MYSQL_USER=root
MYSQL_PASSWORD=your_password_here
MYSQL_DATABASE=your_database
EOF
# 创建 index.js(见上文代码)
# 创建 SKILL.md(见上文内容)
# 测试
node -e "const s = require('./index.js'); s.execute({query:'SELECT 1'}).then(console.log)"
配置到 OpenClaw 只需要一行:
{ "skills": { "custom": "~/.agents/skills/mysql-query" } }
重启即生效。
总结
写一个自定义 Skill 只需要三步:
-
确定能力边界——这个 Skill 能做什么、不能做什么 -
写 SKILL.md——告诉 AI 怎么用 -
实现执行逻辑——用 Node.js 写具体的操作逻辑
最难的不是写代码,是定义清楚”AI 能做什么、不能做什么”。边界越清晰,AI 的表现越可控。
你现在有需要自己写 Skill 的场景吗?你有需要自己写Skill的场景吗?
觉得有用?点个「在看」👇
夜雨聆风