技能文件的最小构成

摘要

在构建 AI Agent 技能(Skill)体系时,很多开发者会陷入一个误区:认为一个"完整"的技能必须包含脚本目录、参考文档、资源文件等一整套结构。事实上,一个技能的最小构成只需要一个 SKILL.md 文件。scripts、references、assets 这些目录都是按需添加的可选组件,而非必需项。本文将深入剖析技能文件的最小构成原则,讲解 SKILL.md 的核心要素,并通过完整的代码示例演示如何从零构建一个最小可用技能,以及何时、如何逐步扩展它。

引言

如果你接触过 Claude Skills、Agent Skills 或类似的技能扩展机制,你大概率见过这样的目录结构:

my-skill/
├── SKILL.md
├── scripts/
│   └── helper.py
├── references/
│   └── api-docs.md
└── assets/
    └── template.json

这个结构看起来很"专业",于是很多人在创建技能时,第一反应就是先把这四个部分都建好,哪怕 scripts 里空空如也,references 里只有一句占位文字。这种做法不仅没有必要,反而会带来几个实际问题:

  1. 增加认知负担:维护者需要判断每个目录该放什么,容易放错位置。
  2. 降低可读性:Agent 在加载技能时需要处理更多无关内容。
  3. 阻碍快速迭代:一个本来 5 分钟能写完的技能,被结构化的仪式感拖慢。

技能系统的设计哲学其实非常克制:一个 SKILL.md 就是全部必需。其他一切都是"当且仅当需要时才添加"的增量。本文将从最小构成讲起,逐层拆解 SKILL.md 的必备要素,再讨论何时应该引入 scripts、references、assets,最后给出一个渐进式演进的完整实践。

正文

一、为什么 SKILL.md 就是全部必需

要理解"最小构成",先要理解技能的本质。一个技能(Skill)对 Agent 而言,本质上是一段结构化的指令 + 元数据,用来告诉模型:

  • 我是谁(名称、描述)
  • 我什么时候该被使用(触发条件)
  • 我该怎么做事(操作步骤、约束、示例)

这三件事,全部可以用一个 Markdown 文件表达。Markdown 天然适合承载自然语言指令,而 YAML frontmatter 天然适合承载结构化元数据。两者结合,就构成了技能的最小完备表达。

从加载机制看,Agent 在启动时通常只读取 SKILL.md 的元数据(名称、描述)来做技能路由;只有当技能被真正触发时,才会读取正文内容。这意味着:

  • 没有 SKILL.md,技能就不存在(无法被发现、无法被路由)。
  • 没有 scripts/、references/、assets/,技能依然完整可用(只要 SKILL.md 把该说的说清楚了)。

换句话说,SKILL.md 是"必要条件",其他目录是"充分条件的可选增强"。把这句话刻在脑子里,你就不会在创建技能时做无用功。

一个真正意义上的最小技能,长这样:

hello-skill/
└── SKILL.md

就这么简单。下面我们来拆解这个文件里到底该写什么。

二、SKILL.md 的必备要素:frontmatter

SKILL.md 的第一部分是 YAML frontmatter,用 --- 包裹。它承载技能的元数据,是技能被正确发现和路由的关键。最小可用版本至少需要两个字段:

---
name: hello-skill
description: 当用户需要生成一个友好的问候语时使用此技能。适用于打招呼、开场白、欢迎消息等场景。
---

这两个字段的含义与写法要点:

字段作用写作要点
name技能的唯一标识小写、连字符分隔、简短、语义化
description技能路由的核心依据说清"做什么"+“何时用”,包含触发关键词

description 是最容易被写坏、也最影响效果的字段。 它不是给人看的简介,而是给模型看的"路由信号"。一个好的 description 应该同时回答两个问题:

  1. 这个技能能做什么?(能力边界)
  2. 什么情况下应该用它?(触发条件)

对比一下:

# ❌ 差:太笼统,模型无法判断何时触发
description: 一个处理文本的技能。

# ✅ 好:能力明确 + 触发场景明确
description: 将 Markdown 文档转换为带样式的 HTML。当用户需要把 .md 文件发布为网页、
需要为博客文章生成 HTML 版本,或要求"把 markdown 转成 html"时使用。

除了 name 和 description,一些实现还支持可选的 license、version、allowed-tools 等字段,但它们都不是必需的。先跑通最小版本,再按需补充元数据,这是正确的顺序。

三、SKILL.md 的必备要素:正文指令

