
最近一段时间“skills”这个词在我关注的几个技术社区里出现的频率越来越高。一开始我以为又是某个新框架的噱头直到自己在实际项目里被 Agent 的“失控”折腾到头疼才认真研究了一下这个叫 Skills 的能力封装方案。简单说它不是什么新语言也不是某个大厂的新框架而是一种正在快速普及的 AI Agent 技能包配置规范——把模型执行某项任务时需要的一切指令、脚本、参考文档、依赖清单打包成一个标准目录让模型在需要时自动“翻阅说明书”并按流程执行。这篇文章我想从一个实际动手做过的人的角度把 Skills 的核心设计、手写一个技能包的完整过程以及我在调试中踩过的坑一次说清楚。适合正在做 Agent 应用、或者被提示词工程折磨够呛的开发者参考。1. Skills 是什么为什么团队都在用它1.1 从“什么都会”到“什么都能”的 Agent 困境先聊聊我遇到的真实问题。之前我在做一个内部文档助手底层接的是大语言模型为了让模型能正确调用内部 API我在系统提示词里塞了十几条工具说明、输出格式示例、参数约束结果提示词越来越长模型反而越来越“笨”。指令稍微复杂一点它就顾此失彼要么忘了先去查权限要么把 JSON 输出成 Markdown。后来我意识到问题的根源不在于模型能力不够而在于我把所有“知识”都堆在了一个线性 Prompt 里没有给模型提供一个结构化的、按需加载的能力包。Skills 解决的正是这个问题。它本质上是一个标准化的文件夹结构里面放着一个技能所需的全部材料说明文档、脚本、模板、参考示例、依赖清单。模型在对话过程中会根据用户请求自动判断需要哪个技能然后去读取对应目录下的 SKILL.md 文件按里面的指令逐步执行。这就像你不再给新员工一本五百页的员工手册而是给每个岗位配一本只有二十页的《岗位操作手册》他需要时再翻开对应的那一页。1.2 Skills 解决的核心问题Skills 最直接的价值是把“能力”从“提示词”里解耦出来。以前你想给 Agent 增加一个新功能比如“把日志文件里的错误信息汇总成周报”你得修改系统提示词这会影响到所有会话。现在你只需要新建一个技能目录把规则和脚本放进去Agent 在遇到日志分析任务时就会自动调用它。改动范围从“全局”缩小到了“局部”这是一个质的变化。另一个核心价值是可复用性。一个写好的技能包可以复制到任何支持 Skills 规范的 Agent 环境中团队内部可以沉淀公共技能库比如“SQL 查询规范”“代码审查清单”“K8s 故障排查手册”每个人都能用不用重复造轮子。我在实际项目中把代码审查做成了一个技能包团队成员接入后新人的代码合入前都会自动跑一遍规范检查效果比我以前在群里反复强调要好得多。1.3 适合谁来用如果你是下面这几类人我觉得 Skills 值得你花一个下午试试第一做 AI 应用开发的工程师正在为 Agent 工具调用不稳定而头疼第二重度使用 ChatGPT、Claude 等对话式 AI 的知识工作者希望让 AI 稳定地按自己的流程输出第三技术团队的管理者想把团队的最佳实践沉淀成可复用的资产。注意Skills 的学习门槛不高它不要求你会写复杂的框架代码核心是目录结构加 Markdown 文档剩下的逻辑可以用 Python 或 Shell 脚本补齐。2. Skills 的核心设计一个技能包该长什么样2.1 目录结构比你想的更严格我第一次看 Skills 规范的时候觉得它就是一个普通的文件夹加文档没什么稀奇。直到自己动手写了一个才发现它的目录约定非常严格而且这种严格是有道理的。一个标准技能包的目录结构大概是这样的skills/ └── json-cleaner/ # 技能目录名称必须是短横线命名法 ├── SKILL.md # 技能说明文件模型首先读取它 ├── scripts/ # 存放可执行脚本 │ └── clean_json.py ├── references/ # 存放参考文档、模板、示例 │ ├── example.json │ └── field_rules.md ├── assets/ # 存放静态资源如配置模板、样本数据 └── requirements.txt # 依赖清单可选注意几个细节。第一SKILL.md 这个文件名是固定的模型扫描技能目录时就是按这个名字来找入口文件的你不能改叫README.md或者skill.md大小写也不能错。第二目录名建议全小写加短横线比如code-reviewer、log-summarizer避免使用空格和驼峰命名因为模型在生成路径和引用文件时会更容易出错。第三子目录不是必须全建的用不到就不要建空目录减少模型扫描时的干扰。2.2 SKILL.md技能包的说明书SKILL.md 是整个技能包的大脑它告诉模型“技能是做什么的、在什么情况下使用、具体怎么执行”。我第一次写的时候把它当成了普通的 README结果模型经常在无关场景下误调用这个技能。后来我调整了写法总结出几个关键块name字段技能的唯一标识符尽量语义化。比如json-cleaner比tool-001好理解得多。description字段这个字段极其重要它是模型判断“是否该用这个技能”的依据。不能写成“用于清理 JSON”而要写清楚触发条件“当用户提供 JSON 文件或 JSON 字符串且要求去除冗余字段、标准化键名或检查数据完整性时使用”。描述越具体误调用率越低。when_to_use和when_not_to_use这是我从实践中加上的效果立竿见影。明确告诉模型“不要用于 XML 数据”“不要用于 JSON Schema 生成”能避免一大批误触发的场景。执行步骤部分用数字列表给出清晰的流程每一步都要可执行。比如“1. 读取用户提供的 JSON2. 调用 scripts/clean_json.py 处理3. 输出清理后的 JSON 和变更说明”。2.3 命名约定和版本管理技能包的名字看起来很随意实际操作起来坑很多。我建议团队在用 Skills 之前先定一套命名规范。首先要保证全仓库内技能目录名唯一否则模型扫描时遇到同名目录就会随机选择一个行为变得不可预测。其次目录名一旦确定就不要轻易改动因为模型会根据技能名记忆调用路径改名会导致旧会话里已经生成的引用失效。版本管理方面我的习惯是在 SKILL.md 里加一个version字段同时把大的变更记录在目录下的CHANGELOG.md里。版本号不一定要遵循严格的 semver但至少要让排查问题的人知道当前跑的是哪一版。我在实践中发现很多“模型行为突然变了”的灵异事件查到最后都是因为同事更新了技能脚本但忘了更新版本号新旧逻辑混杂导致的。3. 实操5分钟手写一个可复用的 Skills 技能包3.1 先想清楚边界技能要解决什么问题动手之前最重要的一步不是写代码而是定义清楚“这个技能到底做什么、不做什么”。我以“JSON 清理器”为例因为这是个很多项目里都用得上的小工具。需求是这样团队成员经常从第三方接口拿到一堆冗余字段的 JSON 数据里面有大量null字段、下划线命名的键、还有嵌套过深的结构需要花时间手工清理。我想让 Agent 自动完成这个过程。定义边界时我明确了几条规则只处理合法 JSON 数据不处理 JSON Schema 定义不做 XML/CSV 转换清理规则以配置文件为准不靠模型自由发挥。把这些边界写进 SKILL.md 的when_not_to_use之后误调用率确实明显下降。这一步看似简单其实决定了整个技能包的质量。3.2 搭建目录和元信息确定边界后我开始搭建目录。我的工作目录是/Users/me/projects/skills-repo/里面已经按规范建好了根目录。现在要在这个根目录下新建一个json-cleaner技能文件夹cd /Users/me/projects/skills-repo mkdir -p json-cleaner/scripts mkdir -p json-cleaner/references然后创建SKILL.md。这个文件是核心我逐字段填写了技能名称、适用场景、触发条件和执行步骤。写 description 的时候我特别注意用“当……时”的句式把触发场景写具体。比如--- name: json-cleaner description: 当用户提供 JSON 文件或 JSON 字符串且要求去除冗余字段、标准化键名、删除空值或整理嵌套结构时使用。当输入不是合法 JSON 时不要使用。 version: 1.0.0 ---正文部分我用数字列表写清楚四步流程并且特别注明了“如果用户没有明确要求不要主动修改 JSON 数值的类型比如不要把字符串数字转成整数”。这个细节让处理结果更符合预期因为模型经常自作主张去“修正”它认为不合理的数据结果反而破坏了原始信息。3.3 写核心逻辑脚本与参考文件SKILL.md 里描述了要调用scripts/clean_json.py那这个脚本就得真正存在。我写了一个很简单的 Python 脚本功能包括删除值为null的字段、把下划线键名转为驼峰、限制最大嵌套深度。核心逻辑不复杂但有一个要注意的点脚本必须从标准输入读取数据并把结果输出到标准输出这样才能被 Agent 环境可靠地调用。#!/usr/bin/env python3 JSON cleaner: remove nulls, rename keys, limit depth. import json import sys def clean(obj, depth0, max_depth5): if depth max_depth: return None if isinstance(obj, dict): result {} for k, v in obj.items(): new_key convert_key(k) cleaned clean(v, depth 1, max_depth) if cleaned is not None: result[new_key] cleaned return result if isinstance(obj, list): return [clean(item, depth 1, max_depth) for item in obj] return obj def convert_key(key): parts key.split(_) return parts[0] .join(p.capitalize() for p in parts[1:]) if __name__ __main__: data json.load(sys.stdin) print(json.dumps(clean(data), ensure_asciiFalse, indent2))我把这个脚本放在技能的scripts目录下并且在 SKILL.md 的执行步骤里写明了调用方式先用 Python 的标准库 json 模块校验输入再运行脚本处理。脚本本身不依赖第三方库避免模型在受限环境里装不上依赖的尴尬。这一步我建议所有技能脚本都尽量用标准库把外部依赖减到最少。3.4 加参考文档让模型“照抄”光有脚本还不够模型在真实场景中经常需要处理一些边界情况比如“什么字段该保留”“嵌套深度超过限制怎么办”。为了让输出更加稳定我在references目录下放了一份field_rules.md里面用表格写清楚了保留和删除的规则。这份参考文档不是给人类看的而是给模型在调用技能时阅读的所以表述要极其明确。比如我会写“保留所有业务字段包括 id、name、status、created_at删除内部调试字段包括 trace_id、debug_info、internal_code当嵌套深度超过 5 层时第 6 层及以下的内容合并到父级并加上 _truncated 前缀。”这种规则模型能直接执行不需要自己去推断。我试过把规则写得模糊一点比如“删除不需要的字段”结果模型每次删的东西都不一样完全没法用。3.5 本地联调与效果验收技能包写完之后先别急着部署到线上我习惯先在本地做一轮简单联调。方法是用一个支持 Skills 的客户端加载技能目录然后直接发消息测试。第一次测试时我发现模型没有调用技能而是直接回答“我可以帮你清理 JSON”原因是 SKILL.md 里的 description 写得太宽泛了模型觉得自己懂 JSON 就不需要调用工具。我把它改成“当用户要求清理或标准化 JSON 时必须使用 json-cleaner 技能”之后模型才开始正常调用。验收的时候我准备三组测试样例一组是普通 JSON 清理一组是深度嵌套的极端情况一组是故意传入非法 JSON 验证模型的兜底行为。三组全部通过后我才把这个技能包提交到 Git 仓库。这一步不建议省因为模型对技能的调用行为会受 description、示例数据、环境热词等多种因素影响不实测根本发现不了问题。4. 常见问题与排查技巧实录4.1 模型不按 SKILL.md 走怎么办我遇到过最频繁的问题就是模型明明已经加载了 SKILL.md但执行步骤完全不是文档里写的那样。比如文档里要求先调用 Python 脚本再输出结果模型却直接凭“印象”生成了清理后的 JSON。排查这个问题的思路是先确认模型是否真的“读”了这份文档而不是靠训练时的记忆在回答。我的办法是在 SKILL.md 的执行步骤里加入一个“强制验证”环节明确要求模型在每一步后输出-- Step N complete --之类的标记。这样我可以从对话日志里判断模型到底走没走流程。如果发现模型根本没调用脚本我就把“必须执行脚本”这句话加粗并放在执行步骤第一条同时在 description 里强调“这是一个需要运行脚本的任务不能仅凭推理回答”。修改后调用成功率提升了不少。这个问题的本质是模型有“走捷径”的倾向你的文档写得越明确它就越难绕开。4.2 技能包调用失败或超时的排查技能包在线上环境偶尔会调用失败最常见的原因是路径错误。模型在生成脚本路径时可能会拼错目录名比如把json-cleaner/scripts/clean_json.py拼成scripts/clean_json.py因为在它的认知里当前目录就是技能目录。解决方法是 SKILL.md 里写路径时一定要用绝对路径或明确的相对路径前缀并且在文档开头就声明“所有脚本路径均相对于本技能目录”。另一个坑是执行超时。如果技能脚本处理的数据量很大比如几十 MB 的 JSONAgent 环境的执行超时时间可能不够。我的方案是在 SKILL.md 里增加前置步骤“先检查输入文件大小超过 10 MB 则先对数据进行采样只清理前 1000 条记录并告知用户已做采样处理。”这样既避免了超时又让用户知道结果不是全量处理的。4.3 命名冲突与多技能包协作当一个仓库里的技能包超过十个以后新的问题出现了不同技能包之间可能互相干扰。比如我有一个json-cleaner同事又建了一个json-formatter两个技能的 description 都覆盖了“处理 JSON”的场景模型就会随机选择输出风格完全不一致。后来我们定了一条规矩技能仓库要有 owner新增技能前必须检索现有技能包如果功能重叠先讨论是合并还是拆分不能直接新建。此外assets和references目录的内容尽量不要跨技能引用。我以前试过在一个技能里通过相对路径去读另一个技能的模板文件短期能用但后来目录一调整就全线罢工。现在所有技能都遵循“自包含”原则用到的任何资源都放进自己的目录下宁可多拷贝一份也不做跨目录依赖。4.4 版本升级时的“技能漂移”问题最后聊一个比较隐蔽的问题——“技能漂移”。技能包在持续更新过程中SKILL.md 的文字描述和脚本行为之间很容易出现偏差。比如脚本已经把“删除所有空数组”改成了“保留空数组”但文档里还写着旧规则模型按文档执行就会和脚本产生冲突。这种问题在团队多人协作时尤其常见。我现在每周会做一次“技能自检”把每个技能包的 SKILL.md、脚本和 references 里的示例重新对齐一遍删除过时描述。另外版本号不只是写给自己看的升级行为变更时必须递增主版本号并在 CHANGELOG 里记录变更内容。这个习惯帮我省掉了大量排查时间因为我发现很多诡异的 Agent 行为追根溯源都是技能文档和实际逻辑不同步导致的。我在实际使用中还有一个体会Skills 的价值不是“写出来”而是“用起来”。一个技能包如果半年没人调用说明它的 description 写得不够精准或者场景太窄了还不如把它删掉保持技能库精简。现在我的团队已经沉淀了十几个技能包代码审查、日志分析、SQL 规范检查都在用接口调用成功率从早期的 60% 左右提升到了 90% 以上。如果你也在做 Agent 方向我建议从手写第一个技能包开始把“到网上找示例”的冲动先放一放从自己的实际场景出发写一个简单的、边界清楚的小技能你会在调试的过程中对模型的行为边界有更深的理解。