如何编写 Skills:AI Agent 技能开发完整指南
一、什么是 Skills?
Skills(技能) 是一种为 AI Agent 提供专门领域能力的模块化扩展机制。通过 Skills,可以让 AI Agent:
- 获得特定领域的专业知识
- 执行复杂的多步骤任务
- 调用特定的工具和 API
- 提供更精准、更专业的服务
Skills 的核心价值
普通 AI Agent + Skills = 专业 AI Agent
↓ ↓ ↓
通用能力 领域专精 专家级服务二、Skills 的工作原理
2.1 基本架构
┌─────────────────────────────────────────────────────────┐
│ AI Agent │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 核心引擎 (LLM) │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Skills 调度器 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Skill 1 │ │ Skill 2 │ │ Skill 3 │ ... │ │
│ │ │ (PDF) │ │ (XLSX) │ │ (Chart) │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 工具层 (Tools) │ │
│ │ read_file │ write_file │ web_fetch │ bash │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘2.2 调用流程
用户请求 → Agent 分析 → 匹配 Skill → 执行 Skill → 返回结果
│ │ │ │ │
│ │ │ │ │
"处理PDF" 识别为 调用 PDF 读取/解析 返回PDF
PDF任务 Skill PDF文件 内容三、Skill 的基本结构
一个完整的 Skill 通常包含以下组成部分:
3.1 元数据定义
yaml
skill:
name: "pdf-processor" # 技能名称
version: "1.0.0" # 版本号
description: "PDF文档处理技能" # 描述
author: "your-name" # 作者
tags: [pdf, document, parsing] # 标签3.2 能力声明
yaml
capabilities:
- name: "read_pdf"
description: "读取PDF文件内容"
inputs:
file_path: "PDF文件路径"
outputs:
content: "PDF文本内容"
metadata: "PDF元数据"
- name: "extract_images"
description: "提取PDF中的图片"
inputs:
file_path: "PDF文件路径"
output_dir: "图片输出目录"
outputs:
images: "图片文件列表"3.3 工具依赖
yaml
tools_required:
- read_file # 需要读取文件能力
- write_file # 需要写入文件能力
- run_shell_command # 需要执行命令能力3.4 提示词模板
yaml
prompts:
system: |
你是一个专业的PDF文档处理专家。
你的任务是帮助用户处理和分析PDF文档。
可用操作:
1. 读取PDF内容
2. 提取文本和图片
3. 分析PDF结构
注意事项:
- 确保文件路径正确
- 处理大文件时注意内存使用
- 保留原始格式信息
user_template: |
请帮我处理以下PDF文件:{{file_path}}
需要执行的操作:{{operation}}四、从零开始编写一个 Skill
让我们以创建一个 Markdown 转 HTML 的 Skill 为例。
步骤 1:创建目录结构
skills/
└── md2html/
├── skill.yaml # Skill 配置文件
├── README.md # 使用说明
└── prompts/
└── system.txt # 系统提示词步骤 2:编写 skill.yaml
yaml
# skills/md2html/skill.yaml
name: "md2html"
version: "1.0.0"
description: "将 Markdown 文档转换为 HTML 格式"
author: "your-name"
# 触发条件
triggers:
keywords:
- "markdown转html"
- "md转html"
- "转换markdown"
patterns:
- "将.*md.*转换为.*html"
- "md2html"
# 能力定义
capabilities:
- name: "convert"
description: "执行 Markdown 到 HTML 的转换"
inputs:
source: "Markdown 文件路径或内容"
output: "输出 HTML 文件路径"
style: "样式选项(可选)"
outputs:
html_content: "转换后的 HTML 内容"
output_path: "输出文件路径"
# 工具需求
tools_required:
- read_file
- write_file
- run_shell_command
# 执行配置
execution:
max_tokens: 4096
timeout: 60
# 提示词
prompts:
system_file: "prompts/system.txt"步骤 3:编写系统提示词
text
<!-- skills/md2html/prompts/system.txt -->
你是一个专业的 Markdown 到 HTML 转换工具。
## 你的职责
1. 读取用户提供的 Markdown 内容
2. 将 Markdown 语法转换为标准 HTML
3. 应用适当的样式和格式
4. 输出格式良好的 HTML 文档
## 转换规则
### 标题转换
- # → <h1>
- ## → <h2>
- ### → <h3>
- 以此类推...
### 文本格式
- **粗体** → <strong>粗体</strong>
- *斜体* → <em>斜体</em>
- `代码` → <code>代码</code>
### 列表转换
- 无序列表:- item → <ul><li>item</li></ul>
- 有序列表:1. item → <ol><li>item</li></ol>
### 代码块
```language → <pre><code class="language-language">
### 链接和图片
- [text](url) → <a href="url">text</a>
-  → <img src="url" alt="alt">
## HTML 模板
输出的 HTML 应该包含完整的文档结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>转换文档</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; }
img { max-width: 100%; }
</style>
</head>
<body>
<!-- 转换内容放在这里 -->
</body>
</html>
## 注意事项
1. 保持原始内容的语义
2. 处理特殊字符转义
3. 支持嵌套的 Markdown 语法
4. 生成格式化、缩进的 HTML步骤 4:编写使用说明
markdown
# skills/md2html/README.md
# Markdown 转 HTML Skill
## 简介
将 Markdown 文档转换为格式良好的 HTML 文件。
## 使用方法
### 基本用法用户: 请把这个 markdown 文件转换为 html: docs/readme.md Agent: [自动调用 md2html skill,读取文件并转换]
### 指定输出路径用户: 将 docs/readme.md 转换为 html 并保存到 output/readme.html
### 添加自定义样式用户: 把 readme.md 转成 html,使用暗色主题
## 示例
输入 Markdown:
```markdown
# 标题
这是一段**粗体**和*斜体*文本。
- 列表项 1
- 列表项 2输出 HTML:
html
<h1>标题</h1>
<p>这是一段<strong>粗体</strong>和<em>斜体</em>文本。</p>
<ul>
<li>列表项 1</li>
<li>列表项 2</li>
</ul>限制
- 不支持复杂的 Markdown 扩展语法
- 表格转换需要特殊处理
- 某些 Markdown 方言可能有差异
## 五、iFlow CLI 中的 Skills 实现示例
以下是一个在 iFlow CLI 中实现 Skill 的完整示例:
### 5.1 Skill 定义示例(PDF 处理)
```typescript
// skills/pdf-skill.ts
interface SkillDefinition {
name: string;
description: string;
tools: string[];
handler: SkillHandler;
}
interface SkillHandler {
(context: SkillContext): Promise<SkillResult>;
}
interface SkillContext {
userQuery: string;
tools: Map<string, Function>;
memory: any;
}
interface SkillResult {
success: boolean;
data?: any;
error?: string;
}
const pdfSkill: SkillDefinition = {
name: 'pdf',
description: 'PDF文档创建、编辑和分析技能',
tools: ['read_file', 'write_file', 'run_shell_command'],
handler: async (context: SkillContext) => {
const { userQuery, tools } = context;
const readFile = tools.get('read_file');
const writeFile = tools.get('write_file');
// 分析用户意图
if (userQuery.includes('读取') || userQuery.includes('查看')) {
// 提取文件路径
const filePath = extractFilePath(userQuery);
// 使用 read_file 工具
const content = await readFile({ absolute_path: filePath });
return {
success: true,
data: { content }
};
}
// 其他操作...
return {
success: false,
error: '无法识别的操作'
};
}
};
function extractFilePath(query: string): string {
// 实现路径提取逻辑
const match = query.match(/['"]([^'"]+\.(pdf|PDF))['"]/);
return match ? match[1] : '';
}
export default pdfSkill;5.2 Skill 注册和使用
typescript
// skill-manager.ts
class SkillManager {
private skills: Map<string, SkillDefinition> = new Map();
// 注册 Skill
register(skill: SkillDefinition) {
this.skills.set(skill.name, skill);
console.log(`Skill "${skill.name}" 已注册`);
}
// 匹配最适合的 Skill
match(query: string): SkillDefinition | null {
for (const [name, skill] of this.skills) {
if (query.toLowerCase().includes(name)) {
return skill;
}
}
return null;
}
// 执行 Skill
async execute(skillName: string, context: SkillContext): Promise<SkillResult> {
const skill = this.skills.get(skillName);
if (!skill) {
return { success: false, error: `Skill "${skillName}" 未找到` };
}
return await skill.handler(context);
}
}
// 使用示例
const manager = new SkillManager();
manager.register(pdfSkill);
const result = await manager.execute('pdf', {
userQuery: '请读取 report.pdf 文件',
tools: new Map([...]),
memory: {}
});六、编写高质量 Skills 的最佳实践
6.1 明确的职责边界
❌ 错误示例:一个 Skill 做所有事情
skill: "文件处理器"
- 读取文件
- 编辑文件
- 转换格式
- 压缩文件
- 发送邮件
✅ 正确示例:单一职责
skill: "pdf-reader" → 只负责读取 PDF
skill: "pdf-converter" → 只负责转换 PDF 格式
skill: "pdf-editor" → 只负责编辑 PDF6.2 详尽的提示词设计
text
好的提示词应该包含:
1. 角色定义
"你是一个专业的 XX 专家"
2. 任务描述
"你的职责是..."
3. 工具说明
"你可以使用以下工具..."
4. 操作步骤
"执行任务时,请按以下步骤..."
步骤 1:验证输入
步骤 2:执行操作
步骤 3:验证结果
步骤 4:格式化输出
5. 约束条件
"注意:不要..."
"限制:只能..."
6. 错误处理
"如果遇到 XX 情况,请..."
"当出现错误时,返回..."
7. 输出格式
"输出应该包含..."
"格式要求:..."6.3 错误处理和容错
yaml
error_handling:
- error_type: "file_not_found"
action: "提示用户文件不存在,询问正确路径"
- error_type: "permission_denied"
action: "提示用户没有权限,建议使用管理员模式"
- error_type: "timeout"
action: "报告超时,建议分批处理或增加超时时间"
- error_type: "invalid_input"
action: "说明输入格式要求,提供正确示例"6.4 性能优化
yaml
optimization:
# 大文件处理
large_file:
strategy: "分块处理"
chunk_size: "1MB"
# 并发处理
concurrent:
max_tasks: 5
timeout_per_task: 30
# 缓存策略
cache:
enabled: true
ttl: 3600
max_size: "100MB"6.5 安全考虑
yaml
security:
# 输入验证
input_validation:
- "验证文件路径是否在允许范围内"
- "检查文件类型和大小"
- "过滤危险字符和命令"
# 权限控制
permissions:
- "最小权限原则"
- "用户确认敏感操作"
- "操作日志记录"
# 数据保护
data_protection:
- "敏感数据不记录到日志"
- "临时文件及时清理"
- "输出内容安全检查"七、调试和测试 Skills
7.1 单元测试
typescript
// skills/__tests__/pdf-skill.test.ts
describe('PDF Skill', () => {
test('应该正确读取 PDF 文件', async () => {
const context = {
userQuery: '读取 test.pdf',
tools: mockTools,
memory: {}
};
const result = await pdfSkill.handler(context);
expect(result.success).toBe(true);
expect(result.data.content).toBeDefined();
});
test('文件不存在时应该返回错误', async () => {
const context = {
userQuery: '读取 nonexistent.pdf',
tools: mockTools,
memory: {}
};
const result = await pdfSkill.handler(context);
expect(result.success).toBe(false);
expect(result.error).toContain('不存在');
});
});7.2 调试技巧
typescript
// 在 Skill 中添加调试日志
const debugMode = process.env.SKILL_DEBUG === 'true';
handler: async (context: SkillContext) => {
if (debugMode) {
console.log('[DEBUG] 接收到的查询:', context.userQuery);
console.log('[DEBUG] 可用工具:', Array.from(context.tools.keys()));
}
// ... 处理逻辑
if (debugMode) {
console.log('[DEBUG] 处理结果:', result);
}
return result;
}7.3 测试检查清单
markdown
## Skill 测试检查清单
### 功能测试
- [ ] 正常输入能否正确处理
- [ ] 边界情况是否处理得当
- [ ] 错误输入是否有合适的提示
### 性能测试
- [ ] 大文件/大数据处理是否正常
- [ ] 并发请求是否稳定
- [ ] 内存使用是否合理
### 安全测试
- [ ] 是否防止了命令注入
- [ ] 是否防止了路径遍历
- [ ] 敏感信息是否得到保护
### 兼容性测试
- [ ] 不同文件格式是否支持
- [ ] 不同操作系统是否兼容
- [ ] 不同编码是否正确处理八、发布和分享 Skills
8.1 打包 Skill
bash
# 目录结构
my-skill/
├── skill.yaml # 配置文件(必需)
├── README.md # 说明文档(必需)
├── prompts/
│ ├── system.txt # 系统提示词
│ └── user.txt # 用户提示词模板
├── handlers/
│ └── main.ts # 处理逻辑(如果需要)
├── tests/
│ └── main.test.ts # 测试文件
└── examples/
└── usage.md # 使用示例8.2 发布到仓库
yaml
# skill-registry.yaml
name: "markdown-to-html"
version: "1.0.0"
author: "xupengboo"
repository: "https://github.com/xupengboo/skills/md2html"
download_url: "https://github.com/xupengboo/skills/archive/md2html-1.0.0.zip"
checksum: "sha256:abc123..."
# 依赖
dependencies:
- "node >= 18.0.0"
# 兼容性
compatible_agents:
- "iflow-cli >= 1.0.0"
- "claude-agent >= 1.0.0"8.3 版本管理
版本号规则:主版本.次版本.修订版本
- 主版本:不兼容的 API 修改
- 次版本:向下兼容的功能新增
- 修订版本:向下兼容的问题修正
示例:
1.0.0 → 初始版本
1.1.0 → 新增功能(如支持表格)
1.1.1 → 修复 bug
2.0.0 → 重构架构,API 变化九、实战练习:手写一个完整的 Skill
练习目标
创建一个 JSON 格式化工具 Skill,功能包括:
- 格式化 JSON(美化输出)
- 压缩 JSON(移除空白)
- 验证 JSON 语法
- JSON 转 YAML
- YAML 转 JSON
练习步骤
第一步:创建项目结构
bash
mkdir -p skills/json-tools/prompts
mkdir -p skills/json-tools/tests
touch skills/json-tools/skill.yaml
touch skills/json-tools/README.md
touch skills/json-tools/prompts/system.txt第二步:编写 skill.yaml
yaml
# 请自己尝试填写
# 提示:参考上面的 md2html 示例
name: "json-tools"
version: "1.0.0"
description: "TODO: 填写描述"
triggers:
keywords:
# TODO: 填写触发关键词
capabilities:
# TODO: 定义能力
tools_required:
# TODO: 列出需要的工具
prompts:
system_file: "prompts/system.txt"第三步:编写系统提示词
text
<!-- skills/json-tools/prompts/system.txt -->
TODO: 编写提示词
应该包含:
1. 角色定义
2. 支持的操作说明
3. 每种操作的详细步骤
4. 错误处理方式
5. 输出格式要求第四步:编写 README.md
markdown
<!-- skills/json-tools/README.md -->
TODO: 编写使用说明
应该包含:
1. Skill 简介
2. 安装方法
3. 使用示例(每种操作至少一个)
4. 参数说明
5. 注意事项第五步:测试你的 Skill
创建测试文件:
json
<!-- skills/json-tools/tests/test.json -->
{
"name": "测试",
"value": 123,
"nested": {
"key": "value"
}
}测试用例:
markdown
1. 格式化测试:请求格式化 test.json
2. 压缩测试:请求压缩 JSON
3. 验证测试:提供错误的 JSON,检查是否报错
4. 转换测试:JSON ↔ YAML 互转参考答案
完成练习后,可以参考以下答案:
点击展开参考答案
yaml
# skills/json-tools/skill.yaml
name: "json-tools"
version: "1.0.0"
description: "JSON 格式化、验证和转换工具"
triggers:
keywords:
- "json格式化"
- "json压缩"
- "json验证"
- "json转yaml"
- "yaml转json"
patterns:
- "格式化.*json"
- "压缩.*json"
- "验证.*json"
capabilities:
- name: "format"
description: "格式化 JSON,使其更易读"
inputs:
json: "JSON 字符串或文件路径"
indent: "缩进空格数(默认 2)"
outputs:
formatted: "格式化后的 JSON"
- name: "minify"
description: "压缩 JSON,移除所有空白"
inputs:
json: "JSON 字符串或文件路径"
outputs:
minified: "压缩后的 JSON"
- name: "validate"
description: "验证 JSON 语法是否正确"
inputs:
json: "JSON 字符串或文件路径"
outputs:
valid: "是否有效"
errors: "错误信息列表"
- name: "to_yaml"
description: "将 JSON 转换为 YAML"
inputs:
json: "JSON 字符串或文件路径"
outputs:
yaml: "YAML 格式内容"
- name: "from_yaml"
description: "将 YAML 转换为 JSON"
inputs:
yaml: "YAML 字符串或文件路径"
outputs:
json: "JSON 格式内容"
tools_required:
- read_file
- write_file
- run_shell_command
prompts:
system_file: "prompts/system.txt"text
<!-- skills/json-tools/prompts/system.txt -->
你是一个专业的 JSON 处理工具。
## 可用操作
### 1. 格式化 JSON
- 识别用户请求中的 JSON 内容或文件路径
- 使用 read_file 读取文件(如果是路径)
- 解析 JSON 并按指定缩进格式化
- 返回格式化结果
### 2. 压缩 JSON
- 移除所有不必要的空白字符
- 保持 JSON 语义不变
- 返回压缩结果
### 3. 验证 JSON
- 检查 JSON 语法是否正确
- 报告错误位置和原因
- 如果有效,返回解析后的数据结构
### 4. JSON ↔ YAML 转换
- 保持数据结构一致
- 正确处理嵌套对象和数组
- 注意 YAML 的特殊语法规则
## 错误处理
- JSON 解析错误:报告错误位置和可能的原因
- 文件不存在:提示用户并提供帮助
- 编码问题:尝试检测编码并转换
## 输出格式
使用代码块展示 JSON/YAML 内容,并在代码块前后说明操作结果。十、学习路径建议
10.1 循序渐进的学习步骤
第 1 阶段:理解概念(1-2 天)
├── 阅读 Skills 原理
├── 研究现有 Skills 示例
└── 理解工具调用机制
第 2 阶段:简单实践(2-3 天)
├── 完成本文档的 JSON Tools 练习
├── 尝试修改现有 Skill
└── 测试和调试
第 3 阶段:进阶开发(1 周)
├── 设计自己的 Skill
├── 实现复杂功能
└── 优化和重构
第 4 阶段:发布分享(可选)
├── 完善文档
├── 编写测试
└── 发布到社区10.2 推荐的练习项目
初级难度:
1. 文本格式转换器(如大小写转换)
2. 简单计算器 Skill
3. URL 编码/解码工具
中级难度:
1. CSV 数据分析 Skill
2. 图片批量处理 Skill
3. API 测试工具 Skill
高级难度:
1. 代码生成器 Skill
2. 数据库迁移工具 Skill
3. 自动化测试框架 Skill十一、常见问题与解决方案
Q1: Skill 如何被 Agent 识别和调用?
Agent 通过以下方式识别 Skill:
1. 关键词匹配
用户输入包含 Skill 名称或预定义关键词
2. 意图识别
Agent 分析用户意图,匹配最合适的 Skill
3. 显式调用
用户明确指定要使用的 Skill
示例:
用户: "用 pdf skill 读取这个文件"
Agent: [识别到 "pdf skill"] → 调用 pdf skillQ2: 如何处理 Skill 之间的依赖?
yaml
# 在 skill.yaml 中声明依赖
dependencies:
skills:
- name: "file-reader"
version: ">=1.0.0"
- name: "text-processor"
version: ">=2.0.0"
# Agent 会自动加载依赖的 SkillsQ3: Skill 可以调用其他 Skill 吗?
typescript
// 可以!在 handler 中调用其他 Skill
handler: async (context: SkillContext) => {
// 调用其他 Skill
const otherSkill = context.skills.get('other-skill-name');
const result = await otherSkill.handler(context);
// 使用其他 Skill 的结果
// ...
}Q4: 如何处理长时间运行的任务?
typescript
// 方案 1:分块处理
for (const chunk of chunks) {
await processChunk(chunk);
// 给 Agent 反馈进度
}
// 方案 2:后台运行
const taskId = await runInBackground(longRunningTask);
// 返回任务 ID,用户可以稍后查询结果
// 方案 3:流式输出
streamOutput(async (emit) => {
const result = await process();
emit(result);
});十二、总结
编写一个好的 Skill 需要:
- 清晰的定位 - 明确 Skill 要解决什么问题
- 完善的设计 - 设计好接口、提示词和错误处理
- 充分的测试 - 覆盖各种使用场景
- 详细的文档 - 让用户和 Agent 都容易理解
- 持续的迭代 - 根据反馈不断改进
记住:
- Skills 是给 Agent 用的工具,要站在 Agent 的角度思考
- 好的提示词是 Skill 成功的关键
- 单一职责、可组合、可复用是设计原则
附录:实用资源
A. Skill 模板仓库
bash
# 克隆模板
git clone https://github.com/your-org/skill-template.git my-skill
# 修改配置
cd my-skill
# 编辑 skill.yaml, README.md, prompts/system.txt
# 测试
npm test
# 发布
npm publishB. 常用提示词模式
text
# 角色+任务模式
你是 [角色],你的任务是 [任务描述]。
# 步骤模式
请按以下步骤执行:
1. [步骤 1]
2. [步骤 2]
# 约束模式
要求:
- [约束 1]
- [约束 2]
# 示例模式
示例:
输入: [示例输入]
输出: [示例输出]
# 错误处理模式
如果 [情况],则 [处理方式]。C. 工具使用最佳实践
typescript
// 检查工具是否可用
if (!context.tools.has('read_file')) {
return {
success: false,
error: '需要 read_file 工具,但未提供'
};
}
// 安全地调用工具
try {
const result = await context.tools.get('read_file')({
absolute_path: sanitizedPath
});
} catch (error) {
// 处理错误
return {
success: false,
error: `读取文件失败: ${error.message}`
};
}祝你学习顺利!有问题随时交流。