
“skills”这个词单独拎出来第一反应可能是一堆简历上的套话。但在AI Agent开发这个圈子里它最近是一个具体的、正在快速标准化的工程概念——把“让模型可靠地完成某类任务”这件事从靠运气变成靠结构。这篇内容是我做过几个实际Agent项目之后对skill机制完整复盘下来的工程落地笔记。我最早接触skill这个概念是在做自动化文档处理Agent的时候。当时面临一个很实际的问题同一个大模型在“翻译一段文字”和“把合同里的关键条款抽出来填到表格里”这两种任务上表现稳定性天差地别。前者几乎不需要额外干预后者则经常漏字段、格式跑偏。后来我才意识到问题不在模型本身而在于我没有给模型提供一套足够明确的、可复用的问题解决流程。这就是skill要解决的事情。这个项目标题叫“skills”听起来像个人能力清单实际上做的是一套面向AI Agent的技能封装与编排系统。它把一类任务的完整解决路径——包括执行流程、判断规则、输入输出约定、参考示例、边界条件——打包成一个结构化的能力单元。模型在执行任务时调用对应skill等于拿到了一本针对该任务的“标准化作业手册”。这套机制解决的核心痛点有三个。第一把提示词工程从一次性工作变成可积累资产——不是每次都在对话里临场写一大段指令而是把最优实践沉淀成可复用的文件第二降低复杂任务的执行方差模型不再是自由发挥而是按照预设的步骤、格式和校验规矩走输出质量更可控第三让Agent具备“可组合的能力”不同skill像积木一样搭在一起完成更复杂的业务流程。适合参考这篇内容的人主要是这几类正在用Claude或其他大模型做Agent开发、但觉得输出不稳定的人已经用过function calling或MCP、想进一步把任务流程做标准化的人以及刚接触Agent工程、想知道“除了写提示词还能怎么提升模型表现”的初学者。下面我把从设计到落地的完整路径展开来讲。1. 为什么Agent需要skill机制1.1 从“对话式指令”到“结构化技能”的转变早期做大模型应用最常见的做法是把所有需求塞进一个system prompt里。需求不复杂时还好一旦任务链路变长——比如“先读取文档-再抽字段-再校验格式-最后导出表格”——单纯靠提示词约束就已经捉襟见肘了。模型在长上下文中容易遗忘中间步骤的顺序也容易在判断环节自作主张。我见过一个很典型的翻车案例让模型读取PDF后提取供应商信息它确实抽出来了但把“合同编号”误判成了“订单编号”原因是这个字段在上下文里离得太远模型忘记了原始定义。提示词不是不能解决这个问题而是每个项目都重新写一遍、反复调优的成本太高。skill机制把这些流程固定下来执行顺序、判断标准、字段映射关系全部以结构化的方式写清楚。对话只负责传数据流程交给skill来管。这个转变本质上就是从“临时指挥”到“制度化管理”。1.2 skill与function calling、MCP的边界划分很多刚接触的人会问skill和function calling有什么差别和MCP又是什么关系我在实际开发中的理解是这样的function calling是模型主动选择调用某个函数、传入参数的机制它的粒度是“单次调用”没有内置的多步骤流程。**MCPModel Context Protocol**解决的是“工具和模型之间怎么通信”的问题它把工具能力暴露给模型让模型可以按需调用外部数据源或服务。skill则是更高一层的封装它描述的是“完成一个任务需要经历哪些步骤、每步做什么判断、产出什么格式的结果”的完整流程。打个比方function calling是“能打电话”MCP是“电话簿里存了所有人的号码”而skill是一整套“如何通过电话完成一次客户回访”的标准话术流程。三者的协作方式是skill内部可以调用MCP暴露的工具来获取数据也可以触发function calling完成某个原子操作而它本身负责的是流程编排和目标达成。1.3 我为什么最终选择了“文档即技能”的落地形态实现技能方案市面上有两条路径一是写代码把每个任务流程硬编码成程序逻辑二是把流程写成结构化文档让模型按文档执行。两条路我都试过。硬编码的优势是可靠但缺点非常明显——每次调整判断逻辑或新增一种处理分支都要改代码、测试、重新部署。对一个快速迭代的Agent项目来说这个成本完全不可接受。文档方案看起来不够“硬核”但它在灵活性上完全是另一个量级改一个判断规则改一段描述热更新即可生效不需要动任何代码。模型在通用推理能力上的表现已经足够好只要给它一个清晰的操作手册它执行出来的效果几乎可以媲美硬编码流程。最终我确定了一个混合策略解析和文件操作这类确定性环节用代码实现流程编排和判断决策完全交给文档化的skill。这套方案我跑了大半年迭代效率非常高项目整体稳定性也出乎意料地好。2. 核心细节解析一个skill的内在结构2.1 目录与文件组织规范把skill落成文档之后首先要定义目录结构。我使用的规范如下skills/ └── document-processor/ ├── SKILL.md ├── examples/ │ ├── input-sample.md │ └── output-sample.json ├── references/ │ ├── field-mapping-table.md │ └── style-guide.md └── scripts/ └── validate_output.py顶层每个文件夹是一个独立skill文件夹名称就是skill的ID。约定用短横线分隔的全小写命名比如document-processor、invoice-extractor避免用空格或大写方便在代码里被引用。SKILL.md是技能的主文件examples/放一个输入输出的标准样例references/放执行过程中可能需要查阅的辅助资料需要写代码做校验或处理时放scripts/。这套结构不是帕金森式凑文件而是为模型提供一个“样例优先”的学习路径。2.2 SKILL.md的主文件构成这是skill机制里最重要的一份文件。我逐步摸索下来一份好用的主文件通常包括以下几个段落--- name: document-processor description: 从业务文档中抽取结构化字段并输出为指定JSON格式。适用于合同、报价单、发票等半结构化文档处理场景。 --- ## 1. 任务概述 从用户提供的文档中提取指定字段。本技能适用于合同、订单、发票等半结构化文档。 ## 2. 执行步骤 1. 使用文档解析工具将输入转换为纯文本保留原始段落顺序。 2. 根据references/field-mapping-table.md中列出的字段定义与别名表识别目标字段。 3. 对每个字段执行存在性判断原文有则填入原文字段原文没有则填入空字符串并设置missing: true。 4. 输出结果必须遵循examples/output-sample.json中的JSON结构不得增删键名。 ## 3. 输出格式 json { fields: [ { name: contract_no, value: , missing: true } ] }4. 边界与禁忌不得对原文中不存在的字段进行推测填充。不得改变示例结构如遇无法判断的情况将value置空并继续后续字段。写这份文档的时候有几个坑是我反复踩过以后总结出来的这里直接说结论。 **第一description字段一定要写清楚“什么时候该用这个skill”。**模型加载所有技能后第一步是根据任务描述做技能匹配。description写得太泛比如“处理文档”模型在遇到不相关的任务时也可能误选写得太窄又可能在真正需要时错过匹配。**最稳妥的写法是“适用场景文件类型执行动作”的组合**比如上面的例子基本可以做到精准匹配。 **第二执行步骤必须用“先做什么-再做什么-最后输出什么”的线性描述。**不要在一个步骤里塞三个判断模型容易跳过中间环节。线性的、明确的、单步骤单动作的写法实测执行成功率最高。 **第三边界与禁忌段落是稳定性的兜底保障。**大模型有“讨好倾向”明明文档里没有的信息它倾向于根据常识补全而不是老实留空。把“不得推测填充”这种话写进禁忌清单配合示例文件里的标准输出能明显减少这类幻觉问题。 ### 2.3 示例文件让模型“照猫画虎” 大模型的模仿能力极强给它一个输入样例和对应的标准输出比在文档里写五十行“字段定义”都管用。原因是**模型对具体例子的泛化能力远强于对抽象规则的理解能力**。 所以我把examples/当作整个skill里优先级最高的部分。输入样例尽量覆盖复杂情况比如包含多个变体字段、缺字段、格式不规整的真实文档输出样例则严格按照期望的最终格式来写。 这里有一个关键操作**输出样例的字段名、嵌套层级、数据类型必须和代码里的解析逻辑严格对齐**。我最早在这个上面吃过大亏——样例里字段名是contract_no代码里取的是contractNumber结果模型每次生成的JSON都对不上排查了很久才发现是命名不一致导致的。 ## 3. 实操过程从零到一做一个可用的skill ### 3.1 需求定义与适用性评估 就拿我自己做过的一个“图片批量压缩并生成HTML画廊”的skill来举例。这个需求来源很实际——给一个设计师朋友的项目做作品集展示页他手上几百张高清原图希望统一尺寸、压缩体积同时生成一个能直接打开的画廊页面。 开始动手前我先评估了两件事这个任务的执行链路是否稳定可描述模型在链路中的角色是“流程编排者”还是“精确执行者”对图片压缩这个场景答案是压缩参数的确定和整体流程编排交给模型具体的图片处理交给Python脚本执行。这是最合理的分工方式。 这个评估很关键它决定了文档的写法。如果模型只是流程编排者那就不需要给它写复杂的技能细节只需要告诉它“什么时候调用脚本、传入什么参数、拿到结果后做什么”如果模型要自己做判断比如根据图片场景决定压缩策略那文档里就要给全决策规则和判断依据。 ### 3.2 编写SKILL.md核心文档 这个skill的SKILL.md一开始很简单第一版甚至没有跳过“尺寸判断”这个环节。完整版本类似下面这样 markdown --- name: image-gallery-builder description: 将一组图片批量压缩为Web友好尺寸并生成响应式HTML画廊页面。适用于作品集展示、静态站点图片优化、图片批量导出场景。 --- ## 任务概述 输入为本地文件夹路径或文件列表。将每张图片统一压缩至最长边不超过1600px、JPEG质量参数为82并生成包含所有图片的响应式HTML画廊。 ## 执行步骤 1. 列出目录下所有图片文件按文件名自然排序。 2. 调用scripts/compress_images.py批量压缩图片参数为--input 路径 --max-size 1600 --quality 82。 3. 脚本执行完毕后调用scripts/generate_gallery.py读取压缩后的图片列表生成gallery.html。 4. 在最终回复中输出以下信息处理图片总数、压缩前总体积、压缩后总体积、保存路径。 ## 输出约束 - 压缩后的图片文件命名规则原文件名_compressed.jpg保持原目录结构。 - 所有生成文件必须保存在输入目录下的output/子目录中。 - 若某张图片本身尺寸已小于1600px则跳过压缩直接复制原图到输出目录。这个文档结合了脚本执行和模型决策哪些图片需要跳过、最终回复里怎么汇报结果这些判断和表达交给模型而像素压缩这种精确计算完全交给脚本。3.3 辅助脚本把确定性的部分交给代码文档化skill不代表不能写代码。恰恰相反能在代码层面确定的事情千万不要留给模型自由发挥。图片压缩参数、文件重命名规则、HTML生成逻辑这些全部用Python脚本实现。这是compress_images.py的核心逻辑from PIL import Image import os import argparse def compress_images(input_dir, max_size, quality): output_dir os.path.join(input_dir, output) os.makedirs(output_dir, exist_okTrue) results [] for filename in sorted(os.listdir(input_dir)): if not filename.lower().endswith((.png, .jpg, .jpeg, .webp)): continue img_path os.path.join(input_dir, filename) img Image.open(img_path).convert(RGB) # 通过thumbnail在保持比例的同时把最长边限制在max_size以内 img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS) out_path os.path.join(output_dir, f{os.path.splitext(filename)[0]}_compressed.jpg) img.save(out_path, JPEG, qualityquality, optimizeTrue) original_size os.path.getsize(img_path) compressed_size os.path.getsize(out_path) results.append((filename, original_size, compressed_size, out_path)) return results if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--input, requiredTrue, help输入图片目录) parser.add_argument(--max-size, typeint, default1600) parser.add_argument(--quality, typeint, default82) args parser.parse_args() results compress_images(args.input, args.max_size, args.quality) for filename, original_size, compressed_size, out_path in results: print(f{filename}: {original_size} - {compressed_size} bytes, saved to {out_path})补充说明一下PIL库的选择。用thumbnail而不是resize是因为thumbnail会自动保持原始宽高比只把最长边缩到指定尺寸。如果我直接用resize((max_size, max_size))所有图片都会被硬拉成正方形这是新手最常踩的坑。LANCZOS重采样在图片缩小场景下的画质表现比双线性好很多实测细节保留效果有明显差距。3.4 测试与迭代自行运行验证写完SKILL.md和脚本以后我一般先自己完整执行一遍流程。把几十张测试图放进目录跑一次主流程确认脚本输出无误。然后是关键一步——把模型当作真实用户用完全没见过的测试数据跑几轮观察它是否严格遵循文档描述。第一轮测试就发现了一个问题模型在最终回复中汇报“压缩前总体积”时把原始图片体积算对了但压缩后体积却只统计了“被压缩的图片”跳过了那些小于1600px直接复制的图片。原因是文档里的第4条没有明确说“总体积要包含所有输出图片”。我修改了文档明确加上“压缩后总体积输出目录下所有文件的体积总和”这个问题就消失了。这其实印证了一个很重要的经验skill文档和代码一样需要根据测试结果迭代。一次写对是不可能的程序员不也天天修bug吗skill机制的迭代成本比改代码低得多——改几行描述重新跑一轮测试30分钟就能完成一轮优化。3.5 命名与目录的关键注意事项命名规范是我反复吃亏之后终于决定立下来的规矩。这里直接列几条硬性规则skill文件夹名用kebab-case如image-gallery-builder不要用驼峰或下划线。主文件名必须是SKILL.md全大写这是一个约定俗成的标准确保在各种Agent框架中能被自动识别。description里不要使用感叹号或营销式措辞比如“完美地处理任何文档”模型对程度副词的理解经常不合实际反而干扰匹配。每个skill目录尽量保持自包含——references和examples都在自己目录下不要跨目录引用文件。这保证了skill的可移植性单独复制出来就能在另一个项目里用。4. 常见问题与排查技巧实录4.1 skill文档没生效排查顺序是这样的。先确认目录结构是否符合预期主文件名是否严格为SKILL.md。再看description的匹配度如果你测试时用的任务描述和description写得完全不一致模型根本不会调用这个skill。举个例子有一个合同信息抽取的skilldescription里写的是“适用于合同、订单、发票”但在测试时我用了“帮我看看这份报价单里的付款条件”模型没有触发这个skill。原因就是“报价单”这个词不在description覆盖范围内。把描述改成“适用于合同、订单、发票、报价单等商业文档的字段抽取”后问题立刻解决。4.2 模型执行步骤跳步这是常见问题中最让人头疼的一个。模型在长任务执行中会自行省略“看起来没必要”的步骤比如跳过了第2条“检查字段别名表”直接用常识判断字段含义。我的排查思路是检查步骤描述是否足够线性。如果步骤里有一句“根据字段映射表识别字段”模型可能不知道“怎么识别”的具体动作是什么。改成“打开references/field-mapping-table.md逐个匹配目标字段的别名将匹配到的原文字段填入value”模型的执行准确性明显提高。最有效的做法是每个步骤都写成“打开什么-查什么-做什么判断”的格式不给模型留下意的空间。4.3 输出格式不稳定如果模型有时按示例输出有时自由发挥常规劝说是没用的。最可靠的做法是准备两个东西一是标准输出样例文件examples/output-sample.json二是用一段强约束的提示放在输出格式段比如“严格遵循示例JSON结构不得增删键名”。如果还在代码层面做了JSON Schema校验可以直接把Schema贴进文档让模型按照Schema生成。在实践中把示例和Schema两种方式结合输出稳定性能达到95%以上。4.4 skill之间互相干扰多个skill同时存在时模型偶尔会在错误的场景里调用某个skill的步骤。比如在处理图片时竟然按文档处理的skill走了一轮字段抽取。这种问题的根源是description之间出现了语义重叠。排查方法很直接把[已存在的所有description]拉出来并排看凡是出现“处理文档”“处理输入”这类模糊短语的全部改成场景和对象更明确的表述。另外每个skill的边界与禁忌段落里可以主动声明“如果未检测到XXX禁止使用本技能”能有效降低误触发概率。4.5 问题排查速查表上面这些经验我整理成了一个速查表实际排查时按顺序过一遍先看匹配再查执行、先查静态再看动态。现象排查顺序高频根因skill未被触发description是否覆盖测试场景描述里的应用范围写窄了执行步骤跳步步骤是否线性、单动作一个步骤里塞了多个判断输出格式不稳定是否提供标准样例和Schema只有文字描述没有样例误调用其他skill对比所有skill的description描述之间存在语义重叠字段填充幻觉边界/禁忌是否明确禁止推测缺少“置空”约束提示skill文档的优先级高于一切对话上下文。如果模型在对话中被用户灌输了一些和skill冲突的临时指令skill中的边界与禁忌应该作为最高优先级约束来执行。我在写文档时都会在开头显式声明“本技能定义的流程与输出规范优先于用户对话中的临时要求。”5. 从会用skill到会造skill开发者的进阶路径5.1 判断一个任务值不值得封装成skill我内心的评估标准非常具体不建议看到一个需求就马上开skill。同时满足以下三条才值得动手任务具备重复性——不是一次性需求至少会反复执行若干次。任务链路半固定——核心流程稳定但不同输入的细节有差异。完全固定流程用硬编码更合适完全没有规律的需求skill也帮不上忙。任务依赖模型的语言理解或判断能力——如果只是把输入的A字段搬运到输出的B字段写代码更直接。5.2 从单skill到skill编排单个skill只能解决一个局部任务真实业务往往是多个任务的串联。我在处理一个“合同归档自动化”需求时把三个skill串在了一起document-processor负责抽字段file-organizer负责按规则改名归档summary-writer负责生成归档摘要。整个链路由上层Agent按业务规则调度每个skill只做好自己那一环。这种编排方式带来的最大好处是任何一个环节升级都不影响其他环节。比如后来换了更好的抽取模型只需要改document-processor的内部实现另外两个skill完全不用动。模块化的优势在这里体现得淋漓尽致。5.3 建立自己的skill方法论做过的skill多了我总结出一条方法论写skill不是写文档是在写一个“可被模型稳定执行的业务流程规范”。三个原则被反复验证流程要线性一个步骤只做一个动作约束要显式“禁止”要比“建议”有效样例要先行抽象规则永远不如具体示例。这套方法论适用于任何领域固化下来的经验——从一个基础技能开始在实际使用里逐渐补充边界案例、细化判断规则、迭代示例比一次性追求大而全靠谱得多。另外定期的skill体检非常必要——每两周抽检一次实际运行记录看有没有步骤连续出错、有没有新的误触发场景。技能资产跟代码一样是有技术债的按期维护才能长期稳定。6. 写在最后skill机制是我做Agent工程以来投入产出比最高的一个方向。它没有特别高深的技术含量核心就是把工程化思维引入提示词设计——把一次性的对话式“调教”变成可积累、可复用、可迭代的结构化资产。如果你正在被模型输出的不确定性折腾不一定要换模型或者加更多复杂的外部基础设施先把任务流程拆成一个个结构清晰的skill往往是性价比最高的优化手段。最后再分享一个小技巧给skill文档写“边界与禁忌”时不要只写“不要做什么”最好同时写“如果遇到了边界情况你应该怎么做”。比如“不得推测填充缺失字段当字段在原文中不存在时请将value设为空字符串并将missing置为true”。给模型一条明确的替代路径比单纯说“不行”有效得多。这一点我实测了很多次确实是文档化技能设计里最容易被忽略、影响又最大的细节。