关于 SKILL.md,写了很多份,也踩了很多坑,今天想认真聊聊:到底什么样的 SKILL.md 才是真正有效的 AI Agent Skills 文件,而不是一份自我感良好的 Markdown 文档。这个问题值得单独拿出来说,是因为我在实际项目里见过太多“写了等于没写”的技能文件——几十行甚至上百行的 skill 描述,LLM 调用之后输出的东西跟没调用时毫无差别,甚至更差。SKILL.md 的设计逻辑和普通技术文档完全不同,它的读者不是人,而是大语言模型。它的作用不是“让人看懂”,而是“让模型在正确的时机、以正确的姿势、输出正确的结果”。这篇文章适合正在做 Agent 技能封装、给 Agent 扩展能力、或者在做技能市场/技能库的同学,我会把从触发到执行的完整设计链路,以及实操中反复踩过的坑,一次讲透。1. 先想明白:SKILL.md 不是给人读的文档1.1 常见的误解:把 SKILL.md 写成培训教材我见过很多团队的 SKILL.md,打开以后是这种风格:开头讲背景、中间讲概念、结尾讲展望,通篇充满了“作为一名专业分析师,你应该具备全面的能力”这种话。问题是,模型读到这种内容以后,根本不知道该做什么。它不会因为你说“你应该具备全面的能力”就真的具备,更不会因为你写了一千字的行业介绍就变成专家。本质上,这是把 SKILL.md 当成培训教材来写了,而模型不是学员,它没法“消化知识然后融会贯通”。真正有效的 SKILL.md,更像一份“操作手册 + 质检标准 + 失败预案”的混合体。它不教育模型,而是约束模型。它要做的事情只有三件:第一,让模型知道什么场景下该拿出这个技能;第二,让模型进入该技能后按照固定流程走,而不是自由发挥;第三,让模型的输出满足可预期的格式和质量,而不是每次都不一样。这一点想不清楚,后面写多少字都是白搭。我见过有人给一个“会议纪要提炼”技能写了四百行,里面塞满了对会议背景的论述,但真正步骤只有一句“提取会议要点”。模型读完后确实被触发,但输出的结果跟不触发时没本质区别。核心问题就是:作者在“描述一个技能”,而不是“约束一个执行过程”。1.2 LLM 读取 SKILL.md 的真实链路要写对 SKILL.md,得先理解模型到底怎么用它。这个链路大致可以拆成三步。第一步是触发判断。系统把用户当前的问题和已经加载的各种技能描述进行比较,模型需要判断“当前任务是否匹配这个技能”。这里注意,很多框架并不是把所有技能文件一股脑塞进上下文,而是先只加载技能的描述区(尤其是 description 字段),让模型判断“要不要用这个技能”。判断完以后,才会把完整的 SKILL.md 正文加载进上下文。第二步是执行规划。模型读到你完整技能文件后,会把你的步骤、约束、示例转化为当前任务的执行计划。这里面有个重要特性:模型是逐层理解指令的,它先看大目标,再看步骤列表,然后看示例,最后才生成输出。如果你的步骤写得太粗,它就会在步骤之间自行脑补;如果你的示例写得太少,它就只能靠“理解”而不是“模仿”来输出。第三步是质量回看。有些技能文件里写了“生成后检查以下条目”,模型在输出完内容后,会多为一个自查过程。这一步很多人忽略,但恰恰是稳定输出质量的关键机制。没有这一步,模型输出完就结束,对不对全靠运气。1.3 有效性怎么衡量:三个可观察信号判断一个 SKILL.md 是否有效,我不看它写了多少字,只看三个信号。第一,触发率是否正常。在大量无关请求下,技能被误触发的次数是否低;在相关请求下,能否稳定触发。如果十个无关问题里有三个触发了技能,说明边界描述不清晰;如果十个相关问题里只有三个触发,说明触发条件写得不够明确。第二,行为是否被约束。同一个技能跑十次,输出结构是否基本一致。如果十次输出每次都换格式、换口径,说明约束力不足。注意,这里说的不是“完全一样”,而是维度、章节、信息完整度是否一致。第三,是否产生增量价值。调用技能后的输出,比不调用时是否明显更好。很多技能的调用结果只是给模型起了一个“专家人设”,输出质量和原来没什么两样,那这个技能就是无效的。增量价值才是技能存在的最终理由。2. SKILL.md 的核心结构:五个层次的递进设计2.1 描述区:决定“什么时候被调用”描述区是 SKILL.md 里最重要的部分,没有之一。因为很多框架在触发阶段只把 description 塞给模型,正文根本还没加载。换句话说,描述写得不好,连被读取的机会都没有。写描述有两条硬规矩。第一,明确触发场景,用动词短语列清楚“当用户请求以下事情时使用”。比如“当用户要求对一段代码进行审查、找出 bug、评估安全隐患时使用”。这类表述要贴近用户真实的说话方式,而不是你自己的专业术语。用户说“帮我看看这段代码写得好不好”,你得把这种口语触发方式也写进去。第二,明确不触发场景,用“不要用于”来划边界。比如“不要用于解释代码逻辑、不要用于生成新代码、不要用于回答通用编程问题”。边界比范围更重要。模型判断“该不该用”时,边界反而更有效。另外我建议在描述区里写清楚这个技能需要的关键输入是什么。比如代码审查技能需要“被审查的代码”,写报告技能需要“数据文件路径”。如果用户没有提供这些输入,模型就会先想办法获取,而不是硬着头皮做。2.2 流程区:决定“执行的稳定性”流程区是技能内核,也是大多数 SKILL.md 写不到位的地方。问题集中在两个极端:要么只有两三步,笼统到等于没写;要么把每一步写得像程序里的函数注释,又长又死,模型反而不知道该不该灵活处理。我的经验是,步骤数量控制在五到九步之间,每步给出三个要素:动作指向、内容要求、产出形式。举个反例,“分析代码问题”就是典型的只写了动作指向,没写内容要求和产出形式。模型读完之后,分析什么维度、用什么标准、输出什么形式,全都靠猜。有效写法是“针对正确性、安全性、性能、可维护性四个维度逐一分析,每个维度输出存在问题和修复建议”。这一步就同时给了维度、评价标准和产出形式。还有一点容易忽略:步骤之间的依赖关系要写清楚。哪些步骤必须在前,哪些可以在任意时候执行,哪些需要上一步的结果作为输入。模型对流程的理解是线性的,如果你暗示了“先A再B,A的结果用于B”,它就很少会乱序执行。2.3 示例区:决定“输出的保真度”示例区是多数 SKILL.md 缺失的部分,这是很可惜的。对于没有经过微调的通用模型,一个具体示例远比十句抽象说明有效。如果技能里涉及固定格式的输出,示例要从输入到输出完整展示一遍,而不只是贴一个输出模板。我在实践中发现,单一正确示例还不够,最好能做到“好坏对比”。给一个符合要求的正面示例,再给一个不符合要求的反面示例,然后告诉模型“反面示例的问题是什么”。这种对比式的示例,对约束模型输出的作用非常明显。因为模型在对比中能学到格式要求和错误模式,这不是单纯给一个模板能实现的。示例还有一个隐藏功能,就是暗中设定语言的严谨度、详略程度和口吻。如果你的示例写得很精炼、用词准确,模型会倾向模仿这种风格。如果你示例写得啰嗦,输出也会啰嗦。所以示例不仅仅是在规定格式,还在规定语言风格。2.4 边界区:决定“失败的兜底”边界区是很多人不写的。这块处理的是“这个技能能做什么、不能做什么、能力不足时怎么办”。未必每个技能都很复杂,但至少要想清楚三件事:模型没有足够输入时怎么办,遇到超出技能范围的要求时怎么办,输出无法满足用户预期时怎么办。比如一个“周报生成”技能,边界区可以写:如果用户没有提供本周具体工作内容,不要编造任务,应主动询问用户补充;如果用户要求的不是周报而是月报,应说明该技能仅适用周报。这种兜底行为看起来简单,但实际作用很大。模型在边界清晰时,面对异常情况不会硬编硬造,而是选择询问、澄清或者拒绝。我在很多事故里看到,模型在没有边界兜底时,面对缺失信息会选择填一个“看起来合理”的假内容。这个风险在真实应用中可能很严重。边界区写清楚,就是在给模型安一个刹车。2.5 自检区:决定“质量的闭环”自检区是给输出做“出厂质检”。具体做法是在技能文件末尾加一个“完成输出后,检查以下项目”的列表。模型在生成完结果后,会依据这个列表快速复核,发现出错时当场修正。自检清单的写法要具体、可执行,不能写“检查内容是否完整”这种废话。要写“确认输出包含总体评价部分”“确认问题列表包含严重级别列”“确认每条建议包含具体修改位置”“确认没有输出与代码审查无关的泛泛而谈”。每一条都是一眼能看过去确认对错的检查项,而不是需要模型二次“理解”的抽象要求。这个机制的价值在于,它把质量保障从“生成前设计”延伸到了“生成后校验”。模型先产出初稿,再按清单快速过一遍,有遗漏当场补上。实操效果非常显著,输出完整度能提高很大一截。3. 从反面教材到正面教材:一个技能文件的改造全流程3.1 第一版:只有三行话的失败文件先看一个我实际见过并参与改造过的例子:一个代码审查技能,第一版长这样。# 代码审查 当用户要求审查代码时使用本技能。请全面审查用户提供的代码,指出其中的问题。这版文件的问题非常典型:没有描述区、没有触发边界、没有审查维度、没有输出格式、没有示例、没有兜底。模型调用后,通常会随便说几句“代码整体清晰,建议优化函数命名”,完全不具备审查价值。用它和不用的区别基本为零。这类文件在 Agent 技能库里其实占绝大多数。它们的共同特点是“说了很多方向,没给任何约束”。LLM 拿到这种内容时,会基于自己的倾向自由发挥,于是每次输出都是另一个版本。3.2 中间版:加入了基础结构的合理配置第二版我们加入了基础结构,开始能看出技能的样子了。--- name: code_review description: 当用户要求审查代码、找 bug、评估代码安全或代码质量时使用。当用户只是要求讲解代码逻辑时不要使用。 --- # 代码审查 ## 步骤 1. 分析用户提供的代码,确认代码语言和主要功能。 2. 从正确性、安全性、性能、可维护性四个维度审查。 3. 输出审查报告。这一版比第一版好了很多,有了触发描述、步骤、输出方向。但它还是有问题。步骤二只说“从四个维度审查”,没说具体看什么。步骤三没说输出报告的结构。模型也许能给出四个维度的结果,但格式和详细程度仍然不稳定。而且没有示例,没有自检,没有边界兜底。3.3 最终版:一份能直接上线用的技能文件最终版是经过多轮迭代后沉淀下来的。这里我给出核心结构,细节已做脱敏处理。--- name: code_review description: 当用户要求审查代码、查找代码中的 bug、评估安全性或代码质量时使用。当用户只是要求解释代码、补全代码或生成新代码时不要使用。本技能需要用户提供待审查的代码,缺失时应先要求用户补充。 --- # 代码审查技能 ## 背景 本技能用于对用户提供的代码进行结构化审查,输出可按严重级别排序的问题清单。 ## 操作流程 1. 确认代码语言、代码用途和涉及的关键依赖。 2. 按以下维度逐一检查代码: - 正确性:是否存在逻辑错误、边界条件遗漏、空值未处理、并发问题。 - 安全性:是否存在注入风险、敏感信息硬编码、认证绕过风险。 - 性能:是否存在明显的时间或空间复杂度过高、循环内重复计算。 - 可维护性:命名是否清晰、函数是否过长、是否存在重复代码。 3. 对发现的问题按严重级别分级:严重、中等、轻微。 4. 生成审查报告。 ## 输出格式 必须按以下 Markdown 格式输出: ### 总体评价 用一句话概括代码整体质量。 ### 问题列表 | 严重级别 | 问题描述 | 所在位置 | 修复建议 | | --- | --- | --- | --- | | 严重 | 未对用户输入做任何校验 | 第 15 行 | 增加输入长度和格式校验 | ### 亮点说明 列出代码中值得肯定的 2 到 3 个点。 ## 示例 输入: ```python def calc(data): return data["a"] + data["b"]输出:总体评价代码逻辑简单,但缺少输入校验和边界处理,存在运行时异常风险。问题列表严重级别问题描述所在位置修复建议严重未检查 data 是否包含 a 和 b 字段,可能抛 KeyError第 1 行使用 get 方法并提供默认值中等未校验 a、b 的数据类型,可能出现拼接字符串的错误第 1 行添加类型检查或转换边界与兜底如果用户提供的代码片段不完整,无法确认上下文,应在报告中注明“基于不完整代码的推测性审查”。如果用户要求审查的不是代码而是配置或文本,说明本技能适用范围的限制,并建议其他技能。自检清单输出完成后,确认:[ ] 报告是否包含总体评价、问题列表、亮点说明三个部分。[ ] 问题列表每一行都包含严重级别、问题描述、所在位置、修复建议。[ ] 每条问题描述是否对应到具体代码位置,而不是含糊表述。[ ] 是否至少发现一个具体可修复的问题,如果代码质量较高也要写明“未发现严重问题”。这个版本为什么比前两版有效?我来拆一下。描述区写清了触发场景、不触发场景、输入缺失处理:三段信息各司其职。操作流程的每一步都有维度、有具体检查点。输出格式给了固定的 Markdown 模板,还配了表格列名。示例给出了输入和输出的完整映射,模型看到一次真实转化过程。边界兜底面向“不完整代码”和“非代码请求”两种常见异常。自检清单强制输出结构完整。这一套下来,模型执行十次的输出结构基本一致,这才是有效技能。 ### 3.4 改造过程中的思考逻辑 这个例子想说明的是,每一轮改造的目标都很明确:先解决“动不动”,再解决“稳不稳”,最后解决“好不好”。第一版动手前,先确认技能的边界,把“触发条件”和“不触发条件”写进描述区。第二步,设计流程时,主要想的是“怎样防止模型自由发挥”,于是把审查维度细化成四个具体方面。第三步,发现输出结构不稳定,于是固定输出模板,加示例。第四步,发现用户经常给半截代码,于是加兜底说明。每一步改动,都对应一个具体的问题,而不是“扩充内容”式地增肥。 ## 4. 实操中的经验与坑:常见问题排查实录 ### 4.1 一张速查表定位大部分问题 这里把我在实际项目中遇到最多的问题、原因和对应解法整理成一张表,排查时可以按图索骥。 | 现象 | 大概率原因 | 对应解法 | | --- | --- | --- | | 相关请求下技能不触发 | description 里触发词覆盖不够,或触发场景写得太书面 | 用口语化表达重写触发场景,多列几种用户真实说法 | | 无关请求触发技能 | 没有写不触发边界,或边界太窄 | 在 description 中增加“不要用于”列表,并覆盖常见干扰场景 | | 调用技能后输出和原来没区别 | 流程步骤过粗,模型没有获得增量约束 | 细化每一步的动作和产出形式,增加维度清单 | | 同一次技能每次输出格式不稳定 | 缺少输出格式模板和示例 | 固定 Markdown 模板,提供完整输入输出示例 | | 输出内容经常缺项 | 缺少自检清单这一闭环 | 在文件末尾加具体可勾选的校验项 | | 面对缺失输入时模型开始编造 | 缺少边界兜底说明 | 增加“信息缺失时询问用户”的指令 | | 技能执行到一半开始泛泛而谈 | 步骤里没有“逐项分析”这种强制分解指令 | 把步骤改为“逐一检查以下列表”的形式 | 这张表覆盖了我遇到的绝大多数问题。还有一个想特别提的:很多技能文件写得过长,几十个步骤、几十个示例,模型加载后不仅浪费 token,还可能因为信息过载而“挑着执行”。技能文件不是越详细越好,而是越精确越好。漏掉关键步骤的信息是坏,塞满无关约束的信息也是坏。 ### 4.2 触发问题的两个极端 触发是问题高发区,也是最不直观的。我见过一个“润色文案”技能,description 里写了“当用户要求优化文案、让文字更流畅时使用”,结果用户说“帮我改一下这段话”也触发了,用户说“这个句子读起来别扭,怎么说比较好”也触发了。问题在于触发词写得太宽泛,没有覆盖意图的差异性。优化文案和“改一下这段话”是不完全一样的任务,前者是润色,后者可能是改写,也可能是纠错。所以我会在 description 里把正例和反例都铺开写。 反过来,我也见过一个“周报生成”技能,description 写成了“当用户需要生成周报时使用”,但用户说的是“把这一周干的几件事整理一下”,模型完全没识别出来这就是周报请求。原因是触发描述里没有包括用户口语表达中“整理、总结、汇总”这类日常动词。解决方式就是收集真实用户的高频说法,然后把它们写进描述。 触发问题没有一次写对的标准答案,只能在实际对话里试错迭代。我会建议,写完一个技能后,拿十个可能触发的问题和十个可能不触发的问题,逐一跑一遍对话,看触发的准确率,再针对性调整 description。这个测试成本很低,但对体验的提升很大。 ### 4.3 示例数量:一个不够,五个太多 示例的颗粒度很多人拿不准。我试下来,一个完整的“输入到输出”示例是基础配置,两个到三个能明显提升稳定性,五个以上就开始边际递减。示例一超过五个,模型不仅不会更听话,反而有可能把你的示例当成全部可能性的上限,遇到需要用其他方式处理的场景时就不知所措。 所以我的建议是:核心技能放一个全流程正例,再放一个反面示例标注“错误原因”,其他技能放一个正例就够了。反面示例的价值我这里再强调一次,它相当于给模型划了一条“不要这样做”的红线,这种约束比正例更直接。更重要的是,反面示例通常还能顺带解决一个问题,就是模型输出过短、缺乏具体内容。在反面示例里展示“由于缺少细节,该输出不通过”,模型见过一次以后,就会格外注意信息的完整度。 ### 4.4 中英文与 token 成本问题 最后说一个很多人忽略的细节:SKILL.md 里尽量用中文写还是要用英文写,取决于你的主语言场景。但不管用哪种语言,都要注意一个问题:如果你的技能文件特别长,比如超过三千个 token,模型在加载时会有更高的“理解成本”。通常我会控制一支技能文件的总长度在 600 到 1500 个 token 之间,保证关键信息不丢,同时不给上下文管理器增加太多压力。 这里还有一个容易被忽略的小技巧:不要以为技能文件里所有信息都会被模型“平等地”处理。模型对靠前和靠后的信息记忆更牢,中间部分容易弱化。所以我会把最关键的“操作流程”和“输出格式”放在文件的前半部分,把“背景”和“补充说明”这类次级信息放后面。如果你的技能依赖多个示例,示例要靠后放,或者只在描述区引用强示例,因为示例本身不要紧,要紧的是模型对输出格式的理解。 另外,很多框架支持在 SKILL.md 里引用外部脚本文件,比如 skill 描述 + 一段 Python 或者 Shell 代码。如果你做的是工具类技能,最有效的写法往往不是让模型“自由调用”脚本,而是把脚本的输入参数、输出格式、错误处理都在 SKILL.md 里写死,让模型只负责“把用户请求翻译成脚本参数”,而不是“理解脚本怎么运行”。这个边界分得越清楚,工具类技能的可靠性就越高。 最后再给一个个人体会。SKILL.md 的编写本质上是一个对抗模型自由度的过程,模型自由度越大,输出越不可控。你写每一个章节、每一条约束,都是在给这个自由度套上缰绳。但这个缰绳不能套得太死,否则模型面对新情况时会失去变通能力。一条最基本的路子是:先写出最小可用的技能文件,跑几个真实场景,从输出差异里找到失控点,然后有针对地修改。不要一开始就追求大而全。把技能当作一个不断迭代的活文件,随时根据测试结果补齐漏洞,会比“憋大招”式的写法靠谱得多。
关于 SKILL.md,写了很多份,也踩了很多坑,今天想认真聊聊:到底什么样的 SKILL.md 才是真正有效的 AI Agent Skills 文件,而不是一份自我感良好的 Markdown 文档。这个问题值得单独拿出来说,是因为我在实际项目里见过太多“写了等于没写”的技能文件——几十行甚至上百行的 skill 描述,LLM 调用之后输出的东西跟没调用时毫无差别,甚至更差。SKILL.md 的设计逻辑和普通技术文档完全不同,它的读者不是人,而是大语言模型。它的作用不是“让人看懂”,而是“让模型在正确的时机、以正确的姿势、输出正确的结果”。这篇文章适合正在做 Agent 技能封装、给 Agent 扩展能力、或者在做技能市场/技能库的同学,我会把从触发到执行的完整设计链路,以及实操中反复踩过的坑,一次讲透。1. 先想明白:SKILL.md 不是给人读的文档1.1 常见的误解:把 SKILL.md 写成培训教材我见过很多团队的 SKILL.md,打开以后是这种风格:开头讲背景、中间讲概念、结尾讲展望,通篇充满了“作为一名专业分析师,你应该具备全面的能力”这种话。问题是,模型读到这种内容以后,根本不知道该做什么。它不会因为你说“你应该具备全面的能力”就真的具备,更不会因为你写了一千字的行业介绍就变成专家。本质上,这是把 SKILL.md 当成培训教材来写了,而模型不是学员,它没法“消化知识然后融会贯通”。真正有效的 SKILL.md,更像一份“操作手册 + 质检标准 + 失败预案”的混合体。它不教育模型,而是约束模型。它要做的事情只有三件:第一,让模型知道什么场景下该拿出这个技能;第二,让模型进入该技能后按照固定流程走,而不是自由发挥;第三,让模型的输出满足可预期的格式和质量,而不是每次都不一样。这一点想不清楚,后面写多少字都是白搭。我见过有人给一个“会议纪要提炼”技能写了四百行,里面塞满了对会议背景的论述,但真正步骤只有一句“提取会议要点”。模型读完后确实被触发,但输出的结果跟不触发时没本质区别。核心问题就是:作者在“描述一个技能”,而不是“约束一个执行过程”。1.2 LLM 读取 SKILL.md 的真实链路要写对 SKILL.md,得先理解模型到底怎么用它。这个链路大致可以拆成三步。第一步是触发判断。系统把用户当前的问题和已经加载的各种技能描述进行比较,模型需要判断“当前任务是否匹配这个技能”。这里注意,很多框架并不是把所有技能文件一股脑塞进上下文,而是先只加载技能的描述区(尤其是 description 字段),让模型判断“要不要用这个技能”。判断完以后,才会把完整的 SKILL.md 正文加载进上下文。第二步是执行规划。模型读到你完整技能文件后,会把你的步骤、约束、示例转化为当前任务的执行计划。这里面有个重要特性:模型是逐层理解指令的,它先看大目标,再看步骤列表,然后看示例,最后才生成输出。如果你的步骤写得太粗,它就会在步骤之间自行脑补;如果你的示例写得太少,它就只能靠“理解”而不是“模仿”来输出。第三步是质量回看。有些技能文件里写了“生成后检查以下条目”,模型在输出完内容后,会多为一个自查过程。这一步很多人忽略,但恰恰是稳定输出质量的关键机制。没有这一步,模型输出完就结束,对不对全靠运气。1.3 有效性怎么衡量:三个可观察信号判断一个 SKILL.md 是否有效,我不看它写了多少字,只看三个信号。第一,触发率是否正常。在大量无关请求下,技能被误触发的次数是否低;在相关请求下,能否稳定触发。如果十个无关问题里有三个触发了技能,说明边界描述不清晰;如果十个相关问题里只有三个触发,说明触发条件写得不够明确。第二,行为是否被约束。同一个技能跑十次,输出结构是否基本一致。如果十次输出每次都换格式、换口径,说明约束力不足。注意,这里说的不是“完全一样”,而是维度、章节、信息完整度是否一致。第三,是否产生增量价值。调用技能后的输出,比不调用时是否明显更好。很多技能的调用结果只是给模型起了一个“专家人设”,输出质量和原来没什么两样,那这个技能就是无效的。增量价值才是技能存在的最终理由。2. SKILL.md 的核心结构:五个层次的递进设计2.1 描述区:决定“什么时候被调用”描述区是 SKILL.md 里最重要的部分,没有之一。因为很多框架在触发阶段只把 description 塞给模型,正文根本还没加载。换句话说,描述写得不好,连被读取的机会都没有。写描述有两条硬规矩。第一,明确触发场景,用动词短语列清楚“当用户请求以下事情时使用”。比如“当用户要求对一段代码进行审查、找出 bug、评估安全隐患时使用”。这类表述要贴近用户真实的说话方式,而不是你自己的专业术语。用户说“帮我看看这段代码写得好不好”,你得把这种口语触发方式也写进去。第二,明确不触发场景,用“不要用于”来划边界。比如“不要用于解释代码逻辑、不要用于生成新代码、不要用于回答通用编程问题”。边界比范围更重要。模型判断“该不该用”时,边界反而更有效。另外我建议在描述区里写清楚这个技能需要的关键输入是什么。比如代码审查技能需要“被审查的代码”,写报告技能需要“数据文件路径”。如果用户没有提供这些输入,模型就会先想办法获取,而不是硬着头皮做。2.2 流程区:决定“执行的稳定性”流程区是技能内核,也是大多数 SKILL.md 写不到位的地方。问题集中在两个极端:要么只有两三步,笼统到等于没写;要么把每一步写得像程序里的函数注释,又长又死,模型反而不知道该不该灵活处理。我的经验是,步骤数量控制在五到九步之间,每步给出三个要素:动作指向、内容要求、产出形式。举个反例,“分析代码问题”就是典型的只写了动作指向,没写内容要求和产出形式。模型读完之后,分析什么维度、用什么标准、输出什么形式,全都靠猜。有效写法是“针对正确性、安全性、性能、可维护性四个维度逐一分析,每个维度输出存在问题和修复建议”。这一步就同时给了维度、评价标准和产出形式。还有一点容易忽略:步骤之间的依赖关系要写清楚。哪些步骤必须在前,哪些可以在任意时候执行,哪些需要上一步的结果作为输入。模型对流程的理解是线性的,如果你暗示了“先A再B,A的结果用于B”,它就很少会乱序执行。2.3 示例区:决定“输出的保真度”示例区是多数 SKILL.md 缺失的部分,这是很可惜的。对于没有经过微调的通用模型,一个具体示例远比十句抽象说明有效。如果技能里涉及固定格式的输出,示例要从输入到输出完整展示一遍,而不只是贴一个输出模板。我在实践中发现,单一正确示例还不够,最好能做到“好坏对比”。给一个符合要求的正面示例,再给一个不符合要求的反面示例,然后告诉模型“反面示例的问题是什么”。这种对比式的示例,对约束模型输出的作用非常明显。因为模型在对比中能学到格式要求和错误模式,这不是单纯给一个模板能实现的。示例还有一个隐藏功能,就是暗中设定语言的严谨度、详略程度和口吻。如果你的示例写得很精炼、用词准确,模型会倾向模仿这种风格。如果你示例写得啰嗦,输出也会啰嗦。所以示例不仅仅是在规定格式,还在规定语言风格。2.4 边界区:决定“失败的兜底”边界区是很多人不写的。这块处理的是“这个技能能做什么、不能做什么、能力不足时怎么办”。未必每个技能都很复杂,但至少要想清楚三件事:模型没有足够输入时怎么办,遇到超出技能范围的要求时怎么办,输出无法满足用户预期时怎么办。比如一个“周报生成”技能,边界区可以写:如果用户没有提供本周具体工作内容,不要编造任务,应主动询问用户补充;如果用户要求的不是周报而是月报,应说明该技能仅适用周报。这种兜底行为看起来简单,但实际作用很大。模型在边界清晰时,面对异常情况不会硬编硬造,而是选择询问、澄清或者拒绝。我在很多事故里看到,模型在没有边界兜底时,面对缺失信息会选择填一个“看起来合理”的假内容。这个风险在真实应用中可能很严重。边界区写清楚,就是在给模型安一个刹车。2.5 自检区:决定“质量的闭环”自检区是给输出做“出厂质检”。具体做法是在技能文件末尾加一个“完成输出后,检查以下项目”的列表。模型在生成完结果后,会依据这个列表快速复核,发现出错时当场修正。自检清单的写法要具体、可执行,不能写“检查内容是否完整”这种废话。要写“确认输出包含总体评价部分”“确认问题列表包含严重级别列”“确认每条建议包含具体修改位置”“确认没有输出与代码审查无关的泛泛而谈”。每一条都是一眼能看过去确认对错的检查项,而不是需要模型二次“理解”的抽象要求。这个机制的价值在于,它把质量保障从“生成前设计”延伸到了“生成后校验”。模型先产出初稿,再按清单快速过一遍,有遗漏当场补上。实操效果非常显著,输出完整度能提高很大一截。3. 从反面教材到正面教材:一个技能文件的改造全流程3.1 第一版:只有三行话的失败文件先看一个我实际见过并参与改造过的例子:一个代码审查技能,第一版长这样。# 代码审查 当用户要求审查代码时使用本技能。请全面审查用户提供的代码,指出其中的问题。这版文件的问题非常典型:没有描述区、没有触发边界、没有审查维度、没有输出格式、没有示例、没有兜底。模型调用后,通常会随便说几句“代码整体清晰,建议优化函数命名”,完全不具备审查价值。用它和不用的区别基本为零。这类文件在 Agent 技能库里其实占绝大多数。它们的共同特点是“说了很多方向,没给任何约束”。LLM 拿到这种内容时,会基于自己的倾向自由发挥,于是每次输出都是另一个版本。3.2 中间版:加入了基础结构的合理配置第二版我们加入了基础结构,开始能看出技能的样子了。--- name: code_review description: 当用户要求审查代码、找 bug、评估代码安全或代码质量时使用。当用户只是要求讲解代码逻辑时不要使用。 --- # 代码审查 ## 步骤 1. 分析用户提供的代码,确认代码语言和主要功能。 2. 从正确性、安全性、性能、可维护性四个维度审查。 3. 输出审查报告。这一版比第一版好了很多,有了触发描述、步骤、输出方向。但它还是有问题。步骤二只说“从四个维度审查”,没说具体看什么。步骤三没说输出报告的结构。模型也许能给出四个维度的结果,但格式和详细程度仍然不稳定。而且没有示例,没有自检,没有边界兜底。3.3 最终版:一份能直接上线用的技能文件最终版是经过多轮迭代后沉淀下来的。这里我给出核心结构,细节已做脱敏处理。--- name: code_review description: 当用户要求审查代码、查找代码中的 bug、评估安全性或代码质量时使用。当用户只是要求解释代码、补全代码或生成新代码时不要使用。本技能需要用户提供待审查的代码,缺失时应先要求用户补充。 --- # 代码审查技能 ## 背景 本技能用于对用户提供的代码进行结构化审查,输出可按严重级别排序的问题清单。 ## 操作流程 1. 确认代码语言、代码用途和涉及的关键依赖。 2. 按以下维度逐一检查代码: - 正确性:是否存在逻辑错误、边界条件遗漏、空值未处理、并发问题。 - 安全性:是否存在注入风险、敏感信息硬编码、认证绕过风险。 - 性能:是否存在明显的时间或空间复杂度过高、循环内重复计算。 - 可维护性:命名是否清晰、函数是否过长、是否存在重复代码。 3. 对发现的问题按严重级别分级:严重、中等、轻微。 4. 生成审查报告。 ## 输出格式 必须按以下 Markdown 格式输出: ### 总体评价 用一句话概括代码整体质量。 ### 问题列表 | 严重级别 | 问题描述 | 所在位置 | 修复建议 | | --- | --- | --- | --- | | 严重 | 未对用户输入做任何校验 | 第 15 行 | 增加输入长度和格式校验 | ### 亮点说明 列出代码中值得肯定的 2 到 3 个点。 ## 示例 输入: ```python def calc(data): return data["a"] + data["b"]输出:总体评价代码逻辑简单,但缺少输入校验和边界处理,存在运行时异常风险。问题列表严重级别问题描述所在位置修复建议严重未检查 data 是否包含 a 和 b 字段,可能抛 KeyError第 1 行使用 get 方法并提供默认值中等未校验 a、b 的数据类型,可能出现拼接字符串的错误第 1 行添加类型检查或转换边界与兜底如果用户提供的代码片段不完整,无法确认上下文,应在报告中注明“基于不完整代码的推测性审查”。如果用户要求审查的不是代码而是配置或文本,说明本技能适用范围的限制,并建议其他技能。自检清单输出完成后,确认:[ ] 报告是否包含总体评价、问题列表、亮点说明三个部分。[ ] 问题列表每一行都包含严重级别、问题描述、所在位置、修复建议。[ ] 每条问题描述是否对应到具体代码位置,而不是含糊表述。[ ] 是否至少发现一个具体可修复的问题,如果代码质量较高也要写明“未发现严重问题”。这个版本为什么比前两版有效?我来拆一下。描述区写清了触发场景、不触发场景、输入缺失处理:三段信息各司其职。操作流程的每一步都有维度、有具体检查点。输出格式给了固定的 Markdown 模板,还配了表格列名。示例给出了输入和输出的完整映射,模型看到一次真实转化过程。边界兜底面向“不完整代码”和“非代码请求”两种常见异常。自检清单强制输出结构完整。这一套下来,模型执行十次的输出结构基本一致,这才是有效技能。 ### 3.4 改造过程中的思考逻辑 这个例子想说明的是,每一轮改造的目标都很明确:先解决“动不动”,再解决“稳不稳”,最后解决“好不好”。第一版动手前,先确认技能的边界,把“触发条件”和“不触发条件”写进描述区。第二步,设计流程时,主要想的是“怎样防止模型自由发挥”,于是把审查维度细化成四个具体方面。第三步,发现输出结构不稳定,于是固定输出模板,加示例。第四步,发现用户经常给半截代码,于是加兜底说明。每一步改动,都对应一个具体的问题,而不是“扩充内容”式地增肥。 ## 4. 实操中的经验与坑:常见问题排查实录 ### 4.1 一张速查表定位大部分问题 这里把我在实际项目中遇到最多的问题、原因和对应解法整理成一张表,排查时可以按图索骥。 | 现象 | 大概率原因 | 对应解法 | | --- | --- | --- | | 相关请求下技能不触发 | description 里触发词覆盖不够,或触发场景写得太书面 | 用口语化表达重写触发场景,多列几种用户真实说法 | | 无关请求触发技能 | 没有写不触发边界,或边界太窄 | 在 description 中增加“不要用于”列表,并覆盖常见干扰场景 | | 调用技能后输出和原来没区别 | 流程步骤过粗,模型没有获得增量约束 | 细化每一步的动作和产出形式,增加维度清单 | | 同一次技能每次输出格式不稳定 | 缺少输出格式模板和示例 | 固定 Markdown 模板,提供完整输入输出示例 | | 输出内容经常缺项 | 缺少自检清单这一闭环 | 在文件末尾加具体可勾选的校验项 | | 面对缺失输入时模型开始编造 | 缺少边界兜底说明 | 增加“信息缺失时询问用户”的指令 | | 技能执行到一半开始泛泛而谈 | 步骤里没有“逐项分析”这种强制分解指令 | 把步骤改为“逐一检查以下列表”的形式 | 这张表覆盖了我遇到的绝大多数问题。还有一个想特别提的:很多技能文件写得过长,几十个步骤、几十个示例,模型加载后不仅浪费 token,还可能因为信息过载而“挑着执行”。技能文件不是越详细越好,而是越精确越好。漏掉关键步骤的信息是坏,塞满无关约束的信息也是坏。 ### 4.2 触发问题的两个极端 触发是问题高发区,也是最不直观的。我见过一个“润色文案”技能,description 里写了“当用户要求优化文案、让文字更流畅时使用”,结果用户说“帮我改一下这段话”也触发了,用户说“这个句子读起来别扭,怎么说比较好”也触发了。问题在于触发词写得太宽泛,没有覆盖意图的差异性。优化文案和“改一下这段话”是不完全一样的任务,前者是润色,后者可能是改写,也可能是纠错。所以我会在 description 里把正例和反例都铺开写。 反过来,我也见过一个“周报生成”技能,description 写成了“当用户需要生成周报时使用”,但用户说的是“把这一周干的几件事整理一下”,模型完全没识别出来这就是周报请求。原因是触发描述里没有包括用户口语表达中“整理、总结、汇总”这类日常动词。解决方式就是收集真实用户的高频说法,然后把它们写进描述。 触发问题没有一次写对的标准答案,只能在实际对话里试错迭代。我会建议,写完一个技能后,拿十个可能触发的问题和十个可能不触发的问题,逐一跑一遍对话,看触发的准确率,再针对性调整 description。这个测试成本很低,但对体验的提升很大。 ### 4.3 示例数量:一个不够,五个太多 示例的颗粒度很多人拿不准。我试下来,一个完整的“输入到输出”示例是基础配置,两个到三个能明显提升稳定性,五个以上就开始边际递减。示例一超过五个,模型不仅不会更听话,反而有可能把你的示例当成全部可能性的上限,遇到需要用其他方式处理的场景时就不知所措。 所以我的建议是:核心技能放一个全流程正例,再放一个反面示例标注“错误原因”,其他技能放一个正例就够了。反面示例的价值我这里再强调一次,它相当于给模型划了一条“不要这样做”的红线,这种约束比正例更直接。更重要的是,反面示例通常还能顺带解决一个问题,就是模型输出过短、缺乏具体内容。在反面示例里展示“由于缺少细节,该输出不通过”,模型见过一次以后,就会格外注意信息的完整度。 ### 4.4 中英文与 token 成本问题 最后说一个很多人忽略的细节:SKILL.md 里尽量用中文写还是要用英文写,取决于你的主语言场景。但不管用哪种语言,都要注意一个问题:如果你的技能文件特别长,比如超过三千个 token,模型在加载时会有更高的“理解成本”。通常我会控制一支技能文件的总长度在 600 到 1500 个 token 之间,保证关键信息不丢,同时不给上下文管理器增加太多压力。 这里还有一个容易被忽略的小技巧:不要以为技能文件里所有信息都会被模型“平等地”处理。模型对靠前和靠后的信息记忆更牢,中间部分容易弱化。所以我会把最关键的“操作流程”和“输出格式”放在文件的前半部分,把“背景”和“补充说明”这类次级信息放后面。如果你的技能依赖多个示例,示例要靠后放,或者只在描述区引用强示例,因为示例本身不要紧,要紧的是模型对输出格式的理解。 另外,很多框架支持在 SKILL.md 里引用外部脚本文件,比如 skill 描述 + 一段 Python 或者 Shell 代码。如果你做的是工具类技能,最有效的写法往往不是让模型“自由调用”脚本,而是把脚本的输入参数、输出格式、错误处理都在 SKILL.md 里写死,让模型只负责“把用户请求翻译成脚本参数”,而不是“理解脚本怎么运行”。这个边界分得越清楚,工具类技能的可靠性就越高。 最后再给一个个人体会。SKILL.md 的编写本质上是一个对抗模型自由度的过程,模型自由度越大,输出越不可控。你写每一个章节、每一条约束,都是在给这个自由度套上缰绳。但这个缰绳不能套得太死,否则模型面对新情况时会失去变通能力。一条最基本的路子是:先写出最小可用的技能文件,跑几个真实场景,从输出差异里找到失控点,然后有针对地修改。不要一开始就追求大而全。把技能当作一个不断迭代的活文件,随时根据测试结果补齐漏洞,会比“憋大招”式的写法靠谱得多。