为 Agent 添加 skill 支持

4765 字
24 分钟
为 Agent 添加 skill 支持

为 AI 代理或开发工具添加 Agent Skills 支持的指南。

如今的主流 Agent 都已集成了 skill 支持。对于大部分开发者而言,掌握如何创建一个 skill 便以足够。虽然如此,了解 Agent 如何调用 skill 有助于我们更好地理解 skill 的相关机制。

本指南将逐步介绍如何为 AI 代理或开发工具添加 Agent Skills 支持。它涵盖了完整的生命周期:发现技能、向模型告知技能、将技能内容加载到上下文中,以及长期保持这些内容的有效性。

无论你的代理架构如何,核心集成方式都是相同的。实现细节因以下两个因素而异:

  • 技能存放在哪里? 本地运行的代理可以扫描用户文件系统中的技能目录。云托管或沙箱化的代理则需要替代的发现机制——API、远程注册表或捆绑的资源。
  • 模型如何访问技能内容? 如果模型具有文件读取能力,它可以直接读取 SKILL.md 文件。否则,你需要提供一个专用工具,或以编程方式将技能内容注入提示中。

本指南会在这些差异出现时加以说明。你不需要支持所有场景——请遵循适合你代理的路径。

前置条件:熟悉 agentskills.io 规范 ,该规范定义了 SKILL.md 文件格式、前置元数据字段和目录约定。

核心原则:渐进式披露#

每个兼容技能的代理都遵循相同的三层加载策略:

层级加载内容时机Token 成本
1. 目录名称 + 描述会话启动每个技能约 50-100 token
2. 指令完整 SKILL.md 正文技能被激活时< 5000 token(推荐)
3. 资源脚本、参考文档、资源文件指令引用它们时不定

模型从一开始就看到目录,因此它知道有哪些技能可用。当它判断某个技能相关时,就会加载完整的指令。如果这些指令引用了支持文件,模型会按需逐个加载它们。

这使得基础上下文保持小巧,同时让模型能在需要时访问专业知识。一个安装了 20 个技能的代理,不需要预先支付 20 套完整指令集的 token 成本——只需为在给定对话中实际使用的那些支付。

步骤 1:发现技能#

在会话启动时,找到所有可用技能并加载其元数据。

1.1 扫描哪些位置#

扫描哪些目录取决于你的代理环境。大多数本地运行的代理至少扫描两个范围:

  • 项目级(相对于工作目录):特定于项目或仓库的技能。
  • 用户级(相对于主目录):对给定用户在所有项目中可用的技能。

其他范围也是可能的——例如,由管理员部署的组织级技能,或与代理本身捆绑的技能。正确的范围集合取决于你的代理部署模型。

在每个范围内,考虑同时扫描客户端特定目录.agents/skills/ 约定

范围路径用途
项目<project>/.<your-client>/skills/你的客户端的原生位置
项目<project>/.agents/skills/跨客户端互操作性
用户~/.<your-client>/skills/你的客户端的原生位置
用户~/.agents/skills/跨客户端互操作性

.agents/skills/ 路径已成为跨客户端技能共享的广泛采用的约定。虽然 Agent Skills 规范没有强制规定技能目录必须放在哪里(它只定义了目录内部的内容),但扫描 .agents/skills/ 意味着其他兼容客户端安装的技能会自动对你的客户端可见,反之亦然。

Info

一些实现还会扫描 .claude/skills/(项目级和用户级)以实现实用的兼容性,因为许多现有技能都安装在那里。其他可能的位置还包括 git 根目录的祖先目录(对 monorepo 有用)、XDG 配置目录,以及用户配置的路径。

1.2 扫描什么#

在每个技能目录中,查找包含名为 SKILL.md 文件的子目录

~/.agents/skills/
├── pdf-processing/
│ ├── SKILL.md ← 被发现
│ └── scripts/
│ └── extract.py
├── data-analysis/
│ └── SKILL.md ← 被发现
└── README.md ← 被忽略(不是技能目录)

实用的扫描规则:

  • 跳过不会包含技能的目录,如 .git/node_modules/
  • 可选择遵循 .gitignore 以避免扫描构建产物
  • 设置合理的边界(例如,最大深度 4-6 层,最多 2000 个目录),以防止在大型目录树中失控扫描