frontmatter 之后是 Markdown 正文。正文是技能的灵魂——它告诉模型"具体怎么做"。一个最小但完整的正文,通常包含以下几个部分(顺序可调整,但建议保持逻辑连贯):

  1. 概述:一两句话说明这个技能解决什么问题。
  2. 使用时机:什么情况下用、什么情况下不用(边界)。
  3. 操作步骤:清晰、可执行的步骤清单。
  4. 输出要求:最终产物应该长什么样。
  5. 示例(可选但强烈推荐):输入 → 输出的具体样例。

下面是一个完整的、可直接使用的最小技能文件:

---
name: friendly-greeting
description: 生成友好、专业的问候语。当用户需要开场白、欢迎消息、打招呼文案,
或要求"写一段问候语""帮我打个招呼"时使用此技能。
---

# 友好问候语生成

## 概述

根据用户提供的场景和对象,生成一段得体、友好的问候语。

## 使用时机

- 用户明确要求生成问候语、开场白、欢迎词。
- 用户提供了场景(如"新同事入职""客户回访")但没有现成文案。

**不适用**:正式的商务合同措辞、法律声明——这些应交给专门技能。

## 操作步骤

1. 识别问候对象(个人 / 群体)与场景(正式 / 非正式)。
2. 确定语气:正式场景用"您好",非正式用"嗨"或"你好"。
3. 生成 1~3 句问候语,包含:称呼 + 问候 + 一句正向表达。
4. 若用户未指定语言,默认使用中文;指定则遵循。

## 输出要求

- 长度控制在 50 字以内。
- 不使用感叹号堆砌,最多一个。
- 直接输出问候语本身,不要附加解释。

## 示例

输入:给新同事的入职欢迎
输出:你好,欢迎加入团队!很高兴认识你,期待一起合作。

把这个文件放进 friendly-greeting/SKILL.md,一个技能就完整可用了。注意:这里没有任何脚本、参考文档或资源文件,但技能功能是完整的。

四、什么时候才需要 scripts/

scripts/ 目录用于存放可执行脚本,通常是被技能调用的确定性逻辑,比如文件处理、数据转换、API 调用封装等。它的引入有一个非常清晰的判断标准:

当某段逻辑用自然语言描述容易出错、或需要确定性执行时,才写成脚本。

举个例子。假设你要做一个"批量重命名图片"的技能。如果只用 SKILL.md 描述:

## 操作步骤
1. 遍历目录下所有 .jpg 文件。
2. 按创建时间排序,依次重命名为 image_001.jpg、image_002.jpg……

模型"理论上"能照做,但实际执行时容易在排序、补零位数、异常处理上出错。这时就该引入脚本:

# scripts/rename_images.py
"""按创建时间批量重命名图片,补零到三位。"""
import sys
from pathlib import Path

