最近翻一些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, quality=80): src, dst = Path(src), Path(dst) dst.mkdir(parents=True, exist_ok=True) 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, quality=quality, optimize=True) print(f"{img_path.name} ok") if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("--src", required=True) ap.add_argument("--dst", required=True) ap.add_argument("--quality", type=int, default=80) 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 +x;Windows环境则要注意路径分隔符和编码问题。依赖缺失最有效的预防手段是在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目录,一定不要偷懒省略,它给模型省下的理解成本,远比你写一百行说明更有用。
最近翻一些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, quality=80): src, dst = Path(src), Path(dst) dst.mkdir(parents=True, exist_ok=True) 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, quality=quality, optimize=True) print(f"{img_path.name} ok") if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("--src", required=True) ap.add_argument("--dst", required=True) ap.add_argument("--quality", type=int, default=80) 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 +x;Windows环境则要注意路径分隔符和编码问题。依赖缺失最有效的预防手段是在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目录,一定不要偷懒省略,它给模型省下的理解成本,远比你写一百行说明更有用。