1.3 处理名称冲突#

当两个技能共享相同的 name 时,应用确定的优先级规则。

现有实现中的通用约定:项目级技能覆盖用户级技能。

在同一范围内(例如,在 <project>/.agents/skills/<project>/.<your-client>/skills/ 下都找到了名为 code-review 的技能),选择先找到或后找到都可以——选定一种并保持一致。当发生冲突时记录警告,以便用户知道某个技能被遮蔽了。

1.4 信任考量#

项目级技能来自正在处理的仓库,该仓库可能是不可信的(例如,新克隆的开源项目)。考虑在信任检查的基础上加载项目级技能——仅当用户已将项目文件夹标记为受信任时才加载它们。这可以防止不可信的仓库在代理上下文中静默注入指令。

1.5 云托管和沙箱化代理#

如果你的代理在容器中或远程服务器上运行,它将无法访问用户的本地文件系统。根据技能范围,发现需要以不同的方式进行:

  • 项目级技能通常是最简单的情况。如果代理在克隆的仓库上操作(即使在沙箱内),项目级技能会随代码一起传输,并且可以从仓库的目录树中扫描。
  • 用户级和组织级技能在沙箱中不存在。你需要从外部源配置它们——例如,克隆配置仓库、通过代理设置接受技能 URL 或包,或让用户通过 Web UI 上传技能目录。
  • 内置技能可以作为静态资源打包在代理的部署产物中,使其在每个会话中都可用,无需外部获取。

一旦技能对代理可用,生命周期的其余部分——解析、披露、激活——就以相同的方式工作。

步骤 2:解析 SKILL.md 文件#

对于每个被发现的 SKILL.md,提取元数据和正文内容。

2.1 前置元数据提取#

一个 SKILL.md 文件有两个部分:--- 分隔符之间的 YAML 前置元数据,以及结束分隔符之后的 Markdown 正文。解析方法:

  1. 找到文件开头的 --- 及其后的结束 ---
  2. 解析两者之间的 YAML 块。提取 namedescription(必需),以及任何可选字段。
  3. 结束 --- 之后的所有内容(去除首尾空白)即为技能的正文内容。

请参阅 agentskills.io 规范 了解完整的前置元数据字段列表及其约束。

2.2 处理格式错误的 YAML#

为其他客户端编写的技能文件可能包含技术上无效的 YAML,但其解析器恰好能接受。最常见的问题是包含冒号的未加引号的值:

# 技术上无效的 YAML——冒号破坏了解析
description: Use this skill when: the user asks about PDFs

考虑一种回退机制,在重试前用引号包裹此类值或将其转换为 YAML 块标量。这能以最小成本提高跨客户端兼容性。

2.3 宽松验证#

对问题发出警告,但在可能的情况下仍然加载技能:

  • 名称与父目录名称不匹配 → 警告,仍然加载
  • 名称超过 64 个字符 → 警告,仍然加载
  • 描述缺失或为空 → 跳过该技能(描述对于披露至关重要),记录错误
  • YAML 完全无法解析 → 跳过该技能,记录错误

记录诊断信息,以便向用户展示(在调试命令、日志文件或 UI 中),但不要因外观问题而阻止技能加载。

Info

agentskills.io 规范name 字段定义了严格的约束(与父目录匹配、字符集、最大长度)。上面的宽松方法有意放宽了这些约束,以提高与为其他客户端编写的技能的兼容性。

2.4 存储什么#

至少,每个技能记录需要三个字段:

字段描述
name来自前置元数据
description来自前置元数据
locationSKILL.md 文件的绝对路径

将这些存储在以 name 为键的内存映射中,以便在激活时快速查找。

你也可以在发现时存储正文(前置元数据之后的 Markdown 内容),或在激活时从 location 读取。存储它会使激活更快;在激活时读取它在总体上使用更少内存,并且能捕获两次激活之间技能文件的更改。

技能的基础目录location 的父目录)稍后需要用于解析相对路径和枚举捆绑资源——需要时从 location 派生即可。

步骤 3:向模型披露可用技能#

告诉模型存在哪些技能,但不加载其完整内容。这是渐进式披露的第 1 层。

