1. 从"skills"这个热词说起:它到底指什么
最近一段时间,"skills"这个词在技术社区里出现的频率明显高了起来。如果你只是偶尔刷到,可能会觉得它就是个泛泛的英文单词,没什么特别。但如果你留意过Agent Skills、claude agent skills、codex skills这些组合词,就会发现大家讨论的其实是同一类东西——给 AI 智能体(Agent)挂载的、可复用的能力模块。
我先把结论摆在前面:这里的 skills,本质上是一套"能力封装规范"。它把一段可复用的操作逻辑、工具调用方式、领域知识,打包成一个结构化的模块,让 Agent 在需要的时候能直接加载使用,而不用每次都在提示词里从头描述一遍。你可以把它理解成给 Agent 准备的"技能插件"——装上一个,它就多会一件事。
为什么这个概念会火?因为过去大家用大模型做复杂任务时,最大的痛点就是"每次都要重新教"。你想让模型帮你处理一份财报、生成一套分镜脚本、或者跑一遍代码审计,就得在对话里把流程、格式、注意事项全部写一遍。下次换个会话,又得重来。skills 要解决的就是这个重复劳动问题:把"怎么做事"沉淀成文件,让 Agent 按需加载。
这篇文章我打算从一线实操的角度,把 skills 这件事讲透。包括它的核心结构长什么样、为什么这样设计、怎么从零写一个能用的 skill、在 Agent 里怎么加载和调试、以及我在实际使用中踩过的那些坑。不管你是刚听说这个词想入门,还是已经写过几个 skill 但总觉得不够稳,应该都能从里面找到能直接抄作业的东西。
需要提前说明的是,skills 目前并没有一个绝对统一的官方标准,不同平台(比如围绕 Claude 的 Agent 体系、围绕 Codex 的体系、以及 Google Cloud 上 Genkit 相关的 Agent 工具链)在细节实现上各有差异。但它们的核心思路高度一致,我会以最通用的那套结构为主线来讲,遇到平台差异的地方会单独点出来。
2. skills 的核心结构:为什么是"文件夹 + 说明书"这套组合
2.1 一个 skill 的最小构成
先看一个最朴素的 skill 长什么样。抛开各种平台的包装,一个 skill 通常就是一个文件夹,里面至少包含两类东西:
- 一份说明文件(通常叫
SKILL.md或类似名字):用自然语言描述这个 skill 是干什么的、什么时候该用它、怎么用。 - 可选的配套资源:脚本、模板、参考文档、示例数据等等。
my-skill/ ├── SKILL.md # 核心说明,Agent 靠它判断"要不要用、怎么用" ├── scripts/ │ └── process.py # 具体干活的脚本 ├── templates/ │ └── report.md # 输出模板 └── references/ └── api-notes.md # 领域知识补充这个结构看起来简单得有点过分,但恰恰是它的精髓所在。我第一次看到的时候也疑惑:就一个 Markdown 文件加几个附件,凭什么能叫"技能"?
答案在于Agent 的加载机制。现代 Agent 框架普遍采用"渐进式披露"(progressive disclosure)的思路:启动时只把每个 skill 的名称和简短描述塞进上下文,让模型知道"有这么个能力存在";只有当模型判断当前任务确实需要它时,才把完整的SKILL.md内容和相关资源读进来。这样一来,哪怕你装了几十个 skill,也不会把上下文窗口撑爆。
2.2 为什么说明文件要用自然语言写
这是很多人第一个想不通的点:既然是给程序用的,为什么不写成 JSON 配置或者函数签名,非要写一大段人话?
我的理解是,skill 的"调度者"是模型本身,不是传统代码里的 if-else。模型判断"现在该不该用这个 skill",靠的是语义理解,而不是精确匹配。所以SKILL.md里的描述写得越清楚、越贴近真实使用场景,模型判断得就越准。
举个例子,对比两种写法:
# 写法 A:干巴巴 name: pdf-processor description: 处理 PDF # 写法 B:带场景 name: pdf-processor description: 当需要从 PDF 中提取表格数据、拆分页面、 或把多个 PDF 合并时使用。支持扫描件 OCR 和文本层提取。写法 B 明显更容易让模型在"用户上传了一份扫描版合同,想提取里面的金额表格"这种场景下正确命中。这就是自然语言描述的价值——它承载的是意图匹配所需的信息。
2.3 描述字段的写法直接决定命中率
我踩过最多次的坑就在这里。早期我写的 description 特别笼统,比如"用于数据分析",结果模型要么该用的时候不用,要么不该用的时候乱用。后来我总结出一个规律:description 要同时回答"做什么"和"什么时候用"。
一个我实测下来比较稳的模板是这样的:
当【触发场景,尽量具体】时使用本 skill。它能【核心能力】。不适用于【明确排除的场景】。
最后那句"不适用于"特别关键。因为很多 skill 的能力边界是模糊的,你不主动划清,模型就会过度使用。比如一个"生成周报"的 skill,如果不写明"不适用于正式对外报告",模型可能拿它去写客户提案,格式就全乱了。
2.4 脚本和模板:把确定性交给代码
SKILL.md负责"决策",但真正执行时,能用代码搞定的部分就别让模型硬算。这是我在实际项目里体会最深的一条。
举个真实例子:我做过一个处理 CSV 的 skill,一开始所有清洗逻辑都写在说明里让模型照着做,结果每次结果都不太一样,小数点精度、空值处理经常飘。后来我把清洗逻辑抽成一个 Python 脚本,SKILL.md里只写"调用scripts/clean.py,传入文件路径",稳定性立刻上来了。
道理很简单:模型擅长判断和生成,不擅长精确计算和重复执行。把这两类工作分开,skill 才可靠。所以一个成熟的 skill 往往是"自然语言决策层 + 代码执行层"的混合体。
3. 手写第一个 skill:从需求拆解到跑通
3.1 先想清楚"这个 skill 解决哪一类重复劳动"
动手之前,先问自己一个问题:我要封装的这件事,是不是会反复出现?如果只是一次性任务,写 skill 反而浪费时间。
判断标准我一般用三条:
- 重复性:同类任务一周内会出现多次。
- 流程稳定性:每次的做法基本一致,不需要大量临场判断。
- 有明确产出:输出格式或结果可以标准化。
三条都满足,才值得做成 skill。比如"把会议录音转成结构化纪要""按固定模板生成分镜脚本""对代码仓库做一轮安全自查"——这些都符合。而"帮我想个创业点子"这种就完全不适合,因为它没有稳定流程。
3.2 目录搭建与命名约定
确定要做之后,先建目录。命名上我建议用小写字母加连字符,语义要具体:
meeting-notes-generator/ financial-report-parser/ code-security-audit/别用tool1、helper这种名字,模型看了也不知道是干嘛的。目录名本身也会进入模型的判断视野,所以它也是"描述"的一部分。
3.3 SKILL.md 的字段逐个拆解
一个完整的SKILL.md通常包含这几块。我按重要性排序讲:
name:skill 的唯一标识,和目录名保持一致最省心。
description:前面说过,这是命中率的关键。要写场景、能力、边界。
使用步骤(Instructions):这是正文主体,告诉模型"具体怎么做"。我习惯写成有序步骤,每步说清楚输入是什么、输出是什么、遇到异常怎么办。
示例(Examples):给一两个输入输出的例子,模型照着模仿的准确率会明显提升。这一块很多人会省略,但我实测下来加上之后效果差别很大。
限制与注意事项:明确写出"不要做什么",防止模型自由发挥。
下面是一个我实际用过的简化版示例:
--- name: meeting-notes-generator description: 当用户提供会议录音转写文本,需要整理成 结构化会议纪要时使用。输出包含议题、结论、待办三部分。 不适用于正式对外发布的会议公告。 --- ## 使用步骤 1. 通读转写文本,识别出讨论的议题边界。 2. 每个议题下提炼结论,没有结论的标注"待定"。 3. 抽取所有带责任人和时间点的待办事项。 4. 按 templates/notes.md 的格式输出。 ## 注意事项 - 不要臆测未明确说出的结论。 - 待办事项必须包含原文中的责任人或标注"未指定"。3.4 用真实输入做第一轮验证
写完别急着说"完成了"。拿三到五个真实的历史任务喂进去,看输出稳不稳。我一般重点看三件事:
- 命中是否准确:该触发的时候触发了吗?不该触发的时候会不会误触发?
- 流程是否被遵守:模型有没有跳步骤、自己加戏?
- 输出格式是否一致:多次运行结果结构是否统一?
第一轮几乎一定会发现问题。常见的是模型"太聪明",觉得你的步骤啰嗦就自己简化了。这时候要么把步骤写得更强制(用"必须""禁止"这类词),要么把关键逻辑挪进脚本。
3.5 迭代:把飘的地方收进代码
验证阶段发现的不稳定点,就是下一轮要收进脚本的部分。我的经验是,只要某个环节连续两次输出不一致,就说明它不该交给模型自由发挥。
比如日期格式、金额计算、字段排序这类,全部下沉到脚本。SKILL.md里只保留"调用哪个脚本、传什么参数、拿到结果后怎么组织"这些决策性内容。这样迭代几轮,skill 就会越来越稳。
4. 在 Agent 里加载与调试 skills 的实操细节
4.1 加载机制:模型是怎么"看见"skill 的
理解加载机制,调试时才能有的放矢。前面提过渐进式披露,这里展开说。
Agent 启动时,框架会扫描 skill 目录,把每个 skill 的name和description拼成一段清单放进系统提示。模型看到的是类似这样的东西:
可用技能: - meeting-notes-generator: 当用户提供会议录音转写文本... - financial-report-parser: 当需要从财报 PDF 中提取...当模型判断需要某个 skill,它会主动"请求加载",框架再把完整的SKILL.md塞进上下文。所以调试时如果发现 skill 没被触发,第一件事就是检查 description 有没有被正确扫描进去,而不是去改正文。
4.2 触发失败的三种典型原因
我遇到过的情况基本归为三类,对应不同的修法:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 完全不触发 | description 太笼统或没被扫描 | 检查清单里有没有它,重写描述 |
| 偶尔触发 | 描述和任务语义距离远 | 补充同义场景词 |
| 频繁误触发 | 边界没写清 | 加"不适用于"排除项 |
第二类最隐蔽。比如你的 skill 描述里写的是"处理文档",但用户实际说的是"帮我看看这份材料",语义上有距离,模型就可能不触发。解决办法是在描述里把常见的口语说法也覆盖进去。
4.3 调试时怎么"看见"模型的决策
不同平台提供的调试手段不一样,但思路相通:想办法看到模型在每一步的中间判断。
有的框架会把"模型决定加载哪个 skill"这一步显式打印出来,有的需要你打开详细日志。如果平台支持,我强烈建议在开发阶段把日志级别调高,观察模型是在哪一步、基于什么理由选择了(或没选择)某个 skill。这比盲目改描述高效得多。
如果平台没有现成的调试输出,一个土办法是临时在SKILL.md开头加一句"如果你正在读这段话,说明本 skill 已被加载",然后看输出里有没有这句。虽然笨,但能快速确认加载链路通不通。
4.4 多 skill 共存时的冲突处理
当你装了十几个 skill,冲突就来了。典型表现是模型在两个相似 skill 之间反复横跳,或者干脆选错。
我的处理原则是主动划清边界,而不是指望模型自己分辨。具体做法:
- 把功能相近的 skill 在描述里互相点名,比如 A 的说明里写"涉及 X 场景请优先用 B"。
- 合并那些边界实在划不清的 skill,宁可少而清晰,不要多而模糊。
- 给每个 skill 一个"最典型的一句话场景",让模型有明确的锚点。
实测下来,skill 数量控制在十个以内、每个边界清晰,整体表现最稳。贪多反而会让命中率下降。
5. 那些文档里不会写的踩坑经验
5.1 描述写得越"全"反而越不准
新手容易犯的错是把 description 写成功能大全,恨不得把所有能做的事都列上。结果模型面对一个具体任务时,反而抓不住重点。
我的经验是:description 要聚焦最高频的一两个场景,其余能力放到正文里。因为 description 的作用是"吸引命中",不是"完整说明"。就像招聘广告,标题写清楚岗位就行,不用把岗位职责全塞进标题。
5.2 别让模型在 skill 里做它不擅长的事
前面反复强调过,这里再单独拎出来。模型不擅长的是:精确计算、长流程的稳定执行、严格的格式校验。这些统统应该进脚本。
我见过有人把整个数据处理流程都写成自然语言步骤,结果每次跑出来数字都对不上。后来拆成"模型判断 + 脚本执行",问题立刻消失。判断力交给模型,执行力交给代码,这条线要划清楚。
5.3 版本管理:skill 也是会"退化"的
skill 不是写完就一劳永逸。你改了描述、换了脚本、升级了模型,行为都可能变。我现在的习惯是给每个 skill 建一个简单的变更记录,写清楚每次改了什么、为什么改、改完验证结果如何。
尤其是模型升级之后,一定要回归测试一遍。我遇到过模型换代后,原本很稳的 skill 突然开始跳步骤,排查半天发现是新模型对指令的理解方式变了。这种问题不回归测试根本发现不了。
5.4 安全边界:skill 能碰什么、不能碰什么
这一点在涉及自动化操作时特别重要。如果一个 skill 会执行脚本、读写文件、调用外部接口,那它的权限边界必须写死。
我的做法是:在SKILL.md里明确列出允许的操作范围,并且在脚本层面做二次校验。比如一个处理文件的 skill,脚本里要限制只能读写指定目录,不能任意路径乱窜。别指望模型每次都乖乖听话,物理层面的限制才是最后一道防线。
6. 从"能用"到"好用":几个进阶思路
6.1 把 skill 组合成工作流
单个 skill 解决单点问题,但真实任务往往是链式的。比如"分析一份财报"可能涉及:解析 PDF → 提取关键指标 → 生成图表 → 撰写解读。这四个步骤可以各自是一个 skill,也可以组合成一个上层 skill 来编排。
我倾向于先做原子 skill,再按需组合。因为原子 skill 复用性高,组合方式可以灵活调整。如果一上来就做大而全的 skill,后面想拆都难。
6.2 用示例驱动输出稳定性
前面提过示例的重要性,这里补充一个技巧:示例要覆盖"正常情况"和"边界情况"各一个。只给正常示例,模型遇到边界就容易乱来;补上边界示例,它就知道该怎么处理异常输入了。
比如一个生成报告的 skill,正常示例展示标准格式,边界示例展示"数据缺失时怎么标注"。两个一给,输出稳定性明显提升。
6.3 定期清理不再使用的 skill
skill 装多了会拖累命中率,所以定期清理很有必要。我的做法是每隔一段时间回顾一遍,把最近一个月没触发过的 skill 归档。别舍不得,留着只会增加模型的判断负担。
判断一个 skill 是否该留,看两点:最近有没有被用到,以及它的能力有没有被别的 skill 覆盖。两个都不满足,就该清理了。
6.4 关于跨平台差异的一点提醒
最后说个现实问题:不同平台对 skill 的支持程度不一样。有的平台对目录结构、字段名有严格要求,有的相对宽松;有的支持脚本执行,有的只支持纯文本说明。
如果你打算把 skill 在多个平台间迁移,尽量把核心逻辑写在通用的 Markdown 说明里,把平台相关的部分隔离到单独文件。这样迁移时只需要改隔离层,主体不用动。我在实际迁移中就是这么做的,省了不少返工。
说到底,skills 这套东西的价值不在于技术多高深,而在于它把"经验沉淀"这件事变得可操作了。你踩过的坑、总结的流程、验证过的做法,都能封装成一个模块,下次直接调用。用得越久,积累越厚,效率提升就越明显。我自己的体会是,刚开始写第一个 skill 时觉得麻烦,但写到第五个、第十个之后,回头一看,那些重复劳动真的被消灭了一大半。