创建自定义技能
本指南将教你如何从零开始创建一个 CountBot 技能,包括文件结构、配置管理和最佳实践。
CountBot 的技能系统设计深受 anthropics/skills 的启发。感谢 Anthropic 团队开创性的 Agent Skills 机制,为整个行业提供了规范与示例。CountBot 在此基础上,增加了图形化配置界面和中文友好的开发体验。
目录
什么是技能
技能(Skills) 是 CountBot 的扩展机制,用于教会 AI 如何完成特定任务。技能本质上是:
- 文档 - 包含使用说明和示例的 Markdown 文件
- 🐍 脚本 - 可选的 Python 脚本实现具体功能
- ⚙️ 配置 - 可选的配置文件(API Key、参数等)
技能让 AI 能够:
- 调用外部 API(搜索、地图、天气等)
- 执行复杂的数据处理
- 生成特定格式的内容
- 与第三方服务集成
技能 vs 工具
理解技能和工具的区别很重要:
| 维度 | 工具(Tools) | 技能(Skills) |
|---|---|---|
| 定义 | 内置的系统能力 | 外部扩展的功能 |
| 调用方式 | LLM 直接调用 | 通过 exec 工具执行 |
| 实现语言 | Python(内置) | 任意语言(通常 Python) |
| 配置 | 系统配置 | 独立配置文件 |
| 示例 | read_file, exec, web_fetch | 天气查询、图片生成、邮件管理 |
工作流程:
用户: "查询北京天气"
↓
AI: 使用 read_file 读取 weather 技能文档
↓
AI: 阅读文档中的命令示例
↓
AI: 使用 exec 工具执行命令
↓
返回结果给用户
快速开始
1. 创建技能目录
技能存放在 workspace/skills/ 目录下:
workspace/skills/
└── my-skill/ # 技能名称( 小写,连字符分隔)
├── SKILL.md # 必需:技能文档
├── scripts/ # 可选:Python 脚本
│ ├── main.py
│ └── config.json # 可选:配置文件
└── README.md # 可选:开发文档
2. 创建 SKILL.md
最简单的技能只需要一个 SKILL.md 文件:
---
name: my-skill
description: 我的第一个技能
metadata: {"CountBot": {"always": false}}
---
# 我的技能
这个技能用于演示如何创建自定义技能。
## 使用方法
直接在对话中提到相关功能,AI 会自动读取并使用这个技能。
## 示例
用户: "使用我的技能"
AI: 我会读取这个文档并按照说明操作
3. 重载技能
创建完成后,在 CountBot Web UI 中:
- 进入「设置」→「技能管理」
- 点击「重载技能」按钮
- 新技能会出现在列表中
4. 使用图形化配置(可选)
CountBot 提供了图形化配置界面,无需手动编辑 JSON 文件:
-
打开技能管理
- 点击右上角的「技能」图标
- 或进入「设置」→「技能管理」
-
查看技能列表
- 显示所有已加载的技能
- 可以启用/禁用技能
- 查看技能详情
-
配置技能
- 点击技能卡片的「配置」按钮
- 在图形化界面中填写配置项
- 支持的字段类型:
- 文本输入(API Key、URL 等)
- 密码输入(自动隐藏)
- 数字输入(带范围验证)
- 布尔开关
- 下拉选择
- 嵌套对象
-
保存配置
- 点击「保存」按钮
- 配置自动写入
scripts/config.json - 自动验证配置格式
图形化配置的优势:
- 无需手动编辑 JSON 文件
- 自动验证配置格式
- 敏感信息自动隐藏
- 提供字段说明和帮助文档
- 支持配置自动修复
文件结构
标准技能结构
my-skill/
├── SKILL.md # 技能文档(必需)
├── scripts/ # 脚本目录(可选)
│ ├── main.py # 主脚本
│ ├── utils.py # 工具函数
│ ├── config.json # 配置文件
│ └── requirements.txt # Python 依赖
├── tests/ # 测试目录(可选)
│ └── test_main.py
├── examples/ # 示例目录(可选)
│ └── example.md
└── README.md # 开发文档(可选)
文件说明
| 文件 | 必需 | 说明 |
|---|---|---|
SKILL.md | 是 | 技能文档,AI 会读取这个文件 |
scripts/main.py | 否 | Python 脚本实现 |
scripts/config.json | 否 | 配置文件(API Key 等) |
scripts/requirements.txt | 否 | Python 依赖列表 |
README.md | 否 | 开发者文档 |
SKILL.md 格式
YAML Frontmatter
SKILL.md 文件以 YAML frontmatter 开头:
---
name: skill-name
description: 技能的简短描述
metadata:
CountBot:
always: false
requires:
bins: ["python", "curl"]
env: ["API_KEY"]
---
Frontmatter 字段
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
name | string | 是 | 技能名称(小写,连字符分隔) |
description | string | 是 | 技能描述(一句话说明功能) |
metadata.CountBot.always | boolean | 否 | 是否自动加载(默认 false) |
metadata.CountBot.requires.bins | array | 否 | 依赖的二进制程序 |
metadata.CountBot.requires.env | array | 否 | 依赖的环境变量 |
always 字段说明
always: true- 技能内容会自动注入到系统提示词,适合频繁使用的技能always: false- 技能只在需要时加载(推荐),节省 Token
Markdown 内容
Frontmatter 之后是 Markdown 格式的技能文档:
# 技能标题
技能的详细说明。
## 功能介绍
- 功能 1
- 功能 2
- 功能 3
## 使用方法
### 命令行调用
\`\`\`bash
python scripts/main.py --arg1 value1 --arg2 value2
\`\`\`
### 参数说明
- `--arg1`: 参数 1 的说明
- `--arg2`: 参数 2 的说明
## 示例
### 示例 1:基本用法
\`\`\`bash
python scripts/main.py --city "北京"
\`\`\`
输出:
\`\`\`
北京天气:晴,温度 25°C
\`\`\`
### 示例 2:高级用法
\`\`\`bash
python scripts/main.py --city "上海" --format json
\`\`\`
## 注意事项
- 注意事项 1
- 注意事项 2
## 错误处理
常见错误及解决方法...
编写技巧
- 清晰的命令示例 - AI 会直接复制命令,确保示例可以直接运行
- 完整的参数说明 - 说明每个参数的含义和可选值
- 实际的输出示例 - 帮助 AI 理解预期结果
- 错误处理说明 - 告诉 AI 如何处理常见错误
配置管理
CountBot 提供两种配置方式:图形化配置(推荐)和手动编辑 JSON。
图形化配置(推荐)
CountBot 的 Web UI 提供了完整的图形化配置界面:
访问配置界面
-
方式一:右上角快捷入口
- 点击右上角的「技能」图标
- 快速访问技能管理
-
方式二:设置页面
- 进入「设置」→「技能管理」
- 查看所有技能
配置流程
-
选择技能
- 在技能列表中找到要配置的技能
- 点击技能卡片
-
查看配置项
- 系统自动加载配置 Schema
- 显示所有可配置的字段
- 每个字段都有说明和帮助文档
-
填写配置
- 根据字段类型填写配置
- 必填项会有标记
- 敏感信息(如 API Key)自动隐藏
-
验证和保存
- 点击「保存」按钮
- 系统自动验证配置格式
- 配置写入
scripts/config.json
支持的字段类型
| 类型 | 界面展示 | 示例 |
|---|---|---|
string | 文本输入框 | 名称、URL |
password | 密码输入框(隐藏) | API Key |
email | 邮箱输入框(验证) | 邮箱地址 |
number | 数字输入框(带范围) | 超时时间、最大值 |
boolean | 开关按钮 | 启用/禁用 |
select | 下拉选择框 | 选项列表 |
object | 可折叠的嵌套表单 | 复杂配置 |
配置验证
图形化界面提供实时验证:
- 必填项检查
- 数据类型验证
- 范围验证(min/max)
- 格式验证(email、URL)
- 自动修复缺失字段
配置文件位置
配置文件位于 scripts/config.json:
{
"api_key": "your_api_key_here",
"api_base": "https://api.example.com",
"timeout": 30,
"max_retries": 3
}
配置 Schema
为了支持图形化配置,需要在 backend/modules/agent/skills_schema.py 中定义配置 Schema:
SKILL_SCHEMAS = {
"my-skill": {
"skill_name": "my-skill",
"version": "1.0.0",
"description": "我的技能配置",
"config_file": "scripts/config.json",
"help_text": "配置说明:\n1. 获取 API Key\n2. 填写配置\n3. 保存",
"fields": [
{
"key": "api_key",
"type": "password",
"label": "API Key",
"description": "API 密钥",
"required": True,
"sensitive": True,
"placeholder": "请输入 API Key",
"help_text": "在服务商网站获取"
},
{
"key": "timeout",
"type": "number",
"label": "超时时间",
"description": "请求超时时间(秒)",
"default": 30,
"min": 1,
"max": 300,
"help_text": "建议设置为 30-60 秒"
},
{
"key": "enabled",
"type": "boolean",
"label": "启用功能",
"description": "是否启用此功能",
"default": True
}
]
}
}
Schema 字段说明
顶层字段:
skill_name: 技能名称(必需)version: Schema 版本(必需)description: 配置说明(必需)config_file: 配置文件路径(必需)help_text: 帮助文档(可选)
字段定义:
key: 配置键名(必需)type: 字段类型(必需)label: 显示标签(必需)description: 字段说明(必需)required: 是否必填(可选,默认 false)sensitive: 是否敏感信息(可选,默认 false)default: 默认值(可选)placeholder: 占位符文本(可选)help_text: 帮助文本(可选)min/max: 数字范围(number 类型)options: 选项列表(select 类型)
嵌套对象示例
{
"key": "email_config",
"type": "object",
"label": "邮箱配置",
"description": "邮箱服务器配置",
"collapsible": True, # 可折叠
"fields": [
{
"key": "server",
"type": "string",
"label": "服务器地址",
"required": True
},
{
"key": "port",
"type": "number",
"label": "端口",
"default": 587
}
]
}
下拉选择示例
{
"key": "mode",
"type": "select",
"label": "运行模式",
"description": "选择运行模式",
"default": "auto",
"options": [
{"value": "auto", "label": "自动"},
{"value": "manual", "label": "手动"},
{"value": "disabled", "label": "禁用"}
]
}
手动编辑配置(高级)
如果不定义 Schema,也可以直接编辑 scripts/config.json:
# 编辑配置文件
vim workspace/skills/my-skill/scripts/config.json
# 验证 JSON 格式
python -m json.tool workspace/skills/my-skill/scripts/config.json
注意:手动编辑时需要确保 JSON 格式正确,否则技能可能无法加载。
字段类型
| 类型 | 说明 | 示例 |
|---|---|---|
string | 文本 | 名称、URL |
password | 密码 | API Key、密码 |
email | 邮箱 | 邮箱地址 |
number | 数字 | 超时时间、最大值 |
boolean | 布尔 | 启用/禁用 |
select | 下拉选择 | 选项列表 |
object | 嵌套对象 | 复杂配置 |
在脚本中读取配置
import json
from pathlib import Path
# 读取配置
config_path = Path(__file__).parent / "config.json"
config = json.loads(config_path.read_text())
api_key = config.get("api_key")
timeout = config.get("timeout", 30)
Python 脚本
基本结构
#!/usr/bin/env python3
"""
技能脚本:my-skill
描述:我的技能实现
"""
import argparse
import json
import sys
from pathlib import Path
def load_config():
"""加载配置文件"""
config_path = Path(__file__).parent / "config.json"
if not config_path.exists():
return {}
return json.loads(config_path.read_text())
def main():
"""主函数"""
parser = argparse.ArgumentParser(description="我的技能")
parser.add_argument("--arg1", required=True, help="参数 1")
parser.add_argument("--arg2", default="default", help="参数 2")
args = parser.parse_args()
config = load_config()
# 实现功能
result = do_something(args.arg1, args.arg2, config)
# 输出结果(JSON 格式)
print(json.dumps(result, ensure_ascii=False, indent=2))
def do_something(arg1, arg2, config):
"""实现具体功能"""
# 你的代码
return {"status": "success", "data": "..."}
if __name__ == "__main__":
try:
main()
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
sys.exit(1)
最佳实践
- 使用 argparse - 标准的命令行参数解析
- JSON 输出 - 结构化输出,便于 AI 解析
- 错误处理 - 捕获异常,输出到 stderr
- 配置分离 - 敏感信息放在 config.json
- 添加 shebang -
#!/usr/bin/env python3
依赖管理
requirements.txt
在 scripts/requirements.txt 中列出 Python 依赖:
requests>=2.28.0
beautifulsoup4>=4.11.0
python-dotenv>=0.20.0
安装依赖
cd workspace/skills/my-skill/scripts
pip install -r requirements.txt
在 SKILL.md 中声明依赖
---
name: my-skill
description: 我的技能
metadata:
CountBot:
requires:
bins: ["python"]
env: ["API_KEY"]
---
CountBot 会在加载技能时检查依赖是否满足。
完整示例:天气查询
让我们创建一个完整的天气查询技能。
1. 创建目录结构
mkdir -p workspace/skills/weather/scripts
cd workspace/skills/weather
2. 创建 SKILL.md
---
name: weather
description: 查询全球城市天气信息
metadata:
CountBot:
always: false
requires:
bins: ["python"]
---
# 天气查询技能
使用 wttr.in 服务查询全球城市的天气信息。
## 功能特性
- 支持全球城市查询
- 中文天气描述
- 包含温度、湿度、风速等信息
- 支持多日天气预报
## 使用方法
### 基本查询
\`\`\`bash
python workspace/skills/weather/scripts/main.py --city "北京"
\`\`\`
### 指定语言
\`\`\`bash
python workspace/skills/weather/scripts/main.py --city "Beijing" --lang zh
\`\`\`
### 多日预报
\`\`\`bash
python workspace/skills/weather/scripts/main.py --city "上海" --days 3
\`\`\`
## 参数说明
- `--city`: 城市名称(中文或英文)
- `--lang`: 语言(zh/en,默认 zh)
- `--days`: 预报天数(1-3,默认 1)
## 输出示例
\`\`\`json
{
"city": "北京",
"current": {
"temperature": "25°C",
"condition": "晴",
"humidity": "45%",
"wind": "东北风 3级"
},
"forecast": [
{
"date": "2026-03-05",
"high": "28°C",
"low": "15°C",
"condition": "多云"
}
]
}
\`\`\`
## 注意事项
- 使用免费的 wttr.in 服务,无需 API Key
- 城市名称支持中文和英文
- 网络请求可能需要几秒钟
## 错误处理
- 城市不存在:返回错误信息
- 网络超时:自动重试 3 次
- 服务不可用:提示用户稍后重试
3. 创建 Python 脚本
scripts/main.py:
#!/usr/bin/env python3
"""
天气查询技能
使用 wttr.in 服务查询天气信息
"""
import argparse
import json
import sys
import requests
def get_weather(city, lang="zh", days=1):
"""
查询天气信息
Args:
city: 城市名称
lang: 语言(zh/en)
days: 预报天数
Returns:
dict: 天气信息
"""
url = f"https://wttr.in/{city}"
params = {
"format": "j1", # JSON 格式
"lang": lang,
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# 解析数据
current = data["current_condition"][0]
forecast = data["weather"][:days]
result = {
"city": city,
"current": {
"temperature": f"{current['temp_C']}°C",
"condition": current["lang_zh"][0]["value"] if lang == "zh" else current["weatherDesc"][0]["value"],
"humidity": f"{current['humidity']}%",
"wind": f"{current['winddir16Point']} {current['windspeedKmph']}km/h"
},
"forecast": [
{
"date": day["date"],
"high": f"{day['maxtempC']}°C",
"low": f"{day['mintempC']}°C",
"condition": day["hourly"][0]["lang_zh"][0]["value"] if lang == "zh" else day["hourly"][0]["weatherDesc"][0]["value"]
}
for day in forecast
]
}
return result
except requests.exceptions.RequestException as e:
raise Exception(f"网络请求失败: {str(e)}")
except (KeyError, IndexError) as e:
raise Exception(f"数据解析失败: {str(e)}")
def main():
"""主函数"""
parser = argparse.ArgumentParser(description="查询天气信息")
parser.add_argument("--city", required=True, help="城市名称")
parser.add_argument("--lang", default="zh", choices=["zh", "en"], help="语言")
parser.add_argument("--days", type=int, default=1, choices=[1, 2, 3], help="预报天数")
args = parser.parse_args()
# 查询天气
result = get_weather(args.city, args.lang, args.days)
# 输出结果
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
try:
main()
except Exception as e:
print(json.dumps({"error": str(e)}, ensure_ascii=False), file=sys.stderr)
sys.exit(1)
4. 创建依赖文件
scripts/requirements.txt:
requests>=2.28.0
5. 测试技能
# 安装依赖
cd workspace/skills/weather/scripts
pip install -r requirements.txt
# 测试脚本
python main.py --city "北京"
python main.py --city "Shanghai" --lang en --days 3
6. 在 CountBot 中使用
- 重载技能(Web UI → 设置 → 技能管理 → 重载)
- 在对话中使用:
用户: 查询北京天气
AI: 我来查询北京的天气信息...
[使用 read_file 读取 weather 技能文档]
[使用 exec 执行命令]
北京当前天气:
- 温度:25°C
- 天气:晴
- 湿度:45%
- 风向:东北风 3级
最佳实践
1. 文档优先
- 先写 SKILL.md,确保 AI 能理解如何使用
- 提供清晰的命令示例和输出示例
- 说明常见错误和解决方法
2. 命令行友好
- 使用标准的命令行参数(argparse)
- 支持
--help查看帮助 - 输出结构化数据(JSON)
3. 错误处理
- 捕获所有异常
- 输出友好的错误信息
- 使用非零退出码表示失败
4. 配置分离
- 敏感信息放在 config.json
- 不要在代码中硬编码 API Key
- 提供配置示例文件
5. 依赖管理
- 在 requirements.txt 中列出所有依赖
- 在 SKILL.md 中声明二进制依赖
- 检查依赖是否满足
6. 测试
- 编写单元测试
- 测试各种输入情况
- 测试错误处理
7. 文档完善
- 添加 README.md 说明开发细节
- 提供使用示例
- 说明配置方法
调试技巧
1. 查看技能是否加载
Web UI → 设置 → 技能管理,检查技能是否出现在列表中。
2. 检查依赖
# 检查 Python
which python
# 检查依赖包
pip list | grep requests
3. 手动测试脚本
cd workspace/skills/my-skill/scripts
python main.py --help
python main.py --arg1 test
4. 查看日志
CountBot 日志会显示技能加载和执行信息:
[INFO] Loaded 10 skills
[DEBUG] Skill 'weather' loaded from workspace
[DEBUG] Skill 'weather' dependencies satisfied
5. 使用 always: true 测试
临 时将技能设置为自动加载,检查是否正确注入到系统提示词:
metadata:
CountBot:
always: true # 测试时使用,正式使用改为 false
6. 检查配置
# 查看配置文件
cat workspace/skills/my-skill/scripts/config.json
# 验证 JSON 格式
python -m json.tool workspace/skills/my-skill/scripts/config.json
常见问题
Q: 技能没有出现在列表中?
A: 检查:
- 目录名称是否正确(小写,连字符分隔)
- SKILL.md 文件是否存在
- YAML frontmatter 格式是否正确
- 是否点击了「重载技能」按钮
Q: AI 不使用我的技能?
A: 检查:
- 技能描述是否清晰
- 是否在对话中提到了相关功能
- 技能文档是否包含清晰的使用示例
- 考虑设置
always: true自动加载
Q: 脚本执行失败?
A: 检查:
- Python 路径是否正确
- 依赖是否已安装
- 配置文件是否存在
- 手动执行脚本是否成功
Q: 如何调试脚本?
A: 方法:
- 在脚本中添加 print 语句
- 查看 CountBot 日志
- 手动执行脚本测试
- 使用 Python 调试器(pdb)
下一步
- 技能系统架构 - 了解技能系统的实现原理
- 工具系统 - 了解内置工具的使用
- Agent Loop - 了解 AI 如何使用技能
- 实战场景 - 查看技能的实际应用