3.1 构建技能目录#

对于每个发现的技能,以适合你栈的任何结构化格式——XML、JSON 或项目符号列表——包含 namedescription,以及可选的 locationSKILL.md 文件的路径):

<available_skills>
<skill>
<name>pdf-processing</name>
<description>Extract PDF text, fill forms, merge files. Use when handling PDFs.</description>
<location>/home/user/.agents/skills/pdf-processing/SKILL.md</location>
</skill>
<skill>
<name>data-analysis</name>
<description>Analyze datasets, generate charts, and create summary reports.</description>
<location>/home/user/project/.agents/skills/data-analysis/SKILL.md</location>
</skill>
</available_skills>

location 字段有两个用途:它支持文件读取激活(参见步骤 4),并为模型提供解析技能正文中相对引用(如 scripts/evaluate.py)的基础路径。如果你的专用激活工具在其结果中提供了技能目录路径(参见步骤 4 中的结构化包装),则可以从目录中省略 location。否则,请包含它。

每个技能为目录增加大约 50-100 个 token。即使安装了数十个技能,目录仍然保持紧凑。

3.2 将目录放在哪里#

两种常见方法:

系统提示部分:将目录作为系统提示中的一个带标签部分添加,前面加上关于如何使用技能的简要说明。这是最简单的方法,适用于任何能访问文件读取工具的模型。

工具描述:将目录嵌入专用技能激活工具的描述中(参见步骤 4)。这使系统提示保持干净,并将发现与激活自然耦合。

两种方法都有效。系统提示放置更简单且兼容性更广;当你拥有专用激活工具时,工具描述嵌入更干净。

3.3 行为指令#

在目录旁边包含一个简短的指令块,告诉模型如何以及何时使用技能。措辞取决于你支持的激活机制(参见步骤 4):

如果模型通过读取文件来激活技能:

以下技能为特定任务提供专门指令。
当任务匹配技能描述时,请先使用文件读取工具
加载所列位置的 SKILL.md,然后再继续。
当技能引用相对路径时,请相对于技能目录
(SKILL.md 的父目录)解析它们,并在工具调用中使用绝对路径。

如果模型通过专用工具激活技能:

以下技能为特定任务提供专门指令。
当任务匹配技能描述时,请调用 activate_skill 工具
并传入技能名称以加载其完整指令。

保持这些指令简洁。目标是告诉模型技能存在以及如何加载它们——技能内容本身在加载后会提供详细的指令。

3.4 过滤#

某些技能应从目录中排除。常见原因:

  • 用户在设置中禁用了该技能
  • 权限系统拒绝访问该技能
  • 该技能选择退出模型驱动的激活(例如,通过 disable-model-invocation 标志)

从目录中完全隐藏被过滤的技能,而不是列出它们并在激活时阻止。这可以防止模型浪费轮次尝试加载它无法使用的技能。

3.5 当没有技能可用时#

如果没有发现技能,请完全省略目录和行为指令。不要显示空的 <available_skills/> 块,也不要注册没有有效选项的技能工具——这会混淆模型。

步骤 4:激活技能#

当模型或用户选择某个技能时,将完整指令传递到对话上下文中。这是渐进式披露的第 2 层。

4.1 模型驱动的激活#

大多数实现依赖模型自身的判断作为激活机制,而不是实现代理侧的触发器匹配或关键词检测。模型读取目录(来自步骤 3),判断某个技能与当前任务相关,然后加载它。

两种实现模式:

文件读取激活:模型使用其标准文件读取工具,并传入目录中的 SKILL.md 路径。无需特殊基础设施——代理现有的文件读取能力就足够了。模型将文件内容作为工具结果接收。当模型具有文件访问权限时,这是最简单的方法。

