1. 为什么我想给 AI Agent 装上一套“肌肉记忆”
第一次认真琢磨 Skill 这件事,是因为我实在受够了每次开新会话都要把同一套规矩重新讲一遍。你肯定也经历过:让 Agent 帮你写周报,它给你整出一堆“首先其次最后”的废话;让它改代码,它把注释删得干干净净;让它整理资料,格式每次都不一样。问题不在于模型笨,而在于它没有“肌肉记忆”——每次动作都要靠大脑临时想,想出来的结果自然飘忽不定。
所谓 AI Agent 的肌肉记忆,说白了就是把那些反复要用、又不想每次都交代的操作规范,固化成一个可复用的能力单元。这个单元就是 Skill。它不是什么高深的东西,本质上就是一份写给 Agent 看的说明书,告诉它“遇到这类任务,按这个流程、这个格式、这个边界来做”。你可以把它理解成给新员工准备的 SOP 手册,只不过读者从人换成了模型。
我之所以想从零手撸一遍再研究自动生成,是因为直接抄别人的 Skill 你永远不知道哪些字段是必须的、哪些是摆设。只有自己踩过一遍坑,才能判断一个 Skill 写得好不好、能不能复用、会不会互相打架。这篇内容适合两类人:一类是刚开始搭 Agent、被提示词管理搞得焦头烂额的开发者;另一类是已经有一堆零散提示词、想把它工程化沉淀下来的实践者。下面我会把设计思路、字段细节、手撸过程、自动生成方案和排查经验全部摊开讲。
2. Skill 到底是什么:把提示词从“一次性”变成“可复用资产”
2.1 从提示词堆砌到能力封装的心智转变
大部分人用 Agent 的起点,是在对话框里敲一大段指令。这种方式在单次任务里没问题,但一旦任务重复出现,你就会陷入复制粘贴的泥潭。更麻烦的是,提示词散落在各个会话里,改了一处忘了另一处,最后自己都不知道哪个版本是对的。Skill 要解决的就是这个问题:把提示词从“一次性消耗品”变成“可版本管理的资产”。
我习惯用一个类比:提示词像是你临时给朋友指路,说“往前走两个路口左转”;Skill 则像是你在路口立了一块路牌,谁路过都能看懂,而且内容固定、不会因为今天心情不好就指错方向。路牌立好之后,你不需要每次都亲自指路,Agent 自己就能按路牌走。这个转变的关键在于,Skill 是有结构的、可被程序读取的,而不是一段自由文本。
从工程角度看,Skill 带来的最大价值是确定性。模型本身是概率性的,同样的输入可能给出不同输出。但当你把操作步骤、输出格式、边界条件都写进 Skill,模型的发挥空间被压缩到一个可控范围内,结果就稳定多了。这也是为什么我坚持认为,Skill 不是可选项,而是 Agent 从玩具走向工具的必经之路。
2.2 SKILL.md 与 YAML:一个管“说什么”,一个管“怎么被找到”
一个标准的 Skill 通常由两部分组成:元数据和使用说明。元数据用 YAML 写在文件头部,负责描述这个 Skill 叫什么、什么时候该用它、需要什么参数;使用说明用 Markdown 写在后面,负责告诉 Agent 具体怎么做。这两者分工明确,缺一不可。
YAML 部分的核心字段一般包括name、description、triggers和inputs。name是唯一标识,不能重复;description是一句话说明,Agent 靠它判断这个 Skill 是否匹配当前任务;triggers是触发条件,可以理解为关键词或场景描述;inputs定义需要用户提供哪些信息。这里有个容易踩的坑:description写得太模糊,Agent 就不知道该不该调用;写得太具体,又容易漏掉变体场景。我的经验是,描述里要包含“动作 + 对象 + 产出”,比如“把网页内容整理成结构化 Markdown 笔记”,而不是笼统的“处理网页”。
Markdown 部分则是真正的操作手册。我一般会分成几个固定小节:适用场景、操作步骤、输出格式、注意事项。适用场景帮 Agent 二次确认是否该用这个 Skill;操作步骤是核心,要写成可执行的序列;输出格式最好给出模板或示例;注意事项用来兜底,防止 Agent 在边界情况下乱来。这套结构不是官方规定,而是我在反复调试后总结出来的,实测下来最不容易出歧义。
2.3 为什么选 Markdown + YAML 而不是 JSON 或纯文本
有人会问,为什么不用 JSON 存元数据、用纯文本写说明?我的理由有三点。第一,Markdown 对人类友好,你随时可以打开看、直接改,不需要专门的解析器;第二,YAML 比 JSON 更适合写配置,支持注释、换行、多行字符串,写触发条件时不用把一长串关键词挤在一行里;第三,这两种格式在各类工具链里支持度极高,几乎不用担心兼容性问题。
纯文本的问题在于没有结构,Agent 很难区分“这是元数据”和“这是正文”,容易把说明文字当成指令执行。JSON 的问题在于可读性差,写多行说明时要疯狂转义,维护成本高。Markdown + YAML 的组合刚好平衡了机器可读和人类可维护这两个需求。当然,如果你的 Agent 框架只认 JSON,那也没办法,但至少在设计 Skill 内容时,我建议先用 Markdown 起草,再转成目标格式。
3. 手撸第一个 Skill:从需求拆解到文件落地
3.1 先想清楚“这个 Skill 到底解决什么问题”
动手写之前,我强迫自己先用一句话回答:这个 Skill 解决什么问题?如果一句话说不清楚,说明需求还没想明白,写出来的 Skill 大概率是四不像。我第一个练手的 Skill 是“把网页内容整理成结构化 Markdown 笔记”,因为这是我每天都要做的事,痛点足够真实。
拆解下来,这个任务包含几个子动作:抓取网页正文、去掉广告和导航、提取标题和关键段落、按固定模板输出。每个子动作都可能出问题,比如正文抓取不准、模板套错。所以 Skill 里必须把这些步骤写清楚,还要给出异常情况的处理方式。这里的关键是不要贪多,一个 Skill 只解决一类问题。我见过有人把“写代码 + 写文档 + 发邮件”塞进一个 Skill,结果 Agent 每次调用都像在猜谜。
3.2 YAML 头部字段逐个填:name、description、triggers、inputs
下面是我实际用的 YAML 头部,逐字段说明为什么这么写。
name: web-to-markdown-note description: 把网页正文整理成结构化 Markdown 笔记,保留标题层级和关键信息 triggers: - 网页整理 - 网页转笔记 - 保存网页内容 inputs: - name: url description: 需要整理的网页地址 required: true - name: template description: 输出模板,可选 default 或 meeting required: false default: defaultname用英文小写加连字符,避免空格和特殊字符,方便程序引用。description我反复改过三版,最终定成“动作 + 对象 + 产出”的结构,因为 Agent 在匹配时主要看这句话。triggers我列了三个近义表达,覆盖用户可能的不同说法。inputs里把template设为可选并给默认值,是为了让 Skill 在大多数情况下不需要额外询问就能执行。这里有个细节:required为 true 的字段如果用户没提供,Agent 应该主动追问,而不是瞎猜。
3.3 Markdown 正文怎么写:步骤、格式、边界一个都不能少
YAML 只是门牌,Markdown 正文才是真正的操作间。我的写法是固定四个小节,每个小节都有明确目的。
适用场景部分,我会写“当用户提供网页链接并希望整理成笔记时使用”。这句话和 YAML 里的description形成呼应,帮 Agent 二次确认。操作步骤部分,我写成编号列表,每一步都是可执行动作,比如“第一步,获取网页正文内容;第二步,识别并移除导航、广告、评论区;第三步,提取主标题和各级小标题;第四步,按模板组织内容”。步骤之间不要有歧义,不要写“适当处理”这种模糊词。
输出格式部分,我直接给一个 Markdown 模板,用代码块包起来,Agent 照着填就行。边界情况部分,我列了几种异常:网页无法访问时返回错误提示;正文过短时提示用户确认;遇到付费墙时说明无法获取完整内容。这些边界如果不写,Agent 遇到时就会自由发挥,结果往往不是你想要的。
提示:Markdown 正文里不要写“你应该”“你必须”这类命令式语气,改成“执行以下步骤”“按此格式输出”更中性,模型执行起来更稳定。
3.4 第一次实测:Agent 到底会不会用这个 Skill
写完文件后,我做了三组测试。第一组,直接说“帮我整理这个网页”,看 Agent 能否自动匹配到 Skill;第二组,说“把这个链接存成笔记”,看触发词是否生效;第三组,故意不给 URL,看它会不会追问。实测下来,第一组和第二组都能正确调用,第三组也追问了,说明required字段起作用了。
但问题也来了:Agent 在提取小标题时,有时候会把网页里的广告标题也带进来。我回头检查 Skill,发现操作步骤里只写了“提取主标题和各级小标题”,没有说明如何区分正文标题和广告标题。于是我补了一句“只保留与正文语义连贯的标题,孤立出现的短句视为广告并移除”。改完之后再测,准确率明显提升。这个经历告诉我,Skill 不是一次写完就完事的,必须拿真实任务反复磨。
4. 让 Skill 自动生成:把重复劳动交给流程
4.1 自动生成的核心思路:模板 + 变量 + 校验
手撸几个 Skill 之后,你会发现大部分内容都是重复的:YAML 头部结构一样,Markdown 小节一样,区别只在具体步骤和触发词。这时候就可以考虑自动生成。我的思路很简单:准备一套模板,把可变部分抽成变量,再用一个校验环节确保生成的 Skill 符合规范。
模板我用的是 Markdown 文件,里面用占位符标记可变区域,比如{{name}}、{{description}}、{{steps}}。变量来源可以是一份结构化配置,也可以是从已有提示词里抽取的信息。校验环节负责检查必填字段是否齐全、name是否重复、triggers是否为空。这套流程跑通之后,新增一个 Skill 的时间从半小时压缩到几分钟。
4.2 用脚本把零散提示词批量转成 Skill 文件
我写了一个 Python 脚本做这件事,核心逻辑是读取一个 YAML 配置列表,逐条渲染模板并写出文件。下面是我用的简化版代码,你可以直接改成自己需要的。
import yaml from pathlib import Path TEMPLATE = """--- name: {name} description: {description} triggers: {triggers} inputs: {inputs} --- ## 适用场景 {scenario} ## 操作步骤 {steps} ## 输出格式 {output_format} ## 注意事项 {notes} """ def render_skill(config): triggers = "\n".join(f" - {t}" for t in config["triggers"]) inputs = "\n".join( f" - name: {i['name']}\n description: {i['description']}\n required: {i.get('required', False)}" for i in config["inputs"] ) return TEMPLATE.format( name=config["name"], description=config["description"], triggers=triggers, inputs=inputs, scenario=config["scenario"], steps="\n".join(f"{idx+1}. {s}" for idx, s in enumerate(config["steps"])), output_format=config["output_format"], notes="\n".join(f"- {n}" for n in config["notes"]), ) def main(): configs = yaml.safe_load(Path("skills.yaml").read_text(encoding="utf-8")) out_dir = Path("skills") out_dir.mkdir(exist_ok=True) for cfg in configs: content = render_skill(cfg) (out_dir / f"{cfg['name']}.md").write_text(content, encoding="utf-8") print(f"generated: {cfg['name']}") if __name__ == "__main__": main()这个脚本的关键在于把steps和notes都当成列表处理,渲染时自动加编号和短横线。这样配置里只需要写纯文本,不用操心格式。实测下来,一次生成二三十个 Skill 文件毫无压力,而且格式统一,不会出现手写时漏掉某个小节的情况。
4.3 生成之后必须做的三项校验
自动生成最大的风险是“垃圾进垃圾出”。配置写错了,生成的文件也是错的,而且批量生成会放大错误。所以我强制自己每次生成后跑三项校验。
第一项,字段完整性校验。检查每个 Skill 是否包含name、description、triggers、inputs四个必填项,缺一不可。第二项,命名冲突校验。把所有name收集起来,看有没有重复,重复的会导致 Agent 调用时行为不确定。第三项,触发词覆盖校验。检查triggers是否为空、是否有明显重复、是否和description语义一致。这三项用几十行代码就能实现,但能挡掉八成低级错误。
注意:自动生成不要追求一次到位。我的做法是先小批量生成五到十个,人工抽查确认没问题,再放大批量。直接生成上百个再检查,工作量反而更大。
5. 踩坑记录:那些文档里不会写的经验
5.1 触发词写太多反而互相打架
我一开始觉得触发词越多越好,恨不得把用户可能说的所有话都列进去。结果发现,当两个 Skill 的触发词有重叠时,Agent 会随机选一个,或者干脆两个都调用,输出变得混乱。比如“整理网页”和“总结文章”这两个 Skill,如果触发词都包含“整理”,就会打架。
后来我定了一条规矩:每个 Skill 的触发词控制在三到五个,且必须和description强相关。如果两个 Skill 确实容易混淆,就在description里写清楚区别,比如一个强调“保留原文结构”,一个强调“提炼核心观点”。实测下来,触发词精简之后,匹配准确率反而更高。
5.2 description 写得太“聪明”会导致匹配失败
有段时间我喜欢在description里用比喻和修辞,觉得这样显得高级。结果 Agent 匹配时经常找不到对应的 Skill,因为它不理解比喻。比如我写“把网页变成知识卡片”,Agent 不知道“知识卡片”是什么,匹配就失败了。改成“把网页正文整理成结构化 Markdown 笔记”之后,匹配立刻正常。
这件事让我明白,Skill 的元数据是写给机器看的,不是写给人类看的。准确、直白、包含关键名词,比文采重要得多。你可以把description理解成数据库里的索引字段,它的唯一任务就是让 Agent 快速判断“这个 Skill 是不是当前任务需要的”。
5.3 输出格式不给模板,Agent 就会自由发挥
我早期写的 Skill 里,输出格式部分只写了“按 Markdown 格式输出”。结果 Agent 每次输出的结构都不一样,有时候用一级标题,有时候用三级标题,有时候干脆不分段。后来我学乖了,直接在 Skill 里贴一个完整模板,用代码块包起来,Agent 照着填就行。
模板不需要很复杂,但必须包含所有固定部分。比如笔记类 Skill,模板里要有标题、来源、正文、要点四个区块。Agent 看到模板后,会自觉把内容往对应位置放,输出一致性大幅提升。这个技巧看起来笨,但效果立竿见影。
5.4 Skill 之间也会“抢活”,需要明确优先级
当 Skill 数量超过十个之后,我遇到了新问题:有些任务同时符合多个 Skill 的触发条件,Agent 不知道该用哪个。比如“把网页整理成笔记”和“把网页内容翻译成中文”,如果用户说“帮我处理这个网页”,两个 Skill 都可能被调用。
我的解决办法是在 Skill 里加一个priority字段,数值越小优先级越高。同时在description里写清楚适用边界,比如翻译类 Skill 明确写“当用户要求翻译时使用”。另外,我会定期检查 Skill 列表,把功能重叠的合并或拆分,保持每个 Skill 职责单一。这套组合拳打下来,抢活的情况基本消失了。
6. 常见问题速查与排查思路
6.1 Skill 不生效的排查顺序
遇到 Skill 不生效,我一般按这个顺序排查:先看 YAML 头部有没有语法错误,比如缩进不对、冒号后面没空格;再看name是否和文件名一致,有些框架要求两者匹配;然后看description和triggers是否覆盖了用户的表达方式;最后看 Skill 文件是否放在框架指定的目录里。这四步能解决大部分问题。
如果四步都查了还是不生效,我会把 Skill 内容打印出来,手动模拟 Agent 的匹配过程,看它在哪一步卡住。有时候问题出在框架的加载逻辑上,比如缓存没刷新、文件编码不对。这种时候重启服务或者清缓存往往能解决。
6.2 输出格式不稳定的三种可能原因
输出格式不稳定,通常有三个原因。第一,Skill 里没给模板,Agent 自由发挥;第二,模板给了但不够具体,比如只写了“用列表”,没写用有序还是无序;第三,Skill 和其他 Skill 冲突,Agent 混用了两套格式。对应的解决办法分别是:补模板、细化模板、排查冲突。
我还会在 Skill 的注意事项里加一句“严格按模板输出,不要增删区块”。这句话看起来多余,但实测能明显减少 Agent 的“创意发挥”。模型有时候会自作主张优化格式,明确禁止之后它就老实了。
6.3 自动生成脚本报错的常见原因
自动生成脚本报错,八成是配置文件的格式问题。YAML 对缩进极其敏感,多一个空格少一个空格都会报错。我建议用支持 YAML 语法高亮的编辑器写配置,能提前发现大部分问题。另外,字符串里如果有冒号或特殊字符,记得用引号包起来,否则解析会出错。
还有一个容易忽略的点是编码。配置文件如果保存成非 UTF-8 编码,读取时可能乱码,导致生成的 Skill 内容错乱。我统一用 UTF-8,并且在脚本里显式指定encoding="utf-8",避免依赖系统默认编码。
| 问题现象 | 可能原因 | 排查动作 |
|---|---|---|
| Skill 完全不触发 | YAML 语法错误或目录不对 | 检查缩进、文件名、存放路径 |
| 触发但输出混乱 | 触发词冲突或缺少模板 | 精简触发词、补充输出模板 |
| 生成文件内容错乱 | 配置编码或占位符错误 | 统一 UTF-8、检查占位符拼写 |
| 多个 Skill 抢活 | 职责重叠、缺少优先级 | 合并拆分、增加 priority 字段 |
6.4 我个人的避坑清单
最后分享几条我踩坑后总结的规矩。第一,Skill 文件命名和name字段保持一致,减少心智负担。第二,每次修改 Skill 后,用至少三个真实任务回归测试,确认没有破坏原有行为。第三,定期清理不再使用的 Skill,避免列表膨胀导致匹配变慢。第四,把 Skill 纳入版本管理,每次改动都有记录,出问题能回滚。第五,不要在一个 Skill 里塞太多步骤,超过十步就考虑拆分。
这些规矩看起来琐碎,但每一条都是真金白银换来的。Skill 这套机制本身不复杂,复杂的是如何让它稳定、可维护、可扩展。手撸一遍是理解它的最好方式,自动生成是放大它的最好手段,而踩坑记录则是让后来者少走弯路的唯一途径。我现在维护着几十个 Skill,日常任务基本都能自动匹配执行,偶尔出问题也能快速定位。这套东西一旦跑顺,你会发现自己从“反复交代”的循环里彻底解放出来了。