def rename_images(directory: str) -> None:
    target = Path(directory)
    # 只处理 jpg,按修改时间排序(跨平台更稳定)
    images = sorted(
        [p for p in target.iterdir() if p.suffix.lower() == ".jpg"],
        key=lambda p: p.stat().st_mtime,
    )
    if not images:
        print("未找到 .jpg 文件")
        return

    for idx, img in enumerate(images, start=1):
        new_name = f"image_{idx:03d}.jpg"
        new_path = target / new_name
        # 避免与自身重名导致报错
        if img.name != new_name:
            img.rename(new_path)
        print(f"{img.name} -> {new_name}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("用法: python rename_images.py <目录>")
        sys.exit(1)
    rename_images(sys.argv[1])

然后在 SKILL.md 中引用它:

## 操作步骤

1. 确认用户提供的目录路径。
2. 执行脚本完成重命名:

   ```bash
   python scripts/rename_images.py "<目录路径>"
  1. 向用户汇报重命名结果。

**关键原则**:脚本是"实现细节的确定性封装",`SKILL.md` 依然是"指令的主入口"。不要因为有了脚本,就把 `SKILL.md` 写成一个空壳——它仍然需要说明何时调用、如何调用、如何处理结果。

### 五、什么时候才需要 references/ 与 assets/

`references/` 和 `assets/` 是最常被"提前创建"的两个目录,也是最不该提前创建的。

**`references/` 用于存放参考文档**,比如 API 手册、规范说明、领域知识。它的引入标准是:

> **当技能需要大量背景知识,且这些知识不适合全部塞进 `SKILL.md` 正文时。**

比如一个"对接某支付网关"的技能,网关有 200 个接口参数。把这些全写进 `SKILL.md` 会让文件臃肿、路由变慢。这时可以:

payment-skill/
├── SKILL.md
└── references/
└── api-reference.md


并在 `SKILL.md` 中指明"需要详细参数时查阅 `references/api-reference.md`"。**注意:只有当正文确实无法承载、且这些知识是"查阅型"而非"执行型"时,才需要 references。**

**`assets/` 用于存放静态资源**,比如模板文件、示例数据、图片、字体等。它的引入标准是:

> **当技能产出需要基于固定模板或固定资源时。**

比如一个"生成周报"的技能,如果周报有固定格式模板,可以放一个 `assets/weekly-report-template.md`。如果只是让模型自由发挥格式,那完全不需要 assets。

一个演进后的、包含全部四个部分的技能可能长这样:

weekly-report/
├── SKILL.md
├── scripts/
│ └── collect_stats.py # 从 git log 收集本周提交统计
├── references/
│ └── report-style-guide.md # 公司周报写作规范
└── assets/
└── template.md # 周报固定模板


但请再次注意:**这个结构是演进而来的,不是一开始就搭好的。** 每一层都应该有明确的、当下的需求支撑。

### 六、渐进式演进:从最小到完整

理解了各部分的职责后,正确的做法是"渐进式演进"。下面用一个真实的演进路径演示。

**阶段 1:最小可用(只有 SKILL.md)**

weekly-report/
└── SKILL.md


```markdown
---
name: weekly-report
description: 生成本周工作周报。当用户要求"写周报""总结本周工作""生成 weekly report"时使用。
---

# 周报生成

## 操作步骤
1. 询问用户本周完成的主要工作(若未提供)。
2. 按"完成事项 / 进行中 / 下周计划"三段式组织内容。
3. 输出 Markdown 格式周报。

此时技能已经能用:模型会主动询问、按三段式组织、输出 Markdown。

阶段 2:发现格式不稳定 → 加 assets

用了几次后发现每次格式都不一样,于是加模板:

weekly-report/
├── SKILL.md
└── assets/
    └── template.md

SKILL.md 正文补充一句:

## 操作步骤
...
4. 严格按照 `assets/template.md` 的结构填充内容。

阶段 3:发现统计工作靠模型算不准 → 加 scripts

需要统计 git 提交次数、代码行数,模型数不准,于是加脚本:

weekly-report/
├── SKILL.md
├── scripts/
│   └── collect_stats.py
└── assets/
    └── template.md

阶段 4:公司规范文档较长 → 加 references

公司有 30 页周报写作规范,塞不进正文,于是加:

weekly-report/
├── SKILL.md
├── scripts/
│   └── collect_stats.py
├── references/
│   └── style-guide.md
└── assets/
    └── template.md

每一次扩展,都是由真实遇到的问题驱动的,而不是"为了结构完整"。这就是"最小构成"哲学的精髓:从最小开始,按需生长。

七、常见误区与检查清单

在实践中最容易踩的坑,我总结成一张检查清单:

误区后果正确做法
一开始就建四个目录结构冗余、维护成本高只建 SKILL.md,按需加目录
description 写得太笼统技能无法被正确触发写清"做什么 + 何时用"
把大量知识塞进正文文件臃肿、加载变慢超出部分移到 references/
用脚本替代指令模型不知道何时调用SKILL.md 仍是主入口,说明调用时机
空目录占位无意义、易误导目录只在有真实文件时创建
正文只有一句"见脚本"技能失去可读性正文必须自解释

发布技能前,问自己三个问题:

  1. 删掉 scripts/、references/、assets/ 后,技能还能用吗? 如果能,说明 SKILL.md 写得合格。
  2. 每个额外目录里的文件,是不是都在 SKILL.md 中被明确引用? 如果没有被引用,它就是死文件。
  3. description 能不能让一个不了解这个技能的模型,准确判断何时触发? 如果不能,回去改。

总结

回到本文的核心命题:一个 SKILL.md 就是全部必需,scripts、references、assets 按需添加。

这句话包含三层含义:

  1. SKILL.md 是必要条件。没有它,技能不存在;有了它,技能就完整可用。它的 frontmatter 负责"被发现与路由",正文负责"被理解与执行"。
  2. 其他目录是可选增强。scripts 解决"确定性执行"问题,references 解决"知识体量"问题,assets 解决"固定资源"问题——每一个都对应一类具体需求,没有需求就不该存在。
  3. 演进优于预设。从最小技能开始,让真实问题驱动结构生长,而不是反过来用结构去套需求。

技能系统的克制设计,本质上是在鼓励一种工程习惯:先让它跑起来,再让它变好。 一个只有 SKILL.md 的技能,往往比一个四目录齐全却内容空洞的技能更有价值。下次创建技能时,不妨先只写那一个文件——你会发现,大多数时候,这就够了。

Logo

更多推荐