专用工具激活:注册一个工具(例如 activate_skill),该工具接受技能名称并返回内容。当模型无法直接读取文件时,这是必需的;即使模型能读取,这也是可选的(但有用的)。相比原始文件读取的优势:

  • 控制返回的内容——例如,去除 YAML 前置元数据或保留它(参见下文模型接收什么)
  • 将内容包装在结构化标签中,以便在上下文管理期间识别
  • 在指令旁边列出捆绑资源(例如 references/*
  • 强制执行权限或提示用户同意
  • 跟踪激活情况以进行分析
Tip

如果你使用专用激活工具,请将 name 参数限制为有效技能名称的集合(例如,在工具模式中作为枚举)。这可以防止模型虚构不存在的技能名称。如果没有可用技能,则根本不要注册该工具。

4.2 用户显式激活#

用户也应能直接激活技能,而无需等待模型决定。最常见的模式是斜杠命令或提及语法/skill-name$skill-name),由代理拦截。具体语法由你决定——关键思想是代理处理查找和注入,因此模型无需自行采取激活操作即可接收技能内容。

自动补全小部件(在用户输入时列出可用技能)也能使其可被发现。

4.3 模型接收什么#

当技能被激活时,模型会接收技能的指令。关于具体内容,有两种选择:

完整文件:模型看到完整的 SKILL.md,包括 YAML 前置元数据。这是文件读取激活的自然结果,模型读取原始文件。对于专用工具,这也是一个有效的选择。前置元数据可能包含在激活时有用的字段——例如,compatibility 记录了环境要求,可以指导模型如何执行技能的指令。

仅正文(去除前置元数据):代理解析并移除 YAML 前置元数据,仅返回 Markdown 指令。在拥有专用激活工具的现有实现中,大多数采用这种方法——在发现期间提取 namedescription 后去除前置元数据。

两种方法在实践中都有效。

4.4 结构化包装#

如果你使用专用激活工具,请考虑将技能内容包装在识别标签中。例如:

<skill_content name="pdf-processing">
# PDF 处理
## 何时使用此技能
当用户需要处理 PDF 文件时使用此技能...
[SKILL.md 正文的其余部分]
技能目录:/home/user/.agents/skills/pdf-processing
此技能中的相对路径相对于技能目录。
<skill_resources>
<file>scripts/extract.py</file>
<file>scripts/merge.py</file>
<file>references/pdf-spec-summary.md</file>
</skill_resources>
</skill_content>

这具有实际好处:

  • 模型可以清楚地将技能指令与其他对话内容区分开
  • 代理可以在上下文压缩期间识别技能内容(步骤 5)
  • 捆绑资源被展示给模型,而无需急切加载

4.5 列出捆绑资源#

当专用激活工具返回技能内容时,它还可以枚举技能目录中的支持文件(脚本、参考文档、资源文件)——但不应急切地读取它们。当技能的指令引用特定文件时,模型会使用其文件读取工具按需加载。

对于大型技能目录,考虑限制列表大小,并注明可能不完整。

4.6 权限允许列表#

如果你的代理具有控制文件访问的权限系统,请将技能目录加入允许列表,以便模型可以读取捆绑资源而无需触发用户确认提示。如果没有这个,每次引用捆绑脚本或参考文件都会导致权限对话框,从而破坏包含 SKILL.md 以外资源的技能的流程。

步骤 5:随时间管理技能上下文#

一旦技能指令进入对话上下文,请在整个会话期间保持其有效性。

5.1 保护技能内容不被上下文压缩#

如果你的代理在上下文窗口填满时截断或总结较旧的消息,请将技能内容免于修剪。技能指令是持久的行为指导——在对话中途丢失它们会静默降低代理的性能,而不会出现任何可见错误。模型继续运行,但失去了技能提供的专门指令。

常见方法:

  • 将技能工具输出标记为受保护,以便修剪算法跳过它们
  • 使用步骤 4 中的结构化标签来识别技能内容,并在压缩期间保留它

5.2 重复激活去重#

考虑跟踪当前会话中已激活了哪些技能。如果模型(或用户)尝试加载已在上下文中的技能,你可以跳过重新注入,以避免相同指令在对话中多次出现。

5.3 子代理委派(可选)#

这是一种高级模式,仅部分客户端支持。技能指令不是注入到主对话中,而是在单独的子代理会话中运行。子代理接收技能指令,执行任务,并将工作摘要返回给主对话。

当技能的工作流足够复杂,值得一个专用的、专注的会话时,此模式很有用。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

为 Agent 添加 skill 支持
https://agentskills.io/
作者
HAC
发布于
2026-08-11
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录