
最近翻一些Agent开源项目发现skills这个词几乎成了一个标配目录。以前大家讨论AI智能体谈的是模型选型、上下文窗口、提示词工程现在画风变了讨论最多的是技能包怎么拆SKILL.md怎么写才能让模型稳定触发。说白了skills就是一组预先封装好的任务说明书和工具脚本你做会议纪要、整理文件、抓取网页、转换格式都可以拆成一个个独立的技能包塞给智能体让它按说明执行。这篇文章我打算用一套完整的实操复盘把技能包从设计、编写、调试到维护讲透。我会以给团队搭建文档助手技能库这个模拟项目X为载体逐步拆解一个技能包里面到底该放什么、每个文件承担什么职责、描述文字怎么写、脚本怎么组织和测试。不管你是刚开始把AI接进工作流还是在做正经的Agent产品这套方法基本都能直接抄。1. 内容整体设计与思路拆解1.1 为什么skills会成为Agent的关键拼图一个常见的认知误区是只要模型够聪明给一段指令它就能干活。实际用下来你会发现模型确实聪明但它的短期记忆上下文是有限的而且每次对话都在重新理解规则。你今天告诉它周报格式是四段本周进展、风险、下周计划、求助项下次它可能就忘了一个字段。更麻烦的是当任务带有固定流程比如先检查文件格式再调用脚本最后汇总输出的时候靠对话一步步引导既慢又容易中途跑偏。技能的思路刚好解决这个问题。它把做什么、按什么步骤做、需要哪些工具、输出成什么格式全部固化在一个目录里模型一但识别到任务类型就会自动读取对应技能包像新员工领到一本SOP手册一样照着流程干活。这就把原来靠人反复纠偏的隐性知识变成了可以复制、可以版本化、可以分发的显性资产。另外从工程角度看技能包把工具调用和决策逻辑解耦了。脚本只负责执行具体动作模型只负责读懂说明并编排步骤。这样调试时也能快速定位是说明写得不清楚还是脚本本身报错不再是一团乱麻。对团队协作来说一个技能库就像一个有据可查的知识库新人接入项目时直接翻技能目录就能理解整个系统能干什么。1.2 技能包的整体目录逻辑一个标准技能包的核心目录结构我建议长这样skills/ └── meeting-notes/ ├── SKILL.md ├── scripts/ │ └── format_notes.py ├── assets/ │ └── template.md └── examples/ └── sample_input.txtSKILL.md是整个技能包的门面也是模型读取的入口文件里面有技能的元信息和处理流程说明scripts目录放可执行脚本处理那些模型不适合自己做的、需要确定性和准确性的动作比如批量重命名、解析JSON、调接口assets目录放模板和静态资源比如周报模板、图片样式examples目录放输入输出样例帮助模型理解边界情况。这个结构不是拍脑袋设计的。把说明文件和脚本分开是因为模型擅长理解规则但不擅长精确计算脚本擅长精确执行但不能理解模糊意图。两者结合才能覆盖大多数任务。如果你的技能包只有一个Markdown文件说明技能对应的是纯知识型任务这没问题一旦涉及文件操作、网络请求、数据处理尽量把脚本单独提出来维护成本会低很多。1.3 选型手写 vs 聚合社区包现在很多人一上来就去社区找现成的技能包。社区包的好处是省时间一些热门的文件转换、网页抓取类技能已经打磨得不错但坑也不少有的技能描述写得稀碎触发率很低有的依赖特定环境拿过来根本跑不了。我的建议是最小闭环从手写开始——挑一个最痛、最重复的任务花一个晚上把它做成技能包跑通之后再决定要不要引入社区包。手写的好处不言而喻你最懂自己的业务场景和文件格式写出来的描述和步骤一定比通用包贴得更准。而且通过手写一个完整的技能包你会真正理解SKILL.md里每个字段的作用后面再调试社区包时一眼就能看出它的问题出在描述上还是脚本上。对于模拟项目X这种需要长期维护的技能库我更倾向于自己维护核心技能社区包只做补充并且每个引入的包都要在本地跑一遍样例才允许进库。2. 核心细节解析与实操要点2.1 SKILL.md 的描述书写决定触发率如果只让我选一个影响技能体验最大的因素我会选description。它直接决定模型什么时候想起你的技能。一段很常见的烂描述是meeting notes skill for generating minutes。这个描述有两个问题一是没有动词模型的触发逻辑很难判断动作二是没有说明输入和输出模型不确定该在什么场景调用。我推荐的写法是动宾结构开头 适用场景 输入条件 输出形式。例如--- name: meeting-notes description: 将会议转写稿或对话记录整理成结构化会议纪要输出包括参会人、议题、讨论要点、决议和行动项。当用户提供会议记录或要求整理会议纪要生成会议总结时使用。 ---这样写模型一看到整理会议纪要就能匹配。description里还可以带不要用于什么场景的负向说明比如不要用于处理单个句子、不需要生成纪要约1小时以上的讨论负向说明在技能多的时候特别有用能减少误触发。另外name的命名也有讲究。用短横线分隔的小写字母比如meeting-notes、batch-image-compress而不是会议纪要或MeetingNotes。因为很多Agent框架在文件系统层面用这个名字做路径定位带空格和中文容易出现兼容问题。2.2 技能内部工作流的三段式设计SKILL.md的正文我习惯按使用时机→处理步骤→输出格式三段来写。使用时机告诉模型这个技能在什么条件下被唤起处理步骤给出可操作的顺序最好带序号输出格式明确模型最后要交出的成品长什么样。以会议纪要技能为例正文可以写成## 使用时机 当用户要求将会议语音转写或文字记录整理为会议纪要时使用。 ## 处理步骤 1. 提取会议的参与者、时间、发起人无法确认时标注为待补充。 2. 通读原文按主要议题分段保留时间线顺序。 3. 对每个议题提炼核心讨论、关键结论、遗留问题。 4. 将行动项汇总为表格列出负责人、截止时间、状态。 5. 如果有待办没有负责人标记为待确认不要自己猜。 ## 输出格式 返回Markdown文档章节依次为会议信息、议题讨论、决议、行动项。行动项必须使用表格。这里的关键是步骤要可执行且要写明边界条件。比如无法确认时标注为待补充不要自己猜这些约束看起来小但对输出质量影响巨大。模型最大的问题是会在信息不足时脑补你必须在说明里明确告诉它哪些可以推断、哪些必须存疑。2.3 命名、粒度与依赖管理的规范技能粒度是很多人会忽略的点。我见过有人把处理各种文档做成一个技能描述写得天花乱坠结果模型根本不知道该在哪种场景触发或者触发了也不知道先执行哪一步。正确的做法是遵循单一职责原则一个技能解决一类任务。比如生成周报和整理会议纪要必须拆开哪怕它们都涉及总结类操作图片压缩和图片加水印也建议拆开这样每个技能的步骤和参数都简单直接模型更容易理解。依赖管理方面凡是脚本要用的外部命令或库必须在SKILL.md里写清楚。例如需要Python3.9依赖Pillow库安装方式pip install Pillow。这一步很多人偷懒觉得自己环境里有就行。但技能包是要分发的换一台机器、换一个团队依赖缺失会让技能直接报废。我习惯在scripts目录旁边放一个requirements.txt并在SKILL.md的安装说明里引用它。2.4 让模型愿意用你的技能技能写好了但模型就是不用这是最让人抓狂的。除了description写得不好还有一个原因是当前对话的任务特征不明显。比如你的技能描述里写的是当用户要求压缩图片时使用但用户说的是帮我处理一下这些图片发邮件太小了传不出去模型可能没法把处理图片和压缩图片关联起来。解决办法是在description里增加更多口语化触发词压缩图片、减小图片大小、处理图片体积、图片发不出去。还有一种情况是多个技能的description互相重叠模型无所适从。比如你有生成周报和生成月报两个技能描述都写着当用户要求生成报告时使用模型就会犹豫。解决办法是在描述里明确区分输入周期周报技能适用于汇报周期为一周的内容月报技能适用于汇总一个月的内容。必要时还可以在正文的开头加一行本技能不处理月报类内容请改用monthly-report技能给模型一个明确的迁移路径。3. 实操过程与核心环节实现3.1 示例一批量压缩图片技能先上一个最简单的技能帮助你建立手感。需求是用户给出一个图片目录脚本需要把里面所有jpg/png压缩到指定体积以下并输出到新目录。技能结构如下skills/ └── batch-image-compress/ ├── SKILL.md └── scripts/ ├── compress.py └── requirements.txtSKILL.md可以这样写--- name: batch-image-compress description: 批量压缩目录中的图片以减小体积。当用户要求压缩图片、减小图片体积、处理图片大小、让图片适合邮件或上传时使用。输入图片目录输出压缩后的图片到指定目录。 --- ## 使用时机 用户提供图片所在目录要求压缩或减小体积。 ## 处理步骤 1. 确认输入目录存在读取目录下所有jpg/jpeg/png/webp文件。 2. 调用scripts/compress.py传入--src和--dst参数。 3. 检查输出目录确认图片数量和压缩前后大小。 4. 向用户报告原始总大小、压缩后总大小及压缩率。 ## 输出格式 先返回压缩结果摘要再列出压缩比超过50%的文件明细。compress.py的关键实现如下#!/usr/bin/env python3 from PIL import Image from pathlib import Path import argparse def compress_dir(src, dst, quality80): src, dst Path(src), Path(dst) dst.mkdir(parentsTrue, exist_okTrue) supported {.jpg, .jpeg, .png, .webp} for img_path in sorted(src.iterdir()): if img_path.suffix.lower() not in supported: continue im Image.open(img_path) # 长边超过2048像素的先等比缩放 if max(im.size) 2048: im.thumbnail((2048, 2048)) im.save(dst / img_path.name, qualityquality, optimizeTrue) print(f{img_path.name} ok) if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--src, requiredTrue) ap.add_argument(--dst, requiredTrue) ap.add_argument(--quality, typeint, default80) args ap.parse_args() compress_dir(args.src, args.dst, args.quality)脚本用Pillow实现压缩质量默认80长边超过2048的先做等比缩放。为什么加等比缩放因为很多图片的问题不只是体积大分辨率也过大邮件附件场景根本不需要4000像素的宽度先缩后压能显著减少输出体积。同时我限定输入目录必须是绝对路径避免脚本的工作目录和用户预期不一致。测试时要看三件事脚本能跑通、输出目录结构正确、压缩率合理。建议先在一个只有两三张图的临时目录里测试再丢给模型调用。模型调用脚本时通常自己会拼参数如果测试阶段发现它拼出的--src路径不对你得回到SKILL.md里补充请使用绝对路径这一句说明。3.2 示例二会议纪要整理技能第二个技能更偏模型自身能力脚本只是辅助。需求是用户丢进来一段冗长的会议转写稿要求生成结构清晰的纪要。这个技能在模拟项目X里使用频率最高值得做精细一点。结构如下skills/ └── meeting-notes/ ├── SKILL.md ├── scripts/ │ └── format_action_items.py ├── assets/ │ └── minutes_template.md └── examples/ ├── sample_transcript.txt └── output_minutes.mdSKILL.md里最关键的是正文字段我上面已经给出了示例。这里补充一个容易忽略的点一定要给模型一个输出格式参考。assets/minutes_template.md 可以用模板的形式展示最终纪要长什么样# 会议纪要{主题} - 时间{YYYY-MM-DD} - 参与者{名单} - 记录人{待补充} ## 议题讨论 ### 议题1{标题} - 讨论要点{要点} - 结论{结论} ## 决议 - {决议项1} - {决议项2} ## 行动项 | 行动项 | 负责人 | 截止时间 | 状态 | |---|---|---|---| | {事项} | {人名} | {日期} | {状态} |examples/sample_transcript.txt可以放一段两三百字的模拟转写稿output_minutes.md放对应的整理结果。为什么要放示例因为模型在读取技能时看到步骤说明模板示例三件套理解起来远比只有抽象描述要准确。你甚至可以观察它是不是参照示例的格式输出了——如果格式不对问题通常就出在这里。scripts/format_action_items.py负责一个很小的动作把模型输出的行动项表格做一次校验检查负责人、截止时间有没有待确认或空白。这个脚本不改变模型的核心工作只做质量兜底防止漏项。#!/usr/bin/env python3 import sys import re def validate(text): # 简单检查行动项表格中的空字段 rows re.findall(r\|([^|])\|([^|])\|([^|])\|([^|])\|, text) for row in rows[1:]: cells [c.strip() for c in row] if len(cells) 4: print(f警告行动项表格格式不正确 - {row}) continue if 待确认 in cells[1] or not cells[1]: print(f警告行动项缺少负责人 - {cells[0]}) if 待确认 in cells[2] or not cells[2]: print(f警告行动项缺少截止时间 - {cells[0]}) if __name__ __main__: content sys.stdin.read() validate(content)技能步骤里可以写整理完行动项后将Markdown内容通过stdin传给scripts/format_action_items.py进行校验如果输出警告补充提示用户确认。这样模型就知道在最后多一步质检动作比光靠它自觉可靠得多。3.3 技能接入与调用的验证方法技能包写完当然要跑通一遍完整链路。我习惯用一个标准验收清单来检验技能的可用性首先打开技能目录确认SKILL.md能被正确解析frontmatter没有语法错误description能被当前Agent框架读到。这一步可以借助框架自带的技能列表命令查看技能是否被加载。其次准备一个最小场景用自然语言触发技能比如直接输入请压缩桌面test文件夹里的图片观察模型是否识别出技能、是否读取了SKILL.md、是否按步骤执行。最后对比输出与预期格式的差异记录下模型因为哪些描述不够清楚导致的行为偏差。我在验收时最看重的是模型是否主动查看脚本代码。如果它在调用脚本前会先去读scripts目录里的源码说明技能说明还不够明确正常情况下模型应该只靠SKILL.md就能正确调用脚本。一旦发现它需要读源码才敢动手就要回头补SKILL.md里的参数和用法说明直到模型不需要打开源码为止。4. 常见问题与排查技巧实录4.1 模型不触发技能这是被问得最多的一个问题我明明写了技能为什么模型完全不理会第一步排查description是不是没有包含用户可能的说法如果你只在描述里写了压缩图片用户说的是图片太大了发不出去那模型识别不出来就很正常。第二步排查技能数量技能库塞了几十个技能之后模型检索的粒度变粗触发率会明显下降此时需要精简描述、增加负向说明或者对技能做分组。第三步排查环境确认技能目录在正确的路径下且框架有权限读取。一个特别隐蔽的坑是frontmatter里的name与目录名不一致。很多框架在加载时以目录名为准但模型在引用技能时读的是name字段两边对不上就会发生技能列表里有但触发后找不到的诡异现象。我吃过一次亏后要求自己所有技能都必须满足name目录名。4.2 参数传递错位脚本参数错位是第二大类问题。最常见的是路径相关用户给了相对路径脚本的工作目录却在别处结果找不到文件。解决方法是SKILL.md里显式写所有路径必须使用绝对路径同时脚本里对路径做严格校验不存在就报错。另一个参数坑是文件格式。你的脚本支持jpg和png但用户给的是webp模型没看脚本就直接调用就会失败。这个时候要在SKILL.md里写明支持格式并把不支持格式的情况也写进去如果输入目录中包含webp以外的格式请提示用户并跳过不要报错退出。给模型一个处理边界的指引比让它自由发挥安全得多。4.3 脚本执行失败脚本本身报错常见原因有三个权限位不对脚本没有执行权限、依赖缺失、Python环境不对。Linux环境下记得chmod xWindows环境则要注意路径分隔符和编码问题。依赖缺失最有效的预防手段是在skills目录放requirements.txt并在SKILL.md里写清楚安装命令。Python环境问题我想多说一句不要用系统自带Python直接跑也不要指望Agent框架自动帮你管理依赖。我一般用一个特定Python版本跑所有技能脚本并在每个脚本开头加一行注释说明需要的版本。遇到脚本独测没问题模型一调就崩的情况90%是环境变量不同导致的比如模型子进程没有继承当前shell的PATH你需要在SKILL.md里指定解释器的绝对路径。4.4 输出结果不稳定模型在调用技能后输出格式经常不稳定。比如要求输出JSON它偶尔输出带json代码块包裹的内容或者把值放到错误字段。这类问题在SKILL.md里写得再详细也未必能完全避免我的办法是在技能输出端加一层格式校验器脚本先接收模型的输出做一次解析和标准化再返回给用户。校验器不改变语义只保证结构正确。比如JSON输出就强制json.loads后再dump一遍表格输出就用正则把关键列抽出来重排。这种思路本质上是永远假设模型输出不可信然后在流程上做钳制。别嫌麻烦生产中一定要这么做否则一个小字段错误可能让后续环节全部失败。5. 维护与复用的经验沉淀5.1 技能库的版本管理技能库一旦开始积累就会面临改坏了、想回滚改进了、想对比这些正常软件开发问题。我的做法是把整个skills目录放进版本控制仓库每次改动一个技能包都提交并附上说明。别小看这一步技能包是一种代码资产不纳入版本管理迟早会乱。我还会在每次修改SKILL.md后手动记录一下这次描述在哪些测试用例上表现好、哪些表现差。版本管理不只是管理代码更是管理经验。一年后再看这些记录比代码本身更有价值。5.2 技能间的复用与组合技能之间不是孤立的它们可以互相调用。以模拟项目X为例会议纪要技能输出行动项后周报技能可以直接读取行动项表格把本周完成事项自动填进去。要让这种组合成立两个技能之间必须有稳定的数据契约前端技能的输出格式就是后端技能的输入约定。因此维护技能库的核心工作不再是写单个技能而是维护这些契约。我建议每个技能的输出段都明确写出本技能输出可作为XX技能的输入。这样模型在编排多步任务时能有意识地串联技能而不只是孤立地调用某一个。组合好的技能库效率是单个技能堆叠所不能比的这也是skills这个设计真正值钱的地方。我自己的实操体会是不要急着把什么都做成技能。一开始我在模拟项目X里疯狂地给每个流程做技能包结果技能之间互相打架维护成本暴涨。后来砍掉一半技能只保留高频且步骤稳定的任务体验反而好了很多。技能包的本质是把稳定流程固化下来而不是把模型变成只会按剧本走的机器人。真正的分界线是判断这个任务够不够重复、标准、可验证。满足这三个条件就值得做成技能否则让它留在提示词里灵活应对反而更好。最后分享一个小技巧每个技能包里的examples目录一定不要偷懒省略它给模型省下的理解成本远比你写一百行说明更有用。