如何为你的智能体添加技能支持#
为 AI 智能体或开发工具添加 Agent Skills 支持的指南。
本指南将深入探讨如何为 AI 智能体或开发工具添加 Agent Skills(智能体技能)支持。它涵盖了技能的完整生命周期:发现技能、告知模型相关技能、将内容加载到上下文中,以及确保这些内容在长时间会话中持续有效。
无论你的智能体架构如何,核心集成逻辑都是一致的。具体的实现细节会根据以下两个因素而有所不同:
- 技能存储在哪里? 本地运行的智能体可以扫描用户文件系统的技能目录。而云端托管或沙箱化的智能体则需要替代的发现机制——例如 API、远程注册中心或捆绑的静态资产。
- 模型如何访问技能内容? 如果模型具备文件读取能力,它可以直接读取
SKILL.md文件。否则,你需要提供专用的工具,或者通过编程方式将技能内容注入提示词。
本指南在这些差异产生影响的地方进行了说明。你不需要支持所有场景——只需遵循适合你智能体需求的路径即可。
前提条件:熟悉 Agent Skills 规范,该规范定义了 SKILL.md 的文件格式、Frontmatter 字段和目录约定。
核心原则:渐进式披露#
每一个兼容技能的智能体都遵循相同的“三层加载”策略:
| 层级 | 加载内容 | 何时加载 | Token 消耗 |
|---|---|---|---|
| 1. 目录 | 技能名称 + 描述 | 会话开始时 | 每个技能约 50-100 tokens |
| 2. 指令 | SKILL.md 的完整正文 | 技能被激活时 | 推荐 < 5000 tokens |
| 3. 资源 | 脚本、引用、资产 | 当指令中显式引用它们时 | 视具体资源而定 |
模型从一开始就能看到目录,因此它知道有哪些可用技能。当模型判断某个技能相关时,它会加载完整的指令。如果这些指令中引用了辅助文件,模型会根据需要单独加载它们。
这种策略能保持基础上下文精简,同时让模型可以按需获取专业知识。即使一个智能体安装了 20 个技能,它也不需要预先承担 20 套完整指令的 Token 成本——只需为给定对话中实际用到的技能付费。
步骤 1:发现技能#
在会话启动时,寻找所有可用技能并加载其元数据。
扫描位置#
扫描哪些目录取决于智能体的环境。大多数本地运行的智能体至少会扫描两个范围:
- 项目级别(相对于当前工作目录):特定于某个项目或仓库的技能。
- 用户级别(相对于用户主目录):跨所有项目对该用户可用的技能。
除此之外,还可以包含其他范围——例如管理员部署的组织级技能,或者随智能体本身捆绑的技能。
在每个范围内,建议同时扫描客户端特定目录和通用的 .agents/skills/ 约定路径:
| 范围 | 路径 | 用途 |
|---|---|---|
| 项目 | <project>/.<your-client>/skills/ | 你的客户端原生路径 |
| 项目 | <project>/.agents/skills/ | 跨客户端互操作 |
| 用户 | ~/.<your-client>/skills/ | 你的客户端原生路径 |
| 用户 | ~/.agents/skills/ | 跨客户端互操作 |
.agents/skills/ 路径已成为跨客户端技能共享的广泛共识。虽然 Agent Skills 规范并没强制要求技能目录的存放位置(它只定义了目录内部的内容),但扫描 .agents/skills/ 意味着其他兼容客户端安装的技能对你的客户端也是可见的,反之亦然。
为了更好的兼容性,一些实现还会扫描
.claude/skills/(项目级和用户级),因为许多现有技能是安装在那里的。其他可选位置还包括向上追溯至 Git 根目录的父目录(对 Monorepo 很有用)、XDG 配置目录以及用户自定义路径。
扫描对象#
在每个技能根目录下,寻找包含名为 SKILL.md 的文件的子目录:
~/.agents/skills/
├── pdf-processing/
│ ├── SKILL.md ← 发现成功
│ └── scripts/
│ └── extract.py
├── data-analysis/
│ └── SKILL.md ← 发现成功
└── README.md ← 忽略(不是合法的技能目录)实际扫描建议规则:
- 跳过不可能包含技能的目录,如
.git/和node_modules/。 - 可选地遵循
.gitignore以避免扫描构建产物。 - 设置合理的边界(例如:最大深度 4-6 层,最多扫描 2000 个目录)以防止在大规模目录树中扫描失控。
处理名称冲突#
当两个技能拥有相同的 name 时,需要应用一个确定的优先级规则。
通用的惯例是:项目级技能覆盖用户级技能。
如果在同一范围内(例如在 <project>/.agents/skills/ 和 <project>/.<your-client>/skills/ 下都找到了名为 code-review 的技能),可以选择“先到先得”或“后到先得”——只要保持一致即可。发生冲突时建议记录一条警告,以便用户知道某个技能被覆盖了。
信任考量#
项目级技能来源于正在开发的仓库,而仓库可能是不可信的(例如新克隆的开源项目)。考虑在加载项目级技能前进行一种“信任检查”——只有当用户将该项目文件夹标记为受信任时才加载技能。这可以防止恶意仓库静默地向智能体上下文中注入指令。
云端托管和沙箱化智能体#
如果你的智能体在容器或远程服务器上运行,它将无法访问用户的本地文件系统。此时发现机制需要根据技能范围有所调整:
- 项目级技能 通常最容易处理。如果智能体在克隆的仓库上操作(即使是在沙箱内),项目级技能随代码一起存在,可以直接从仓库目录树中扫描。
- 用户级和组织级技能 在沙箱中不存在。你需要从外部来源预置它们——例如,克隆一个配置库、通过智能体设置接受技能 URL 或安装包,或者允许用户通过 Web 界面上传技能目录。
- 内置技能 可以作为静态资产封装在智能体的部署包中,使其在每个会话中无需外部获取即可使用。
一旦技能对智能体可用,后续生命周期——解析、披露、激活——的处理方式都是相同的。
步骤 2:解析 SKILL.md 文件#
对于每个发现的 SKILL.md,提取其元数据和正文内容。
Frontmatter 提取#
一个 SKILL.md 文件分为两部分:位于 --- 限定符之间的 YAML Frontmatter,以及结束限定符之后的 Markdown 正文。解析步骤如下:
- 寻找文件开头的第一个
---以及随后的第二个---。 - 解析之间的 YAML 块。提取必填字段
name和description,以及其他可选字段。 - 结束限定符
---之后的所有内容,经过去除首尾空白后,即为该技能的正文内容。
完整的 Frontmatter 字段及约束请参见规范。
处理格式错误的 YAML#
为其他客户端编写的技能文件可能包含技术上无效的 YAML,但它们的解析器可能碰巧接受了。最常见的问题是在未加引号的值中包含冒号:
# 技术上无效的 YAML —— 冒号会破坏标准解析
description: Use this skill when: the user asks about PDFs考虑增加一个回退机制,在重试解析前尝试将此类值包裹在引号中或转换为 YAML 块标量。这能以极小的成本提高跨客户端的兼容性。
宽容校验#
对发现的问题发出警告,但在可能的情况下仍加载该技能:
- 名称与父目录名称不匹配 → 发出警告,仍加载。
- 名称超过 64 个字符 → 发出警告,仍加载。
- 描述缺失或为空 → 跳过该技能(描述对“披露”至关重要),并记录错误。
- YAML 格式完全无法解析 → 跳过该技能,并记录错误。
记录诊断信息,以便能够向用户展示(如通过 debug 命令、日志文件或 UI),但不要因为一些小的格式问题就封杀整个技能。
规范对
name字段定义了严格约束(需匹配目录名、字符集限制、长度限制)。上述“宽容方法”有意放宽了这些限制,目的是提高与为其他客户端编写的技能的兼容性。
存储内容#
每个技能记录至少需要存储三个字段:
| 字段 | 描述 |
|---|---|
name | 来自 Frontmatter |
description | 来自 Frontmatter |
location | SKILL.md 文件的绝对路径 |
将这些记录存储在以 name 为键的内存 Map 中,以便在激活时快速查找。
你也可以在发现阶段就加载并存储正文内容(Frontmatter 之后的内容),或者在收到激活指令时才按需读取。预先存储能让激活更迅速;按需读取则能节省内存消耗,并能实时获取文件的最新修改。
技能的基础目录(location 的父目录)在后续解析相对路径和枚举资源时会用到——需要时从 location 派生即可。
步骤 3:向模型披露可用技能#
告知模型关于可用技能的信息,但不要加载它们的全部内容。这是 渐进式披露 的第一层。
构建技能目录#
对于每个发现的技能,在适合你技术栈的结构化格式(XML、JSON 或列表均可)中包含 name、description,以及可选的 location(SKILL.md 的路径):
<available_skills>
<skill>
<name>pdf-processing</name>
<description>提取 PDF 文本、填写表单、合并文件。在处理 PDF 时使用。</description>
<location>/home/user/.agents/skills/pdf-processing/SKILL.md</location>
</skill>
<skill>
<name>data-analysis</name>
<description>分析数据集、生成图表并创建摘要报告。</description>
<location>/home/user/project/.agents/skills/data-analysis/SKILL.md</location>
</skill>
</available_skills>location 字段有两个用途:它支持“文件读取激活”模式(见步骤 4),并为模型提供了一个基准路径,用于解析技能正文中的相对引用(如 scripts/evaluate.py)提供基准路径。如果你的专用激活工具会在结果中包含技能路径(见结构化包装),则可以从目录中省略 location。否则,请保留它。
每个技能大约会增加 50-100 个 tokens。即使安装了几十个技能,目录依然会保持非常小巧。
放置位置#
常见的方案有两种:
- 系统提示词 (System Prompt) 章节:将目录作为系统提示词中的一个独立部分,并在前面加上如何使用技能的简短说明。这是最简单的方法,适用于任何具备读取文件工具的模型。
- 工具描述 (Tool Description):将目录嵌入到专用激活工具的描述中(见步骤 4)。这能保持系统提示词清爽,并将“发现”与“激活”逻辑自然地解耦。
两种方案都可行。系统提示词法更简单、兼容性更广;工具描述法在有专用激活工具时更显优雅。
行为指令#
在目录旁边包含一段简短的指令块,告诉模型如何以及何时使用技能。具体的措辞取决于你支持的激活机制:
方案 A:如果模型通过读取文件激活技能:
以下技能为特定任务提供了专业指令。
当任务与技能的描述匹配时,在继续操作前,请使用你的文件读取工具加载
所列位置处的 SKILL.md。
当技能中引用相对路径时,请相对于技能目录(SKILL.md 的父目录)进行解析,
并在工具调用中使用绝对路径。方案 B:如果模型通过专用工具激活技能:
以下技能为特定任务提供了专业指令。
当任务与技能的描述匹配时,请调用 activate_skill 工具
并传入对应的技能名称,以加载其完整指令。保持这些指令简洁。目标只是告诉模型技能已经存在以及如何获取它们——技能内容本身加载后会接管详细的行为指导。
过滤机制#
某些技能应当从模型可见的目录中排除。常见原因包括:
- 用户在设置中明确禁用了该技能。
- 权限系统拒绝了对该技能的访问。
- 该技能通过 frontmatter(如
disable-model-invocation: true)主动选择了退出模型调用。
应直接从目录中移除这些被过滤的技能,而不要在列出它们后再在激活时报错。这可以防止模型浪费轮次去尝试加载一个无法使用的工具。
当没有可用技能时#
如果没发现任何技能,请完全省略上述所有章节。不要展示空的 <available_skills/> 块或注册一个没有任何选项的技能工具——这只会让模型感到困惑。
步骤 4:激活技能#
当模型或用户选择了一个技能,将其完整指令加载到对话上下文中。这是 渐进式披露 的第二层。
模型驱动激活#
大多数实现方案都依赖于模型的“主观判断”作为激活机制,而不是在客户端硬编码规则匹配。模型读取目录,判断某个技能相关,并主动加载它。
具体的实现方式有两种:
- 文件读取激活:模型调用其标准的文件读取工具,读取目录中提供的
SKILL.md路径。无需额外开发——只要智能体有读文件能力即可。 - 专用工具激活:注册一个特定的工具(如
activate_skill),该工具接收技能名称并返回其原始内容。当模型不能直接读文件时必须用此种方式,即使模型能读文件,这种方式也很有好处。其优势包括:- 内容控制:可以决定返回什么内容(例如是否屏蔽 frontmatter)。
- 结构化识别:可以用标签包裹内容,方便后续上下文管理。
- 枚举资源:在返回指令的同时,可以列出关联的资源文件。
- 安全审计:方便强制执行权限检查或记录激活日志。
如果使用专用工具激活,请将
name参数限制在已发现的技能名称内(例如通过 Enum 枚举)。这能有效防止模型“幻听”不存在的技能。如果没有可用技能,就不要注册此工具。
用户显式激活#
用户也应当能直接激活技能,而不必等待模型决定。最常见的模式是 Slash 命令或提及语法(如 /skill-name 或 $skill-name),这些输入会被客户端拦截并直接注入对应的内容。具体的语法随你定,关键是由客户端处理查找和注入逻辑。
为用户提供一个自动补完的小组件(在用户输入时提示可用技能)会极大地提升这一功能的发现率。
模型接收的内容#
关于指令内容的呈现,有两种主流选择:
- 全量文件:模型看到包含 Frontmatter 的完整 Markdown。这在文件读取模式下是自然的。某些前置字段(如
compatibility硬件/环境要求)可能对模型的决策依然有用。 - 纯正文(去除 Frontmatter):客户端解析并剥离 YAML,仅返回 Markdown 核心指令。大多数采用专用激活工具的实现都倾向于这种更干净的做法。
实践表明,这两种方式都能正常工作。
结构化包装#
如果你采用了专用激活工具,建议将返回的内容包装在可辨识的标签中。例如:
<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.md</file>
</skill_resources>
</skill_content>这种结构化包装的好处:
- 模型能明确区分“技能指令”与“普通对话”。
- 客户端在进行上下文压缩(步骤 5)时能轻松识别并特殊处理。
- 探测关联资源但不强制加载。
枚举配套资源#
当激活工具返回指令时,它可以一并列出目录下的配套资源(脚本、文档、资产),但不要自动读取它们。模型会在需要时,根据指令的引导,使用其文件读取工具按需加载特定的资源。
如果目录极其庞大,建议对列表长度进行截断,并加注“可能未完全显示”。
权限白名单机制#
如果你的智能体有管控文件访问的权限系统,请记得将技能目录加入白名单。否则,每当模型想读取一个配套的脚本或参考文档时,都会弹出一个权限确认框,极大地干扰了自动化体验。
步骤 5:管理随时间变化的技能上下文#
技能加载进来后,需要确保它们在整个会话中都能发挥作用。
防止在上下文压缩中由于误删而失效#
当对话由于太长而不得不截断或总结时,请务必保护技能内容不被清理。技能指令是持久性的行为准则——一旦在会话中途丢失,智能体的表现通常会大幅退化,且往往不会报错,只会表现为“变笨了”。
常用手段:
- 将激活工具的输出标记为“不可清理”。
- 利用结构化标签(见步骤 4)在压缩算法中识别出技能板块并强制保留。
去重确认#
记录当前会话中已经激活的技能清单。如果模型或用户尝试再次加载已经存在于上下文中的技能,可以忽略本次请求,以节省上下文空间并防止指令冗余。
子智能体委派(可选)#
这是一种进阶模式。技能指令不再注入给主模型,而是启用一个全新的、专注的子智能体会话,并将技能加载给它。子智能体完成任务后,仅将执行结果和摘要返回给主会话。这种方式能有效地隔离复杂任务的上下文干扰。