Agent Skills实战:从提示词堆砌到可插拔技能包设计
不少人问过我你那个跑自动化任务的 Agent 服务系统提示词到底是怎么维护的现在堆了多少字他们以为答案是某个精心调校过的、藏着各种技巧的巨型 Prompt。实际上我几乎把 system prompt 里的“规则”清空了换成一组可以插拔的技能包。这个方案在我们内部仓库里的代号就叫skills。它解决的问题很直接提示词拆成模块之后模型输出更稳了迭代代价大幅下降而且一套技能包可以在不同 Agent 之间复用。这篇文章会把我在设计、实现和排错过程中积累的经验完整写出来。适合两类人看一类是做 Agent 应用、被“提示词越堆越多但效果越调越差”困扰的开发者另一类是虽然目前只写传统业务代码但想搞清楚现在圈子里频繁提到的“Agent Skills”到底是什么、和 MCP 工具有什么区别、以及怎么设计一套真正能用于生产的技能集。我把自己的项目拆开来讲包括目录结构、SKILL.md 元数据设计原则、技能包内部的工作流拆分逻辑以及四个非常容易踩的坑。1. 我为什么从“堆提示词”转向“技能包”——Skills 机制解决的真实问题先说结论系统提示词里能不放的东西尽量不要放。这个结论不是我一开始就得出来的。早期做 Agent 服务时我也干过特别蠢的事——为了让它写出符合团队规范的前端代码我一次性把编码规范、组件命名规则、分支管理流程、注释格式要求全部塞进 system prompt。大概写了将近两千字以为信息越多模型执行越准。1.1 提示词的不可复用性和上下文膨胀实际跑起来之后问题接踵而至。首先是上下文空间被大量挤占。我的 Agent 处理的任务链通常包含读取文件、分析需求、生成代码、自检、提交变更说明等多个环节每一步都需要模型 comprehension 大量上下文。两千字的规则说明直接挤掉了一部分原本可以用来放代码和文件内容的空间。更麻烦的是当我把同一套 Agent 切换到“数据处理”任务时这些前端规范不但毫无用处还会形成指令干扰。模型在处理数据分析时偶尔会因为残留的前端规则把返回格式强行变成带组件标签的 Markdown非常恼火。其次是迭代成本过高。每次要改某一条规则比如把“组件默认导出”改成“命名导出”我都要重新跑一遍所有测试场景。你永远不知道这条改动会在哪个环节影响模型的判断。那种感觉就像在一个抽屉里翻了半天也找不到想要的那把改锥明明抽屉里塞满了工具可用的却只有那么几把。这里我想先做一个通俗类比。传统堆提示词的方式等于把所有工具都堆在同一个抽屉里每次干活都要翻找干扰项多还容易拿错。而 Skills 机制更像一套标准化工具箱每个工位里有明确的操作手册、专用工具和检查清单。干活之前根据任务类型选一个工位其他工位的东西根本不会出现在当前操作台上。This is the essence of skills——按需加载、用完即走。1.2 技能包的核心思路与适用边界那么技能包到底是个什么东西用一句话概括它是一组把“触发条件、执行步骤、领域知识、关联资源”打包在一起的、可复用的功能模块。模型在判断当前任务匹配某个技能包之后才加载对应的说明与资源平时这些内容不进入上下文。这是它与“把指令写进 system prompt”最本质的区别。我当时还做了一次很关键的区分技能包不是插件也不是工作流引擎。它和 MCP 工具的关系经常被人搞混。我自己是这么理解的MCP 解决的是 Agent 怎么访问外部能力比如读写文件、调用数据库、操作浏览器它是 Agent 的“手”Skills 解决的是 Agent 对某一类任务怎么做才稳定比如怎么审查代码、怎么分析一份 CSV、怎么生成结构化的 PR 描述它是 Agent 的“操作手册”。手和手册各有分工实际项目里它们通常配合使用技能包描述流程和规范MCP 提供执行能力。这个区分让我在设计时想清楚了一个原则技能包不应该试图自己实现所有功能它只需要把任务拆解清楚、把质量关卡住真正动手执行的部分交给宿主 Agent 的基础工具链。这个过程我在第三节具体讲。2. 动手设计第一套技能包目录结构与元数据规则第一套被我命名为fe-coding-assistant的技能包专门负责前端组件生成与修改。当时我没有直接参考某个现成框架而是按自己的使用习惯设计了一套目录结构后来发现这个结构和 Claude 官方推荐的 Skills 结构高度一致算是不谋而合。2.1 SKILL.md 的 frontmatter 怎么写才能匹配得准一个技能包的核心是SKILL.md文件它相当于这份操作手册的封面和执行摘要。模型先读到这个文件再决定要不要加载技能包、以及怎么调用它。目录结构一开始长这样skills/ fe-coding-assistant/ SKILL.md assets/ guidelines.md examples/ component-before.tsx component-after.tsx templates/ pr-description.md scripts/ self-check.sh test_cases/ case-01-search-filter/ case-02-table-export/SKILL.md顶部的 frontmatter 是整个技能包的灵魂尤其是description字段。它决定了模型在什么场景下会想到这个技能包。我最开始写的是--- name: fe-coding-assistant description: 前端编码助手帮助生成和修改前端代码。 ---这种描述太弱了。模型根本分不清什么时候该调它。后来我改成了下面这种写法--- name: fe-coding-assistant description: 当用户需要生成或修改前端页面、React 组件、表单交互、列表页时使用。输入应包含页面需求或现状代码输出应包含组件代码、依赖清单、验证方式。不适用于纯后端逻辑、数据库设计。 ---区别在哪里我给模型提供了三个维度的判断依据触发场景React 组件、表单、列表页、输入要求要有页面需求或现状代码、输出承诺生成代码与验证方式同时写清不适用范围。这样模型在做语义匹配时可以快速确认“现在这个任务是不是该用这个包”。我的切身体会是描述里不要堆形容词但一定要明确边界。2.2 元数据字段的扩展与资源组织策略除了 name 和 description我在实践中逐步加上了requires、when_to_use和output_format这样的字段。后两个字段虽然没有被所有平台原生支持但写在 SKILL.md 正文里模型照样能读到并遵循关键是要保持位置固定、格式清晰。我的习惯是在 frontmatter 里维护结构化字段在正文里用一个大标题专门写when_to_use避免模型漏看。关于资源文件我也趟过一段弯路。第一版技能包里我把完整的编码规范拆成 20 条细则直接贴在 SKILL.md 正文里结果模型加载后经常“只见细则不见任务”输出变得非常僵硬。后来我改成把详细拆分成assets/guidelines.md在 SKILL.md 正文中只写一句话“组件命名、文件组织、状态管理规范见 assets/guidelines.md仅在生成代码前查阅。”这样模型默认不会读额外资源但当它真正需要判断命名是否合规时知道去哪查。这个“按需读资源”的设计让我在上下文占用上省下了大量空间。做个总结我的 SKILL.md 实际上是由两部分组成的frontmatter 负责匹配正文负责执行路径。匹配部分要精确到“什么任务触发”和“什么任务不触发”执行路径部分要把动作拆到模型不需要临场发明流程的程度。前几百字决定技能包是否被叫到后面每句话都在影响执行质量。3. 技能包里的执行逻辑工作流拆分、工具调用与结果校验很多人在设计技能包时容易把它当成了一个“大号提示词模板”写一段话让模型照做。但如果只是这样它和普通的 few-shot 示例没有本质区别稳定性还是不够。我真正开始觉得技能包设计是一门手艺是从拆工作流开始的。3.1 把任务拆成步骤让每一步责任单一fe-coding-assistant这个技能包我最初的定义是“帮用户生成组件代码”。这太宽泛了模型可以从需求直接跳到代码中间没有任何质量闸口。后来我按自己的开发流程把它拆成了五个明确步骤需求澄清把用户的模糊描述转成结构化需求列出组件功能点、输入输出、交互状态。方案选择判断应该新建组件还是修改现有组件如果修改说明影响面。组件生成按规范产出核心代码关键逻辑加注释。自检清单对照规范检查命名、State 边界、事件处理是否正确。输出变更说明生成人话版的改动摘要包括文件列表、依赖变化、验证方式。每一步我都注明了“输入是什么、要做成什么样、不要做什么”。比如方案选择这一步我明确写了“不要在没有确认数据接口之前开始写代码”。这个要求看似简单但当我没写它时模型会经常性先写代码再问接口最后代码整段返工。拆步骤的核心目的是让每个步骤的责任边界足够小小到模型不需要“临场发挥”就能完成。3.2 工具调用与技能包解耦避免技能包变成单体脚本技能包很容易犯的一个错误是把具体的工具调用方式写死在步骤里。我在早期版本里干过这种事比如在 SKILL.md 中直接写“用 filesystem 工具读取 src/components/user-list.tsx”。看着挺具体结果项目目录一重构文件路径变了技能包全部失效。后来我调整了设计原则技能包描述“需要什么能力”但具体能力由宿主 Agent 提供。比如我会在requires字段中声明需要“文件读取”“文件写入”“终端执行”三类能力而不是绑定某个命名工具。执行时通过参数把具体的文件路径传进来。这种解耦思路借鉴了接口设计里的依赖倒置原则技能包依赖抽象能力宿主提供具体实现。顺带说一下这也解释了为什么技能包和 MCP 不冲突MCP 提供具体能力实现技能包消费这些能力。两者通过一套稳定的接口约定对接而不是彼此写死。3.3 结果校验的兜底设计这一节是我最想强调的技能包执行完之后不能只输出一段自然语言就结束。必须要求模型输出结构化、可被宿主程序校验的结果。我要求fe-coding-assistant在最后一步输出一个固定格式的变更摘要比如summary: files_changed: - src/components/user-list.tsx dependencies_added: [] verification: npm run lint npm run test checklist: naming_ok: true states_handled: true edge_cases_noted: false这个 YAML 块会被宿主的轻量解析器吃进去做三项检查文件是否存在、验证命令是否非空、checklist 里有没有 pending 项。如果没有这个结构技能包执行得再怎么天花乱坠后续自动化步骤也无从接续。本质上这一步是把技能包从“聊天式输出”变成一个“可编程调用单元”。4. 实测中踩过的四个坑上下文、权限、嵌套与故障定位把技能包方案落地到真实项目最耗时间的不是设计阶段而是排错阶段。这一节我把踩过的四个典型坑原原本本列出来每个坑都附上根因和解决办法。这些问题在官方文档里基本不会有但它们才是决定一个技能包能否真正用于生产环境的关键。4.1 技能包把上下文撑爆了第一版“数据分析技能包”里我为了让我写的 Agent 能快速理解数据库全貌把 20 张表的建表语句都放进 assets 并让它加载。结果一调用就经常报超上下文错误模型生成到一半会因为 context 不够开始“选择性失忆”输出内容错漏百出。根因很简单我把“高价值信息”默认为“每次都要用的信息”。实际上这 20 张表里一次任务真正涉及的可能只有 2-3 张。解决思路是调整资源组织方式在技能包的 assets 里只放了一张表清单概览表名、用途、关键字段真正要建表语句时要求 Agent 用数据库查询工具去找information_schema。改完之后上下文占用下降了 70%效果稳定很多。这也让我总结出一个资源放置原则真正每次都会用到的小而精的内容才内联在 SKILL.md偶尔用到的大块内容走引用查取只有特殊场景会用到的内容应该完全放在技能包之外按需检索。4.2 多个技能包同时活跃时的工具参数冲突我的 Agent 有时需要同时挂载“前端编码助手”和“技术文档撰写助手”。单独跑都没问题但一旦并行执行两个技能包都要写文件而它们对工具参数的描述不一致前端技能包要求传path文档技能包要求传file_path。模型在切换上下文时经常把参数名写错导致工具调用失败。我试过在 SKILL.md 里强行统一描述措辞但治标不治本。最终的方式是在宿主层做统一约定所有技能包的requires字段里对同一类工具的调用参数名称必须一致如果某个技能包需要特殊参数通过执行参数而非自然语言描述来传。这个约束让我避免了一大类由于模型乱起变量名导致的低级故障。4.3 技能包嵌套调用时的输出格式冲突更隐蔽的一个坑出现在技能包互相调用的场景。我设计过一个“代码审查技能包”它内部会调用“前端编码助手”来分析代码。外层的审查技能包要求所有输出为 JSON内层的编码助手默认输出 Markdown。结果就是外层把 Markdown 当 JSON 解析报错报得非常莫名其妙。这个坑的本质是技能包之间传递数据时缺少显式的接口契约。解决办法是在 SKILL.md 正文里增加一个可被外层覆写的参数output_format默认值是 markdown但被嵌套调用时外层通过入参把它强制改为 json。我后来设计所有技能包时都会预留这个参数把“被调用时的输出格式”和“独立执行时的默认格式”分开。如果你打算设计多层嵌套的技能包请一定在第一天就把输出契约确定下来。4.4 排障时无法判断问题出在技能包还是模型最后一个坑也是最难定位的技能包效果时好时坏。经常是同一段输入上周跑得好好的这周突然开始跳步骤。因为没有日志你根本分不清是 skill 描述写得有歧义还是模型抽风、工具超时、还是外部依赖变了。我的做法分两步。第一每个技能包在执行开始时生成一个 trace_id宿主把模型每一步的决策输出和工具调用结果都记录下来独立保存成运行日志。第二给每个技能包维护一个测试套件跑固定 mock 输入对比当前输出和基线输出的差异。定位问题的时候先把日志拉出来看模型在哪个步骤偏离了预期路径再回到 SKILL.md 看那一步描述是否足够明确。这套流程基本帮我锁定了 80% 的问题源头。5. 设计一套可复用技能包的五个原则踩了这些坑之后我把自己的经验沉淀成了五个设计原则。现在每次写新的技能包都会拿这五条过一遍。5.1 一个技能包只负责一个动词技能包最怕变成“万能助理”。我最初差点做一个叫“辅助开发”的大杂烩技能包后来果断拆成了fe-coding-assistant、fe-code-reviewer、test-generator三个。动词越具体description 匹配越准调试也越容易。归纳起来就是一句话如果技能包的名字里可以拆出两个动词就该考虑拆成两个包。5.2 显式声明依赖不要隐式假设在 SKILL.md 里把需要的能力、资源、参数全部写清楚。我曾经写过一个技能包隐式假设了 Agent 拥有访问某个内部 API 的能力结果换个环境跑就直接断掉。后来我在requires里显式声明了这个依赖宿主在加载技能包时就能先做检查能力不满足就直接提示而不是让模型跑到一半才报错。5.3 给模型留出“拒绝执行”的出口这一点很多人忽略。我在技能包里加了一条固定语句“当输入内容与上述触发条件显著不符时停止执行并输出 NEED_MORE_INFO。”不要小看这一行它给了模型一个合法出口避免它为了完成任务硬套流程产生幻觉式的错误操作。引入之后我技能包的误操作率降得肉眼可见。5.4 轻量资源优先重资产走引用判断一份资源是“内联”还是“引用”我用的三个标准是使用频率、体积、必然使用概率。举几个例子资源类型使用频率体积放置方式状态码字典高小内联进 SKILL.md编码规范全量文档中大放在 assets按需查阅完整数据库 Schema低大外部存储按需检索内联的资源必须小而精超出这个范围的资源一律走引用。每次看到有人把几百 KB 的说明文档塞进 SKILL.md我都想摇着他们的肩膀喊一句上下文空间真的很宝贵。5.5 用真实任务做回归测试而不是手工验证技能包本质上是一种“可迭代的程序”那它就应该有“回归测试”。我的做法是在test_cases/目录下维护 3-5 组真实任务输入和期望输出。每次修改 SKILL.md就跑一遍全部用例不只是看最终输出还关注中间步骤有没有跳步、有没有多执行多余动作。有一点建议测试用例里不能只放成功的输入还要放失败输入。比如一个不完整的需求、一个明显不属于这个技能包的任务。失败用例能让技能包学会“不做什么”这对稳定性的提升很多时候比成功用例还要关键。目前这套方案已经在我们内部稳定运行了挺长一段时间。如果非要总结一个最实在的经验我想说的是不要把 Agent 的技能包理解成更多提示词的组合它更接近一份“带资源引用的操作标准”。按这个思路去设计你会发现技能的复用性、稳定性和可测试性都会提升一个台阶。如果你正在被自己的 system prompt 膨胀困扰不妨从最小的一类任务开始试着把它拆成第一个技能包然后让这套机制替你做减法。