我维护 agent-skills 这个项目已经有段时间了,最初它只是我工作目录里几个零散的 Markdown 文件,后来慢慢变成了我开发 AI 应用时离不开的底仓。如果你已经厌倦了把同样的任务指令复制粘贴到每一个 AI 对话框,如果你希望自己的 Agent 能像一位熟悉业务的老员工一样,按需调用沉淀下来的标准操作流程,那么 agent-skills 这套技能库的设计思路,值得你花十分钟看看。
简单说,agent-skills 是一个以 SKILL.md 文件为核心组织的可复用技能库。它让大语言模型 Agent 在遇到任务时自动加载对应的知识、步骤和示例,而不是把所有上下文都塞进系统提示词里。这个项目解决的最大问题,是提示词的膨胀、复用困难和任务处理不稳定。适合正在搞 AI 应用开发、自动化流程、RAG 工具链的开发者,也适合想用 AI 高效处理重复性文字或数据任务的效率型用户。下面把我从零搭建这套技能库的完整过程、设计取舍和踩过的坑都摊开聊。
1. 项目背景:为什么我需要一个 Agent 技能库
1.1 从三个痛点说起
最开始我用 AI 处理日常任务时,主要靠系统提示词。比如让模型修复 JSON,我会在系统提示词里写一大段规则:先检查尾逗号、再检查缺引号、检查截断……这套提示词当时觉得挺好用,但用了两周就发现问题了。
第一个痛点是指令膨胀。我同时要处理 JSON 修复、Markdown 格式化、日志分析、代码审查、周报总结等多类任务,每个任务的规则都写进系统提示词,系统提示词很快就超过两千 token。模型注意力被分散,指令越长,后面的规则越容易被忽略,导致任务质量下降。
第二个痛点是任务碎片化。同类任务每天都有,但输入格式、边界条件各不相同。今天我处理截断的 JSON,明天处理带尾逗号的 JSON,后天处理 JSON 中的转义问题。如果每次都重新写一遍规则,不仅效率低,而且每次写的规则还不一致,结果自然不稳定。
第三个痛点是不可复用。同一个修复逻辑,在 A 项目里写成了一套提示词,到 B 项目里又要翻出来改一改。若是换了平台、换了工具链,几乎所有提示词都要重写。这种“一次性提示词”的模式让我感觉很浪费,我想把它们变成像代码模块一样的东西,随时 import。
1.2 为什么不用传统的插件或 MCP 工具
提到给 Agent 封装能力,很多人第一反应是 MCP(Model Context Protocol)工具。我也实验了不少 MCP 服务,比如数据库查询工具、文件操作工具、网页抓取工具。它们确实适合连接外部系统、获取实时数据和执行结构化命令。但我在实际使用中逐渐发现,MCP 工具是“代码级”的能力,它解决的是“Agent 怎么调用一个函数”的问题。
而 agent-skills 想解决的是另一个问题:Agent 在处理一个任务时,该按什么流程思考、有哪些规则要遵守、什么样的输出算合格。这更像是“知识级”或“流程级”的能力。举个例子,JSON 修复我完全可以写一个format_json()的 MCP 工具,但遇到不完整的 JSON 时,工具可能会直接报错;而一个技能可以告诉模型“先尝试解析,失败后检查尾逗号,再检查引号,最后尝试修复”,这是工具做不到的。
插件方式也不行。很多插件绑定特定平台,换个环境就得重新折腾。agent-skills 选择用纯 Markdown 和目录结构组织,本身不依赖某个具体平台的 SDK,任何能读 Markdown 的 Agent 都能使用。这个决策让技能库的迁移成本变得很低。
1.3 agent-skills 的定位:给 Agent 用的 SOP 手册
你可以把 agent-skills 想象成给新员工写的岗位操作手册。新员工叫 Agent,手册里每个技能对应一个岗位任务。手册不会在上班第一天全塞给员工,而是在员工遇到对应任务时,自动打开相关章节,照着做就行。
这套设计有三个核心要素:触发条件、执行步骤、验收标准。触发条件决定 Agent 什么时候学习这份技能;执行步骤是模型在处理任务时必须遵守的操作序列;验收标准是任务完成后的自检清单,确保输出合格。
和 RAG 相比,agent-skills 也有明显区别。RAG 是被动检索,你问一个问题,系统去向量库里找相似文本,把结果拼进上下文。而技能库是按需加载,Agent 根据当前任务的语义主动决定加载哪个技能。这种机制更贴近“能力调用”,而不是“知识查询”。
2. agent-skills 的核心设计与目录结构
2.1 单个技能包长什么样
一个技能包就是一个独立目录,目录下至少有 SKILL.md 文件,也可以附带示例文件、模板、参考数据等。我习惯的目录结构:
agent-skills/ skills/ json-repair/ SKILL.md examples/ broken.json fixed.json log-analyzer/ SKILL.md templates/ analysis-template.md markdown-formatter/ SKILL.mdSKILL.md 是技能的核心,由一个 YAML frontmatter 和正文 Markdown 组成。frontmatter 记录技能的名称、描述、触发场景、版本号等元信息;正文则包含具体指令、示例和注意事项。
--- name: json-repair description: 修复无法解析的 JSON 字符串,并返回规范化的 JSON 对象 when_to_use: 当输入文本看起来是 JSON 但包含多余逗号、缺少引号、内容被截断或转义错误时 version: 1.2.0 ---这种格式有一个非常大的好处:任何能读 Markdown 的工具都能解析它,不需要额外写注册代码。我用 Python 脚本扫一遍目录就能建立索引,用 Claude 时它能直接读取 SKILL.md 的内容,非常灵活。
2.2 为什么用 Markdown 而不是 Python 实现
有人问我:既然都是给 Agent 用,为什么技能正文不直接写代码?这个问题的答案其实很简单。
第一,Markdown 是为“人”写的,也恰好是 LLM 训练时见过最多的文本形式。模型读 Markdown 的指令,比读压缩过的代码字符串更容易理解。第二,技能的大部分内容是“规则和思考过程”,用自然语言描述比用 Python 强得多。第三,Markdown 可以方便地做差异比较,代码接口很难做到这一步。
当然,如果某个技能需要调用外部 API、操作数据库、或者做精确的数值计算,那就别硬塞在 SKILL.md 里。我的原则是:需要真实执行环境的能力交给 MCP 工具,需要模型发挥判断力的能力写成技能。两者互补,而不是互相替代。
2.3 技能库的存放位置与加载机制
在 agent-skills 项目里,技能库可以放在两个层级。一个是全局技能目录,比如~/.agent-skills/skills,所有项目都能用;另一个是项目级目录,比如当前项目下的.agent-skills/skills,只对当前项目生效。层级越近优先级越高,项目级技能能覆盖全局同名技能。
加载机制是我最看重的地方。Agent 在启动时并不会把技能库里所有 SKILL.md 全部读进上下文,那样 token 开销太大。实际做法是:Agent 会先扫描技能目录,读取每个 SKILL.md 的 frontmatter,尤其是name和description字段,形成一个“技能清单”。当任务描述与某个技能的description匹配时,Agent 才把完整的 SKILL.md 内容加载进上下文。
这就是为什么description如此重要。它相当于技能包的“门面”,写得好不好直接决定了这个技能会不会被正确触发。我见过一些技能内容很扎实,但 description 写得太宽泛,结果 Agent 在不需要的时候加载了它,反而干扰主任务。
2.4 命名与描述规范(非常重要)
我在实践中总结了几条硬性规范,现在把这些规范直接固化到了项目的 CONTRIBUTING 文档里。
第一,技能名使用短横线分隔的小写英文,动词开头。json-repair、log-analyzer、weekly-report-generator都是好名字。不要用utils、helper这种谁也说不清功能的词。
第二,description 必须包含“场景 + 输入 + 输出”三个要素。比如:
| 写法 | 效果 |
|---|---|
| 修复 JSON | 太泛,会在很多不相关的 JSON 场景触发 |
| 当输入文本是截断的 JSON 或包含尾逗号的 JSON 时,解析并输出完整 JSON 对象 | 准确,触发时机可控 |
第三,一行只写一个技能描述,不要在 description 里堆叠多个意图。我接手过的技能库中,很多问题都出在“这个技能既能处理 A,也能处理 B”的描述上,模型容易迷茫,结果 A、B 都处理不好。宁可多拆几个技能,也不要搞全能型技能。
3. 从零编写一个技能:以“JSON 数据修复”为例
3.1 为什么选这个技能作为样板
JSON 修复是我日常使用频率最高的技能之一,也特别适合用来讲解技能编写,因为它边界清晰、易于测试、失败模式明确。无论是大模型输出的 JSON 偶尔出错,还是外部接口返回了不完整的数据,都需要一个能稳定处理这类问题的能力。
这个技能的输入是“一段可能不是合法 JSON 的文本”,输出是“一个合法且结构完整的 JSON 对象”。失败模式也很清楚:要么修不好,要么修坏了。正因为如此,我们可以很容易地通过单元测试来验证技能是否有效。
3.2 编写 SKILL.md 的详细过程
我写技能正文时,会遵循一个结构模板:
--- name: json-repair description: 当输入文本是截断的 JSON 或包含尾逗号的 JSON 时,解析并输出完整 JSON 对象 when_to_use: 字符串看起来像 JSON 但无法被标准解析器解析时使用 version: 1.2.0 --- # json-repair ## 目标 将不合法但可能修复的 JSON 文本转换为合法的 JSON 对象。 ## 处理规则 1. 尝试用标准 JSON 解析器解析输入文本。如果成功,直接返回。 2. 如果失败,依次检查以下问题: - 是否存在多余尾逗号,例如 `{"a": 1,}`。 - 是否存在缺少双引号的键,例如 `{a: 1}`。 - 是否存在单引号代替双引号,例如 `{'a': 1}`。 - 是否存在字符串未闭合或内容被截断,例如 `{"a": "hello`。 - 是否存在注释或特殊空白字符,例如 `// comment`。 3. 修复时保持原意,不要猜测缺失数据,不要添加额外字段。 4. 修复完成后,将文本解析为 JSON 对象。 ## 输出格式 返回一个 JSON 代码块,内容包含修复后的 JSON,加上一行修复说明。 ## 示例 输入: `{"name": "Alice", "age": 30,}` 输出: ````json {"name": "Alice", "age": 30}修复说明: 移除了多余尾逗号
这个模板现在很多技能都在用。你会发现,它的核心不是给模型一套严格的“算法”,而是给了模型一个决策顺序:先尝试、后检查、再修复、最后输出。模型在推理时天然喜欢这种渐进式流程。 ### 3.3 如何测试和调优技能 写完之后不能直接丢进库里,必须做一轮系统的测试。我的测试方法很简单:准备一组“坏 JSON”样例,包括尾逗号、缺引号、单引号、截断、注释混合等情况。然后在真实 Agent 环境里强制加载 `json-repair` 技能,逐个输入这些样例,记录修复成功率和输出质量。 第一次测试的结果通常不会太好。我遇到过技能指令写得不够细,比如没有明确“不要猜测缺失数据”,模型就自作主张补了一个缺失字段。后来我在规则里明确加上“不要添加不存在的信息”,效果立刻不一样。 调优是一个持续迭代的过程。每发现一个失败案例,我会分析它属于规则覆盖不到还是模型没遵守规则。如果规则覆盖不到,就补充规则;如果模型没遵守,就把规则提前到“处理规则”第一条,或者用加粗、短句来强化。 ### 3.4 验证 token 开销 技能加载不是免费的。我实测过一份 100 行左右的 SKILL.md,完整加载进上下文大约需要 800 到 1200 token。如果 Agent 同时加载 5 个技能,就会增加 5000 token 左右,确实不少。 这让我更坚定“按需加载”的必要。也提醒我写正文时要控制篇幅,尽量去掉官话套话。技能里的每一个字都可能是从模型注意力里挤出来的,能不写就不写。 ## 4. 技能库的管理与版本控制 ### 4.1 用 Git 管理技能库 技能本质上是文本资产,非常适合用 Git 来管理。我现在的 agent-skills 就是一个 Git 仓库,每个技能是一个独立目录,目录里有自己的变更历史和说明文档。 这里有一个很重要的设计原则:技能之间不要相互耦合。一个技能如果依赖于另一个技能的文件路径,一改路径就得跟着改,非常脆弱。我在仓库里要求每个技能尽量自包含,如果确实需要引用公共资源,则把资源复制到技能自己的目录下,而不是引用外部路径。 安装到本地时,我写了一个非常简单的 `install.sh`,它读取仓库里的技能清单,然后把每个技能目录软链接到全局技能目录。链接方式的好处是,仓库里的改动可以立刻生效,不需要反复复制。 ```bash # 安装所有技能到全局目录 ./install.sh ~/.agent-skills/skills这个脚本本质上就是一个循环加ln -s,没什么高深内容。但对我来说,省去了每次手动复制文件的麻烦,也让团队共享技能库变得容易。
4.2 为技能增加元信息与依赖管理
当技能数量超过十个,就会开始面临依赖和冲突问题。我在 frontmatter 里加了两个可选字段:requires和tools。
requires用来声明该技能依赖的其他技能,比如一个“周报生成”技能可能依赖“数据摘要”。Agent 在加载技能时,如果发现requires字段,会先确保依赖的技能已经加载。tools用来声明技能执行过程中需要用到的外部工具,比如bash、file-reader,这样 Agent 可以提前准备。
冲突处理也比较粗暴。我规定同一路径下不能出现同名技能,如果两个分支都修改了某个技能,以版本号高的为准。这个规则虽然简单,但对个人项目完全够用。
4.3 自动更新与 CI
既然技能库是 Git 仓库,我自然加了一个简单的 CI 流程。GitHub Actions 在每次推送时跑两条命令:一条检查所有 SKILL.md 的 frontmatter 是否完整、description 是否超过预设长度;另一条跑一组预设技能测试。
测试用例我直接写在仓库的tests/目录下。比如对于 json-repair,会有一个脚本逐个读取examples/broken.json,调用 Agent 执行技能,然后对比输出是否与examples/fixed.json一致。这样每次改动技能后,我都能知道有没有破坏原有能力。
CI 还有一个作用:自动生成索引 README。我把每个技能的description汇总到一个INDEX.md,方便人眼快速浏览,也方便 Agent 扫描时加载更准的索引。
5. 常见问题与排查技巧实录
5.1 技能总是“该触发时不触发”
这是最让人抓狂的问题。明明技能写得挺完整,可 Agent 遇到真实任务时就是不加载。我排查过很多次,大部分原因都出在description上。要么是描述太泛,模型觉得这不是一个“专门任务”;要么是描述里的措辞和用户提示词差距太大,模型没有把两者关联起来。
我后来在描述里强制加入“当……时”的句式,把触发场景写清楚。比如不写“处理 Markdown 表格”,而是写“当输入的文本中包含混乱或不对齐的 Markdown 表格时,将其整理为标准表格”。
还有一种排查方法:在 Agent 里手动触发。直接说“使用 json-repair 技能处理下面这段内容”,看看技能能否正常加载。如果手动能触发、自动不行,那肯定是触发条件描述的问题。
5.2 技能被误触发
和“不触发”相反,有些技能用得太勤,稍沾边就跑出来了。这往往是因为 description 写得过于宽泛,或者在技能正文开头没有设置“负面防御”。
我会在每条 skill 的 “处理规则” 前加一句话:“只有满足以下所有条件时才使用本技能,否则不要执行。” 然后列出条件。这句话虽然简单,但对模型的行为纠偏非常有效。
另一个技巧是给 description 加上排除项,比如“不适用于已经结构化且合法的 JSON”。模型读清单时会把这个条件记在心里,误触发率能下降不少。
5.3 指令写了但模型不执行
有时技能加载了,也按照内容执行了,但模型只走马观花地读一遍,没有真正遵守操作序列。这种情况常见于技能正文过长,重点被淹没。
我的解决办法是用“必须”和“禁止”来标记硬性规则:比如“必须保持原意,禁止添加新字段”。模型对这类强约束词比较敏感。另外,把最重要的步骤放在最前面,一条技能的核心步骤不要超过五条,否则模型的记忆很容易打折。
还有一个隐蔽的原因:版本没更新。本地用的还是旧文件,模型看到的内容是缓存里的。排查时务必检查技能文件路径和时间戳。
5.4 多个技能同时可用的顺序冲突
当 Agent 同时加载了多个技能,比如“数据清洗”和“周报生成”,模型可能会搞不清先后顺序。我会用一个“编排型技能”来解决这个问题。它不是真正的处理技能,而是定义了一个流程,告诉 Agent 遇到复合需求时先调用哪个、再调用哪个。
比如在周报生成技能里写:“如果输入数据包含脏数据,必须先使用>