欢迎阅读「从零开始写好 Skill」系列——上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写“工作手册”。
开场:看到结构 ≠ 会写结构
上一篇,我们看到了 wespy-fetcher 的 SKILL.md,知道了它分成几个部分,也感受到了“有 Skill vs 没 Skill”的巨大差距。
但“看到”和“会写”是两回事。
同样是写描述,为什么有的 Skill 一触即发,有的却被 Agent 视而不见?同样是写操作步骤,为什么有的让 Agent 一步到位,有的却让 Agent 反复出错?
这篇我们就拿 wespy-fetcher 的真实 SKILL.md 开刀,逐段拆解。这次的重点不再是“这段是什么”——上一篇已经讲过了。这次我们聚焦于:这段怎么写才有效,写错了会怎样。
一、先看全貌:一个 Skill 的四层骨架
让我们先把 wespy-fetcher 的 SKILL.md 完整地看一遍,不加批注,感受它的原貌:
---
name: wespy-fetcher
description: 获取并转换微信公众号/网页文章为 Markdown 的封装 Skill,
完整支持 WeSpy 的单篇抓取、微信专辑批量下载、专辑列表获取、
HTML/JSON/Markdown 多格式输出。
Use when user asks to 抓取微信公众号文章、公众号专辑批量下载、
URL 转 Markdown、保存微信文章、mp.weixin.qq.com to markdown.
---
# WeSpy Fetcher
封装 tianchangNorth/WeSpy 的完整能力。
## 功能范围(与 WeSpy 对齐)
- 单篇文章抓取(微信公众号 / 通用网页 / 掘金)
- 微信专辑文章列表获取(--album-only)
- 微信专辑批量下载(--max-articles)
- 多格式输出(Markdown 默认,支持 HTML / JSON / 全部)
## 使用
脚本位置:scripts/wespy_cli.py
# 单篇文章(默认输出 markdown)
python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/s/xxxxx"
# 专辑批量下载
python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/mp/appmsgalbum?..." --max-articles 20
## 实现说明
- 优先使用本地源码路径 ~/Documents/QNSZ/project/WeSpy
- 若本地不存在则自动执行 git clone 到该目录
- 通过导入 wespy.main.main 直接调用上游 CLI,保持行为一致
这份文件虽然不长,却有着清晰的四层结构。Agent 读取它的过程,就像面试一样,是分层递进的:
第一层:头部(YAML frontmatter)—— 简历筛选
Agent 启动时扫描所有 Skill 的 description,判断“这个任务该不该用这个 Skill”。绝大多数 Skill 在这一步就被跳过了。如果你的 description 写得不好,Agent 根本不会往下读。
第二层:概述(标题 + 功能范围)—— 电话面试
通过了第一层筛选,Agent 会快速读一下概述,确认能力范围是否匹配。“这个 Skill 能处理专辑批量下载吗?”——扫一眼功能范围列表就知道了。
第三层:操作指南(使用方式、命令、参数)—— 入职培训
确认要用这个 Skill 了,Agent 开始读具体的操作步骤。“脚本在哪?怎么调用?参数怎么传?”——这一层给出所有执行细节。
第四层:补充说明(实现细节、依赖、兜底逻辑)—— 应急手册
执行过程中遇到问题了,Agent 会来查阅这一层。“WeSpy 没装怎么办?”——补充说明会告诉它应该自动 clone。
请记住这个分层逻辑,它直接决定了你在写 SKILL.md 时,每一段应该放什么、不应该放什么。高质量的技术文档正是建立在这种清晰的逻辑结构之上。
二、头部:description 是触发器,不是摘要
头部的 YAML frontmatter 是整个 Skill 最关键的部分。这几行如果写错了,后面的内容再好也没用——因为 Agent 根本读不到后面。
让我们再仔细审视一下 wespy-fetcher 的 description:
description:
获取并转换微信公众号/网页文章为Markdown的封装Skill,
完整支持WeSpy的单篇抓取、微信专辑批量下载、专辑列表获取、
HTML/JSON/Markdown多格式输出。
Use when user asks to 抓取微信公众号文章、公众号专辑批量下载、
URL转Markdown、保存微信文章、mp.weixin.qq.com to markdown.
这段 description 做对了三件事:
第一,前半段是能力声明——“我能做什么”。
“获取并转换微信公众号/网页文章为 Markdown”,一句话讲清楚核心能力。Agent 扫到这里就明白:这是一个处理公众号文章的 Skill,不是处理视频或 PDF 的。
第二,后半段是触发词列表——“用户怎么说时该想到我”。
“Use when user asks to” 后面跟了一串关键词:抓取微信公众号文章、公众号专辑批量下载、URL 转 Markdown、保存微信文章、mp.weixin.qq.com to markdown。
这些不是给人看的,是给 Agent 看的。用户说“帮我抓取这篇公众号”,Agent 会用“抓取”和“公众号”去匹配所有 Skill 的 description,一旦命中“抓取微信公众号文章”,就会加载这个 Skill。
第三,覆盖了用户的多种说法。
同一件事,用户可能有不同的表达方式。有人说“抓取”,有人说“下载”,有人说“保存”,有人甚至直接丢一个 mp.weixin.qq.com 的链接。好的 description 会把这些变体都列上。
现在来看一个反面教材。假如 description 写成这样:
description: 一个用于处理微信文章的工具。
问题在哪里?
“处理”这个词太模糊了——是指抓取、翻译、排版还是总结?Agent 无法判断这个 Skill 是否匹配当前任务。触发词也太少,用户说“帮我下载这篇公众号文章”时,“下载”和“处理”可能匹配不上。
再看另一个极端——写得太长:
description: 这是一个非常强大的工具,可以帮助你从微信公众号平台上
获取任意文章的完整内容,包括文字、图片和格式信息,它使用了
先进的爬虫技术来绕过微信的反爬机制,支持多种输出格式,
并且可以批量处理微信专辑中的所有文章......
问题在于:Agent 扫描 description 时需要快速判断,不是来读长篇大论的。冗长的描述反而可能增加误匹配的风险,而且真正有用的触发词会被淹没在废话里。
description 的写作公式:
一句话能力声明 + "Use when user asks to" + 用户可能说的各种关键词
简洁、精准、覆盖变体。做到这三点就够了。
三、概述:划边界,不是做广告
通过了 description 的筛选,Agent 开始阅读概述部分。wespy-fetcher 的概述只有一句话:
封装 tianchangNorth/WeSpy 的完整能力。
这句话的价值不在于它具体说了什么,而在于它清晰地划定了边界。它明确告诉 Agent:这个 Skill 的能力范围完全等同于 WeSpy 的能力范围,不多也不少。这样一来,Agent 就不会试图用它去完成 WeSpy 做不到的事情。
接着是“功能范围”列表:
- 单篇文章抓取(微信公众号 / 通用网页 / 掘金)
- 微信专辑文章列表获取(--album-only)
- 微信专辑批量下载(--max-articles)
- 多格式输出(Markdown 默认,支持 HTML / JSON / 全部)
这部分的作用是让 Agent 快速进行能力匹配。用户说“帮我批量下载这个专辑”,Agent 扫一眼功能范围,看到“微信专辑批量下载”,确认匹配,就会继续往下读操作步骤。如果用户说的是“帮我把这篇文章翻译成英文”,Agent 扫一眼发现没有“翻译”相关功能,就会跳过这个 Skill,去寻找其他合适的 Skill。
这里有一个常见的坑需要注意:在概述里塞进太多操作细节。
比如有人在功能范围里这样写:
- 单篇文章抓取(使用 python3 scripts/wespy_cli.py 命令,
需要先确保 WeSpy 已安装在 ~/Documents/QNSZ/project/WeSpy 目录,
如果没有安装会自动 git clone...)
这样写的问题是:Agent 在概述阶段只需要判断“能不能做”,还不需要知道“具体怎么做”。操作细节应该放在下一层。在概述阶段塞入太多信息,反而会干扰 Agent 的匹配判断。
概述的写作原则:说清楚能做什么、不能做什么,其他的一概不说。
四、操作指南:Agent 的执行剧本
确认要使用这个 Skill 后,Agent 就会进入操作指南部分。这是 Skill 的核心——Agent 拿到具体任务后,将严格遵循这里给出的步骤来执行。
wespy-fetcher 的操作指南长这样:
## 使用
脚本位置:scripts/wespy_cli.py
# 单篇文章(默认输出 markdown)
python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/s/xxxxx"
# 专辑批量下载
python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/mp/appmsgalbum?..." --max-articles 20
注意它做对了什么:它提供了多个具体场景的命令示例,而不是只给一个笼统的通用命令。
“单篇文章”和“专辑批量下载”是两个不同的使用场景,用户的需求可能是其中任何一个。Agent 看到这些具体示例,就能根据用户的实际需求选择最匹配的命令,而不是每次都生搬硬套同一个命令。
如果操作指南只写成这样:
## 使用
python3 scripts/wespy_cli.py [URL] [OPTIONS]
那么 Agent 就得自己猜:当用户需要批量下载时,OPTIONS 该填什么?是 --max-articles 还是 --batch?--album-only 又是什么意思?猜错了就会导致执行失败。
操作指南的第一原则:给具体场景的示例,不要给抽象的通用模板。
不过,这里需要指出一个重要的前提:wespy-fetcher 之所以采用“命令示例”这种写法,是因为它本质上是一个工具封装——将一个已有的命令行工具包装成 Skill,教会 Agent 如何调用它。
但并非所有 Skill 都是工具封装。不同类型的 Skill,操作指南的写法也完全不同:
- 如果你的 Skill 是一个生成器(比如“生成技术报告”),操作指南应该写成:加载模板 → 向用户收集信息 → 填充模板 → 输出文档。
- 如果你的 Skill 是一个审查器(比如“代码审查”),操作指南应该写成:加载审查清单 → 逐条检查用户代码 → 按严重程度分组 → 输出结构化报告。
- 如果你的 Skill 是一个流水线(比如“从代码生成 API 文档”),操作指南应该写成:步骤1 → 检查点(用户确认)→ 步骤2 → 检查点 → 步骤3。
- 如果你的 Skill 是一个采访模式(比如“项目规划”),操作指南应该写成:按顺序提问 → 等待用户回答 → 收集完信息后再综合输出。不允许 Agent 在收集完所有必要信息之前就开始行动。
操作指南的写法取决于你的 Skill 属于哪种类型,没有放之四海皆准的格式。 这些类型我们会在后续文章中详细展开。这里先建立这样一个认知:当你评估一个 Skill 的操作指南时,首先要判断它属于哪种类型,然后再评估它写得好不好。
五、补充说明:你以为不重要的部分,其实最防坑
让我们看看 wespy-fetcher 的“实现说明”:
- 优先使用本地源码路径 ~/Documents/QNSZ/project/WeSpy
- 若本地不存在则自动执行 git clone 到该目录
- 通过导入 wespy.main.main 直接调用上游 CLI,保持行为一致
很多人写 Skill 时,会把精力主要花在 description 和操作指南上,补充说明部分则随便写两句,甚至干脆不写。这是一个很大的误区。
设想一下这个场景:Agent 按照操作指南开始执行,调用 python3 scripts/wespy_cli.py,结果发现 WeSpy 根本没安装。怎么办?
如果没有补充说明,Agent 可能会有以下几种反应:
- 直接报错:“WeSpy 未安装,请手动安装后重试”——这就回到了上一篇提到的“甩锅”状态。
- 自己猜测安装方式:比如执行
pip install wespy——但如果猜错了,可能会安装一个完全不相关的包。
- 去网上搜索安装方法——既浪费时间,又可能搜到过时或错误的信息。
但有了上面这三行补充说明,Agent 就知道:先检查 ~/Documents/QNSZ/project/WeSpy 这个路径,如果存在就直接使用;如果不存在,就自动执行 git clone 到这个目录。流程形成了一个闭环,不会在执行中卡死。
这就是补充说明的核心价值:把 Agent 执行过程中可能遇到的“岔路口”提前堵死。
什么是“岔路口”?就是那些需要 Agent 自行判断、但又很可能判断失误的地方:
- 依赖不存在怎么办?
- 网络超时怎么办?
- 输入格式不符合预期怎么办?
- 输出目录不存在怎么办?
- 同名文件已经存在,是覆盖还是跳过?
每一个你曾经踩过的“坑”,都应该沉淀到补充说明里。一个 Skill 之所以会变得越来越“聪明”、越来越好用,正是因为它将过去遇到的问题和解决方案都记录在了这里。 这其实也是优秀开源实战项目文档的共通特点。
六、纠正一个简化:Skill 不只是一个文件
在上一篇中,为了降低理解门槛,我们说“Skill 就是一个 Markdown 文件”。这个说法帮助你建立了最初的认知,但从严格意义上讲,它不够准确。
Skill 本质上是一个文件夹,SKILL.md 仅仅是它的入口文件。
一个完整的 Skill 文件夹通常包含以下结构:
wespy-fetcher/
├── SKILL.md ← 入口文件,Agent 首先读这个
├── scripts/
│ └── wespy_cli.py ← 可执行脚本,Agent 调用它来完成任务
├── references/ ← 参考资料(API 文档、编码规范等)
└── assets/ ← 模板、示例输出、配置文件等
- SKILL.md 是大脑——告诉 Agent 该做什么。
- scripts/ 是手脚——提供实际执行任务所需的脚本。
- references/ 是参考书——存放 Agent 在执行过程中可能需要查阅的背景知识。
- assets/ 是工具箱——存放模板、配置等辅助资源。
Agent 并不是读完 SKILL.md 就凭空开始干活——它会探索整个文件夹,在需要的时候读取对应的文件。例如,wespy-fetcher 的操作指南里指明了 scripts/wespy_cli.py,Agent 在执行时就会去 scripts/ 目录下寻找这个脚本。
这引出一个重要的实践原则:SKILL.md 本身应该保持精简,建议将行数控制在 500 行以内。 超出这个范围的内容,应该拆分到子文件中,然后在 SKILL.md 里用相对路径进行引用。
为什么要这么做?因为 Agent 的上下文窗口是有限的。如果你把一个长达 3000 行的 API 文档全部塞进 SKILL.md,那么 Agent 每次加载这个 Skill 时都会消耗大量的上下文空间,留给执行其他任务的空间就所剩无几了。
更好的做法是:在 SKILL.md 里写明“详细的 API 规范请参见 references/api.md”。这样,Agent 只有在真正需要查阅 API 时,才会去加载那个特定的文件。这就是所谓的渐进式揭示——按需加载,而不是一次性把所有信息都塞进去。
七、一个检查清单:你的 SKILL.md 写对了吗?
理论拆解完毕,现在给你一份可以直接带走的检查清单。下次写完一个 SKILL.md 后,可以对照着逐一核对:
头部(description):
- [ ] 写的是触发条件,而非功能摘要。
- [ ] 包含 “Use when user asks to” 或类似的触发词引导句。
- [ ] 覆盖了用户可能的多种表达方式(同一件事的不同说法)。
- [ ] 长度适中,通常不超过 5 行。
概述:
- [ ] 用一句话说清楚这个 Skill 的能力边界。
- [ ] 功能范围列表只罗列“能做什么”,不包含具体的操作细节。
操作指南:
- [ ] 给出了针对具体场景的命令或步骤示例。
- [ ] 为不同的使用场景提供了不同的示例(不仅仅是一个通用模板)。
- [ ] 写法与 Skill 的类型相匹配(例如:工具封装、生成器、审查器、流水线、采访模式)。
补充说明:
- [ ] 覆盖了依赖缺失时的兜底方案。
- [ ] 覆盖了常见的错误场景及其处理方式。
- [ ] 将过去踩过的“坑”和解决方案都沉淀在了这里。
整体结构:
- [ ] SKILL.md 文件本身控制在 500 行以内。
- [ ] 超出部分的内容已合理拆分到
references/、scripts/、assets/ 等子目录中。
- [ ] 四层结构清晰明了:头部 → 概述 → 操作指南 → 补充说明。
下一篇预告
现在,你已经知道了一个好 Skill 应该长什么样,也拥有了一份实用的检查清单。但是,纸上得来终觉浅——读一百个别人的 Skill,也不如自己动手从零开始写一个。
下一篇,我们将从一个真实的需求出发,手把手教你编写一个 Skill。你将看到一个完整的迭代过程:第一版写得很粗糙,Agent 使用时错误百出;然后我们一步步修改、踩坑、补充,直到第三版终于变得稳定好用。这个“从烂到好”的实践过程,远比直接看一个完美的成品更有价值。
「从零开始写好 Skill」系列是「从零开始理解 Agent」系列的姊妹篇。如果你对人工智能 Agent 的基础原理还不太熟悉,建议先从 第一篇:Agent 的底层原理 开始阅读,这将帮助你更好地理解 Skill 在整个 Agent 体系中的作用。