1. 从单一 Prompt 到 Skill 仓库Agent 为什么必须拆能力我发现很多团队做 Agent 的第一版都是把需求一股脑塞进系统 Prompt角色设定、工具列表、流程步骤、输出格式、行业背景、禁忌事项……开头还收敛得住等需求迭代几轮Prompt 膨胀到几千字甚至上万字问题就接踵而至。我所在的团队去年接手一个偏重业务的 Agent 项目最初那份系统 Prompt 将近八千字。每次改一句话都要小心翼翼因为模型经常被前后矛盾的指令搞糊涂回答一会儿遵循旧逻辑、一会儿遵循新逻辑。更麻烦的是上下文预算被大量无关指令占用明明模型能力不差实际表现却越来越笨。后来我们基本推倒重做把能力拆解成一个个独立、可复用、按需加载的 Skill项目代号就叫 agent-skills。这一套东西后来被验证是 Agent 工程里性价比最高的一次重构推理质量上去了迭代速度上去了连测试都变得容易了。这篇文章就把我们设计、实现、维护这套 Skill 体系的完整过程讲清楚适合正在被单体 Agent Prompt折磨的开发者参考。所谓 Skill本质就是一组能力封装。它把完成某个任务的指令、可选脚本、元信息、使用边界打包成一个单元Agent 在运行中根据当前任务判断是否需要加载它。这个思路跟人脑很相似你不会在写代码的时候把如何煎牛排的说明书一直挂在脑子里只有真的要做饭时才会调出对应的步骤。Agent 也一样把能力按需加载才能把宝贵的上下文留给当前任务本身。所以这篇文章不是讲某个具体框架的 API 调用而是讲 Skill 体系从零到一的设计决策和工程实践。我会把目录结构、指令编写、触发边界、测试方案、版本管理这几个关键环节逐一拆开附上直接的配置示例和我们踩过的坑。2. Skill 目录的第一版结构元信息、加载机制与命名约定2.1 文件夹即 Skill一个目录装一个能力我们最早的设计原则很朴素一个 Skill 对应一个文件夹文件夹里必须有描述文件有脚本就放脚本没有脚本就只放描述。这个约定虽然简单但带来的好处是巨大的——所有 Skill 的形态统一遍历目录就能生成索引加一个新能力不用改任何框架代码。一个典型 Skill 文件夹长这样skills/ fetch_page/ SKILL.md fetch.py requirements.txt summarize_doc/ SKILL.md query_kb/ SKILL.md search.py如果一个 Skill 只是告诉模型该怎么做那它完全可以只有一个 SKILL.md比如总结类、写作类的 Skill 通常不需要额外脚本。但如果 Skill 涉及外部系统的调用、数据解析、格式转换就需要配套脚本。这里有个经验脚本尽量做成无状态的命令行工具输入输出都用标准输入输出或临时文件传递这样 Agent 调用脚本就跟调用系统命令一样不需要框架层面做复杂的进程管理。2.2 SKILL.md 的元信息字段设计SKILL.md 是整个 Skill 的入口模型读不读得懂、会不会在正确时机调用它全看这份文件写得好不好。我们用 YAML frontmatter 放元信息后面跟正文指令。--- name: query_kb description: 检索本地知识库返回与问题相关的段落及来源文档。用于回答需要引用内部资料的问题。 when_to_use: 用户问题涉及内部文档、产品手册、历史决策记录或者需要给出带出处的回答时。 version: 1.3.0 dependencies: [] tags: [kb, retrieval] ---字段设计上最关键的是 description 和 when_to_use。这两个字段直接参与触发判断写得好不好决定了模型会不会在错误的场景调用 Skill、或者在正确的场景漏掉 Skill。要特别注意 description 里别写笼统的处理用户问题这种废话要写清楚这个 Skill 能做什么、不能做什么、典型输入长什么样。2.3 加载机制全量注入与摘要路由的取舍Skill 数量少的时候把所有 SKILL.md 直接塞进系统 Prompt 是没问题的。但 Skill 一多——比如超过二十个——上下文预算就吃紧了而且模型面对一堆候选也容易选错。我们后来做了两阶段的加载机制第一层是索引层。系统 Prompt 里只放每个 Skill 的一句话摘要类似于查询知识库提取网页正文按模板生成周报。模型先根据这句话判断要不要调用某个 Skill如果决定调用框架再加载这个 Skill 的完整 SKILL.md 和脚本。第二层是缓存层。如果 Agent 在同一个会话里多次命中同一个 Skill完整定义只需要加载一次后续命中直接复用不用重复注入。这个优化对长对话场景特别重要我们实测能让上下文消耗降低 30% 到 40%。这个机制就是在模型自主判断和工程强制路由之间找了个平衡点。纯靠模型从一堆完整文档里选选错率高纯靠人工规则路由又失去了 Agent 的灵活性。摘要路由是当前工程上最稳的折中方案。3. 编写 Skill 指令的四个硬约束别让模型自由发挥太多3.1 指令要写步骤不要写目标很多人在写 Skill 指令时犯一个错只告诉模型要达成什么目标不告诉它具体怎么走。比如写一个总结文档的 Skill只写请总结这篇文档的要点模型就会自由发挥——有时候输出三段式有时候输出列表有时候洋洋洒洒写一页。我们的做法是强制指令里包含明确的操作步骤把自由度收敛到一个可接受的范围。拿总结类 Skill 举例请按照以下步骤总结文档 1. 先阅读全文识别文档类型技术方案、会议纪要、产品说明等。 2. 提取核心结论最多列出 5 条每条控制在 50 字以内。 3. 列出存在的风险或未决问题若无则写明无。 4. 输出格式为结论列表 风险列表不要输出其他内容。步骤写清楚之后输出的稳定性大幅提升。关键在于步骤是给模型看的操作手册而不是给用户看的验收标准。两者的区别在于操作手册写的是过程验收标准写的是结果。过程越明确模型的行为偏差越小。3.2 边界条件比主流程更重要一万次调用里九千次走主流程九百次走分支还有一百次是属于模型自己都不知道该怎么处理的情况。很多 Skill 写崩不是主流程有问题而是边界条件没有写清楚。我们后来在每个 SKILL.md 里都固定加了一段什么情况下不要使用本 Skill的说明。这个反直觉的字段实际作用非常大。比如 query_kb 这个 Skill我们会写明如果用户只是闲聊、不需要引用内部资料不要调用本 Skill如果问题是关于代码逻辑的请使用代码分析类 Skill。另一类边界是输入格式。模型经常会把格式错误的输入直接丢给脚本导致脚本报错。处理方法是在指令里明确要求模型调用脚本前先检查输入格式并给出格式错误的兜底策略——是尝试修复还是直接向用户澄清。没有这个兜底Skill 一遇到脏数据就整体崩掉。3.3 上下文预算指令能短则短脚本能省则省我们会定期统计每个 Skill 的加载开销也就是它被加载时占用的 token 数。开销超过一定阈值就要做瘦身。瘦身的方向有两个第一是压缩指令文本把冗长的解释性文字删掉只留行为约束第二是把指令里的复杂逻辑挪到脚本里让模型只做决策把计算交给代码。比如一个翻译 Skill早期我们会在指令里写一大堆术语对照表后来发现模型根本记不住而且对照表本身还占上下文。把术语表放进一个 data.json由脚本在翻译前自动查表注入效果立刻好了很多——模型只需要根据用户输入决定是否调用这个 Skill至于术语怎么翻让脚本通过 few-shot 示例来控制。这个拆分的本质是模型擅长的是语义理解和意图判断代码擅长的是确定性的映射和计算Skill 设计就是尽量把这两类任务分开各自交给更擅长的一方去做。3.4 Skill 之间的依赖宁可重复不要耦合Skill 多了以后一定会出现这个 Skill 内部想调用另一个 Skill的需求。我们的经验是绝大多数情况下都不要让 Skill 之间产生直接依赖。原因很实际一旦 Skill A 依赖 Skill B升级 B 的时候必须测 A而 B 可能有自己的触发条件A 调用 B 时还要绕过触发判断链路一复杂调试难度直线上升。我们的做法是把公共逻辑抽取成共享脚本库。比如好几个 Skill 都要做文本清洗那就建一个 common/text_utils.py谁都可以 import但彼此之间不互相调用。Skill 的依赖关系只允许Skill 依赖共享工具不允许Skill 依赖另一个 Skill。规则很简单维护成本显著降低。4. 从本地冒烟到回归评测Skill 测试体系的搭建过程4.1 一个 Skill 一个测试夹用真实输入做冒烟大多数团队给 Agent 写测试时很敷衍甚至完全不写。但我们发现Skill 体系天然适合测试因为每个 Skill 的输入输出边界比单体 Agent 清晰得多。我们把一个 Skill 一个测试夹定为硬性规范。测试夹里放三类文件样例输入可能是一段用户问题、一个文档路径、一条查询、期望输出模板不追求逐字逐句匹配而是结构化字段匹配、以及必须避免的负面案例。比如 query_kb 的负面案例是用户明明在问一个不需要检索的问题模型却调用了检索 Skill这就意味着触发逻辑有问题。冒烟测试的目标很简单每次改动一个 Skill先跑本地冒烟确保主流程能走通、输出格式正确。凡是脚本改动必须能通过对应的单元测试凡是指令改动必须人工过一遍冒烟用例确认模型行为没有退化。这一步能挡住大部分低级错误。4.2 评测不只看结果还要看调用过程只测最终输出对不对是远远不够的。Agent 系统的核心风险在于调用决策也就是模型在什么时候调用哪个 Skill。因此我们的评测会记录完整的调用轨迹包括每一步的触发判断、加载了哪些 Skill、传入了什么参数、脚本返回了什么、模型最终输出什么。评测指标分三层任务完成率结果对不对、路径正确率该调用哪个 Skill 时是否调用了它、资源开销加载了几个 Skill、消耗了多少 token。有些输出结果碰巧正确的测试用例在路径正确率上可能不过关——比如模型不该调用检索却调用了结果因为运气好也答对了。这类问题必须通过路径指标发现只看最终结果一定漏掉。我们在评测集里专门设计了干扰项给模型一大堆它其实用不上的输入字段、相似的选项、多余的附件考察它能不能沉住气不调用不该调用的 Skill。这个测试对抑制模型乱操作特别有效很多模型在干扰场景下会过度调用工具而加了干扰项评测之后我们会特意收紧 when_to_use 的描述让模型学会克制。4.3 回归测试随指令走每次改动都重跑业务类 Agent 的评测有个特点改一点可能会影响一片。改了一个 Skill 的触发条件另一个 Skill 的调用频率可能跟着变。所以我们规定任何 SKILL.md 的改动都必须触发整库的回归评测而不是只测被改的那个。回归评测跑一轮很快因为大多数用例是离线跑完的。我们把用例编排成 JSON 文件统一驱动测试跑完后输出对比报告哪些用例通过、哪些失败、失败原因集中在哪一类。有了这个基线改指令就敢放手改了因为任何一处退化都会被报告撂在明面上。注意Skill 的指令修改有个非常隐蔽的风险——模型版本升级后同样的指令表现可能会变。我们每个季度会全量重跑一次回归基线确保当前模型版本下没有隐性退化。模型大盘一换旧评测结果就当参考必须重测。5. Skill 仓库的工程化管理版本、索引与跨 Agent 复用5.1 用语义化版本管理 Skill 演进Skill 的改动频次比普通代码高因为指令调优是持续性的。我们把每个 Skill 的版本号写进 frontmatter遵循语义化版本规则主版本号变更意味着行为重构一定概率引入不兼容变化次版本号变更意味着新增了可选项或扩展了覆盖场景补丁版本就是文字微调、示例更新这类不影响行为的小改动。版本号不是给人看的是给框架和测试体系看的。发布流程里框架把版本号写进加载日志和测试报告一旦线上表现异常能快速定位到是哪个 Skill 的哪个版本在生效。我们还搞了一个版本锁定机制线上 Agent 固定使用某一批 Skill 版本新版本先在预发环境跑回归跑完再整体替换避免边上线边出问题。5.2 自动生成 Skill 索引比手写 README 靠谱Skill 数量超过三十个之后靠人记是记不住的所以我们写了个脚本扫描全部 SKILL.md 的 frontmatter自动生成索引页。索引页包含每个 Skill 的名称、一句话摘要、版本号、标签、最后修改时间。这个索引既是给模型做摘要路由用的也是给团队成员当目录用的。摘要路由用的索引和给人看的 README要分开。给模型的索引条目要短每条约 30 个字内只保留触发判断所需的信息给团队看的索引可以详细一些包含设计思路、维护人、受影响的 Agent 列表。两套索引都自动生成这样就不用担心文档和实际代码不同步。索引自动化的另一个好处是能快速发现孤儿 Skill——长期没有被任何用例覆盖的 Skill。这类 Skill 大部分是早期试验留下的跑在线上但没人清楚它到底有没有用处。我们每个季度用调用统计筛一次孤儿直接下架保持仓库干净。5.3 多 Agent 共享同一套 Skill统一底座单独覆盖我们内部有十几个 Agent各自负责不同的业务场景。最开始每个 Agent 用一套独立的 Skill后来发现大量逻辑是重复的——都要检索、都要总结、都要格式化输出。于是我们把 Skill 分成两层公共层任何 Agent 都可以加载必须保证通用、稳定专用层只服务特定场景允许写得更激进、更定制。跨 Agent 复用的收益很直观修一次公共层问题所有 Agent 一起受益。但代价是要控制公共层的改动频率和兼容性。我们的约定是公共层 Skill 的版本升级不能静默进行要发变更通知列出受影响的 Agent 清单每个 Agent 的负责人确认无回归后才能替换。这套流程看起来重实际跑顺了之后反而省心——因为冲突都在流程里提前消化掉了不会在线上炸出来。6. 让我最后再唠叨几句Skill 体系常见的坑这套体系我们跑了大半年踩过的坑不少最后挑几个最典型的说一下。第一个坑是Skill 命名太抽象。早期我们有几个 Skill 叫 helper、utils、processor模型在触发判断时经常搞不清它们的边界。后来所有 Skill 命名必须体现动作加对象比如 fetch_page、query_kb、summarize_doc。命名清晰了触发准确率立刻上一个台阶因为模型对这个 Skill 到底是干什么的有了直觉。第二个坑是过度设计触发逻辑。我们一度尝试用分类模型做路由给每个 Skill 训练一个分类器。折腾了两个星期效果还不如直接靠模型自带的语义理解。核心原因在于 Skill 数量少的时候分类器是过度工程等 Skill 数量上百分类器又不够灵活新增 Skill 还要重新训练。反而是模型自判断 摘要路由 人工反馈修正这套轻量方案最好用随着用例积累持续微调 when_to_use 的描述就够了。第三个坑是脚本异常没有兜底。早期脚本一旦报错Agent 整个对话就尴尬地停在那里。后来我们给所有脚本调用套了一层降级策略脚本挂了Skill 要能退化为纯指令模式或者明确告诉用户当前能力不可用。记住Skill 体系的健壮性取决于最差情况下的表现而不只是正常情况下的表现。最后想说的是Skill 化改造不是一次性的项目而是一个需要持续投入的工程习惯。每次线上出了问题复盘时多想一步这条经验能不能沉淀进某个 Skill 的指令里时间长了仓库就会越来越厚实。我现在的体会是Agent 的能力上限不取决于模型多强、框架多花哨而取决于你积累了什么样的 Skill 资产——这是从做 agent-skills 这个项目以来最值钱的一条心得。