1. 从“长提示词”到“技能包”的认知转变
1.1 一个被反复验证的失败模式
过去一年多,我参与过好几个智能体项目的落地,从客服问答到内部流程自动化都有涉及。几乎每一个项目在早期都会经历同一个阶段:为了让智能体表现得“更聪明”,团队会把所有能想到的规则、示例、边界条件、输出格式全部塞进系统提示词里。一开始效果确实不错,模型在简单场景下表现得很听话。但随着需求不断叠加,提示词从几百字膨胀到几千字,再到上万字,问题就开始集中爆发了。
最典型的表现是:模型开始“遗忘”早期的指令。你明明在提示词开头写了“所有金额必须保留两位小数”,结果对话进行到第五轮,它输出的金额变成了整数。你写了“禁止编造不存在的订单号”,它依然会在用户追问时凭空生成一串数字。更麻烦的是,每次新增一个功能,都要回头检查旧规则有没有被新规则覆盖或冲突,维护成本呈指数级上升。
这个失败模式让我意识到一个根本问题:长提示词本质上是一种“全量加载”策略,它假设模型能在每一次推理中均匀地关注到所有指令,但现实是模型的注意力是有限且不均匀的。提示词越长,单条指令被“淹没”的概率就越高。这不是模型能力的问题,而是架构设计的问题。
1.2 SKILL.md 带来的结构性解法
后来接触到 Agent Skills 这套思路,核心变化在于把“一个巨大的提示词”拆解成“一组按需加载的技能模块”。每个技能用一份 SKILL.md 来描述,里面包含这个技能的触发条件、执行步骤、输入输出规范、边界约束和示例。智能体在运行时,先根据当前任务判断需要哪些技能,再把这些技能的 SKILL.md 加载进上下文,而不是一次性把所有技能都塞进去。
这个转变看起来只是“拆分文件”,但实际影响远不止于此。它解决的是三个层面的问题:注意力聚焦、维护解耦、权限隔离。注意力聚焦让模型每次只处理与当前任务相关的指令,减少干扰;维护解耦让每个技能可以独立迭代,不会牵一发而动全身;权限隔离则让不同技能拥有不同的操作边界,避免一个技能的错误扩散到整个系统。
我个人的体会是,SKILL.md 更像是一份“操作手册”而不是“系统提示”。操作手册的特点是:需要的时候翻到对应章节,不需要的时候放在书架上。而系统提示更像是贴在墙上的规章制度,不管你有没有用,它都在那里占着视觉空间。
1.3 谁适合参考这套思路
如果你正在做智能体应用,尤其是那种需要处理多类任务、多个流程、多种输出格式的场景,这套思路会很有帮助。比如内部工单处理、多步骤数据分析、跨系统操作编排等。如果你只是做一个简单的问答机器人,可能长提示词就够用了,不必过度设计。
但如果你已经遇到了“提示词越写越长、效果越来越差”的困境,或者团队里多个人同时在改同一份提示词导致冲突不断,那么按需加载和权限边界的思路值得认真考虑。它不一定能解决所有问题,但至少能把“混乱”变成“可控的复杂”。
2. SKILL.md 的核心结构与设计逻辑
2.1 一份 SKILL.md 应该包含什么
我见过不少团队把 SKILL.md 写成了“迷你提示词”,只是把原来的一大段拆成了几个小段,本质上还是描述性的自然语言。这样做虽然比长提示词好一点,但没有发挥出技能包的真正价值。一份合格的 SKILL.md,应该包含以下几个核心部分:
- 触发条件:什么情况下应该加载这个技能。可以是关键词匹配、意图分类结果、上游技能的输出标记等。
- 前置依赖:执行这个技能之前需要哪些上下文或工具可用。比如需要数据库连接、需要某个API的访问权限、需要前一步的输出结果。
- 执行步骤:具体的操作流程,每一步做什么、调用什么工具、传入什么参数、预期输出是什么。
- 输出规范:结果的格式要求,包括字段名、类型、取值范围、示例。
- 边界约束:这个技能不能做什么,哪些操作需要额外确认,哪些输入需要拒绝。
- 失败处理:当步骤执行失败或输出不符合预期时,应该回退到哪一步,或者向用户请求什么信息。
- 示例:至少一个完整的输入输出示例,帮助模型理解期望的行为。
这些部分不需要全部写满,但触发条件、执行步骤和边界约束是必须的。我见过最精简的 SKILL.md 只有这三部分,依然运行得很好。
2.2 为什么触发条件比内容更重要
很多人写 SKILL.md 时把大量精力花在“步骤写得多详细”上,却忽略了触发条件的准确性。实际运行中,技能加载错了比技能内容写得粗糙更致命。如果触发条件过于宽泛,不该加载的技能被加载进来,会占用上下文空间,还可能干扰模型判断;如果触发条件过于严格,该加载的技能没加载,模型就会“裸奔”,只能靠通用能力硬扛,结果往往不可控。
我的经验是,触发条件应该尽量用“可判定的信号”而不是“语义描述”。比如“当用户询问订单状态时”就是一个语义描述,模型需要理解“订单状态”是什么意思;而“当意图分类结果为 order_status 或输入中包含订单号格式的字符串时”就是一个可判定的信号,更容易稳定触发。
当然,不是所有场景都能做到完全可判定。折中方案是:先用语义描述做初筛,再用一个轻量级的分类步骤做确认。这样既保留了灵活性,又增加了稳定性。
2.3 按需加载的三种实现层次
按需加载不是只有一种做法,根据系统复杂度和团队能力,可以分三个层次来落地:
第一层:手动选择。在对话开始前,由用户或上游系统指定需要哪些技能。比如工单系统根据工单类型,直接告诉智能体“这次需要 order_query 和 refund_policy 两个技能”。这种方式最简单,可控性最强,但灵活性最差,适合流程固定的场景。
第二层:规则匹配。根据输入内容的关键词、正则表达式或简单分类模型,自动决定加载哪些技能。比如检测到“退款”关键词就加载退款相关技能。这种方式实现成本低,但规则需要不断维护,容易漏判或误判。
第三层:模型自决策。让模型先看一遍所有技能的摘要列表,然后自己决定需要加载哪些。这种方式最灵活,但对模型的判断能力要求高,而且摘要列表本身也会占用上下文。我的建议是,如果技能数量超过二十个,就不要让模型自己选了,先用规则缩小范围,再让模型在候选集里做选择。
实际项目中,我通常采用“规则匹配 + 模型确认”的混合模式:规则先筛出一批候选技能,模型再根据当前任务确认最终加载哪些。这样既控制了上下文长度,又保留了一定的灵活性。
2.4 权限边界不是可选项
权限边界是 SKILL.md 里最容易被忽视、但出事时后果最严重的部分。我见过一个案例:某个智能体被赋予了“查询订单”和“修改订单地址”两个技能,但修改地址的技能没有加确认步骤,结果用户在对话中随口说了一句“帮我改到新地址”,智能体就直接执行了,而那个地址其实是用户之前提到的另一个人的地址。这就是典型的权限边界缺失。
权限边界应该至少包含三个维度:
- 操作范围:这个技能能操作哪些数据、哪些系统、哪些字段。比如“只能读取订单表,不能写入”“只能修改收货地址,不能修改支付方式”。
- 确认机制:哪些操作需要用户二次确认,确认的方式是什么。比如“修改地址前必须让用户复述新地址并确认”。
- 拒绝策略:当请求超出边界时,应该直接拒绝、转人工、还是请求额外授权。拒绝时的措辞也很重要,不能让用户觉得是系统故障,而要明确告知“这个操作不在当前权限范围内”。
注意:权限边界不是写在提示词里让模型“自觉遵守”就够了。对于高风险操作,应该在工具调用层做硬性拦截,模型只是第一道防线,不是最后一道。
3. 实操:从零搭建一个按需加载的技能系统
3.1 技能目录的组织方式
假设我们要为一个内部工单系统搭建智能体,涉及订单查询、退款处理、地址修改、物流跟踪四类任务。我会先建立一个技能目录,每个技能一个文件夹,里面放一份 SKILL.md 和可选的辅助文件。
目录结构大概是这样:
skills/ order_query/ SKILL.md examples/ refund_process/ SKILL.md examples/ address_update/ SKILL.md examples/ logistics_track/ SKILL.md examples/每个 SKILL.md 的头部用 YAML 格式写元信息,方便程序解析:
--- name: order_query trigger: keywords: ["订单", "查询", "单号", "购买记录"] intent: order_status dependencies: - database: orders permissions: read: ["orders", "order_items"] write: [] confirmation: none ---元信息下面是正文,用 Markdown 写具体的执行步骤和输出规范。这样做的好处是,程序可以只读取元信息部分来做技能筛选和权限校验,不需要把整个 SKILL.md 都加载进上下文。只有确定要加载某个技能时,才把正文部分读进去。
3.2 触发条件的编写与调试
触发条件的编写需要反复调试。我的做法是:先写一版宽泛的规则,然后收集一批真实输入,跑一遍看哪些该触发的没触发、哪些不该触发的触发了,再逐步收紧。
以 order_query 为例,最初的触发条件可能只是“包含‘订单’或‘单号’”。但实际运行会发现,“我要退款,订单号是123”这句话也会触发 order_query,而实际上应该触发 refund_process。这时候就需要调整:如果同时包含“退款”和“订单号”,优先触发 refund_process,order_query 作为辅助技能加载。
调试触发条件时,我建议维护一个测试集,至少包含三类样本:正例(应该触发)、负例(不应该触发)、边界例(容易混淆的)。每次修改触发规则后,跑一遍测试集,看准确率和召回率的变化。这个习惯能帮你避免“改了一个规则,崩了三个场景”的情况。
3.3 加载顺序与上下文预算
当一次任务需要加载多个技能时,加载顺序会影响模型的理解。我的经验是:先加载“约束类”技能,再加载“操作类”技能。约束类技能定义了什么不能做、什么需要确认,操作类技能定义了什么可以做、怎么做。先约束后操作,模型在执行时更不容易越界。
上下文预算也需要提前规划。假设模型的上下文窗口是 128k token,系统提示和对话历史占 20k,那么留给技能的空间大概是 100k。如果每个 SKILL.md 平均 2k token,理论上可以加载 50 个技能。但实际上,加载太多技能会导致注意力分散,效果反而下降。我的建议是,单次加载的技能数量控制在 5 到 8 个以内,超过这个数量就要考虑拆分任务或引入中间层做路由。
如果确实需要加载很多技能,可以考虑“两级加载”:先加载技能摘要(每个技能一两句话),让模型判断需要哪些,再加载完整内容。这样第一级可以覆盖更多技能,第二级只加载真正需要的。
3.4 权限校验的落地方式
权限校验不能只靠模型自觉。我的做法是在工具调用层加一个校验中间件,每次模型请求调用某个工具时,中间件先检查当前加载的技能是否包含该工具的权限,以及请求的参数是否在允许范围内。
举个例子,address_update 技能声明了write: ["shipping_address"],那么当模型请求调用update_order工具并传入field: "payment_method"时,中间件就会拦截并返回错误。模型收到错误后,会根据 SKILL.md 里的失败处理逻辑,告知用户“当前只能修改收货地址,支付方式需要联系人工客服”。
这种硬性拦截的好处是,即使模型因为某种原因“忘记”了边界约束,系统层面依然能兜住。模型是概率性的,系统是确定性的,两者结合才能既灵活又安全。
提示:权限校验中间件的错误信息要设计得对模型友好。不要返回“权限不足”这种模糊信息,而要返回“当前技能 address_update 不允许修改 payment_method 字段,允许的字段是 shipping_address”。这样模型能更准确地理解发生了什么,并给出合适的回复。
4. 常见问题与排查技巧实录
4.1 技能加载了但模型不执行
这是最常见的问题之一。技能明明加载了,SKILL.md 里也写了步骤,但模型就是不走流程,而是用自己的通用能力回答。原因通常有三个:
第一,SKILL.md 的步骤描述太抽象,模型不知道具体该调用什么工具。比如“查询订单信息”就不如“调用 order_query 工具,传入 order_id 参数,获取返回的 status 和 items 字段”来得明确。
第二,技能加载的时机太晚。如果模型已经开始生成回复了,再加载技能就来不及了。正确的做法是在模型生成第一个 token 之前,就完成技能筛选和加载。
第三,系统提示里没有明确告诉模型“优先使用已加载的技能”。模型可能会觉得自己的通用知识足够回答问题,就不去走技能流程。解决办法是在系统提示里加一句:“当已加载技能与当前任务相关时,必须按照技能定义的步骤执行,不得跳过。”
4.2 多个技能冲突时的优先级
当两个技能对同一件事有不同的规定时,模型会困惑。比如 order_query 说“输出订单状态用中文”,refund_process 说“输出退款状态用英文”,如果两个技能同时加载,模型就不知道该听谁的。
解决冲突的原则是:越具体的技能优先级越高,越接近当前任务的技能优先级越高。可以在技能元信息里加一个priority字段,数值越大优先级越高。加载时按优先级排序,冲突时高优先级覆盖低优先级。
另一种做法是,在技能设计阶段就尽量避免冲突。比如把“输出语言”这种全局设置抽出来,放在系统提示里统一规定,而不是让每个技能各自定义。技能只负责自己独有的逻辑,公共规则上收。
4.3 排查技能未触发的思路
当用户反馈“智能体没有按预期处理某个请求”时,我会按以下顺序排查:
- 看输入是否命中了触发条件。把用户输入和触发规则逐条比对,确认是规则太严还是输入太偏。
- 看技能是否被加载。检查加载日志,确认技能有没有进入上下文。有时候是加载逻辑的 bug,不是触发规则的问题。
- 看模型是否读取了技能内容。如果技能加载了但模型没执行,可能是技能内容太长被截断,或者位置太靠后没被注意到。
- 看工具调用是否成功。如果模型执行了步骤但结果不对,可能是工具调用参数错了,或者工具本身返回了异常。
- 看输出是否符合规范。如果工具调用成功但最终回复不对,可能是输出规范没写清楚,或者模型在最后一步“自由发挥”了。
这个排查顺序是从外到内、从简单到复杂,能帮你快速定位问题所在。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 技能未触发 | 触发条件太严 | 比对输入与规则 | 放宽关键词或增加同义词 |
| 技能误触发 | 触发条件太宽 | 查看误触发样本 | 增加排除条件或提高阈值 |
| 加载后不执行 | 步骤描述太抽象 | 检查 SKILL.md 步骤 | 补充具体工具名和参数 |
| 执行结果不对 | 工具参数错误 | 查看工具调用日志 | 修正参数映射或默认值 |
| 输出格式不符 | 输出规范不明确 | 检查输出示例 | 补充字段类型和示例 |
| 权限越界 | 边界约束缺失 | 查看工具调用记录 | 增加中间件硬拦截 |
| 多技能冲突 | 优先级未定义 | 检查冲突技能 | 设置优先级或抽离公共规则 |
| 上下文超限 | 加载技能过多 | 统计 token 消耗 | 减少加载数量或做摘要 |
4.5 几个踩过的坑
第一个坑是把 SKILL.md 写成了文档而不是指令。文档是给人看的,可以有很多背景介绍和解释;指令是给模型看的,需要直接、明确、可执行。我早期写的 SKILL.md 里有很多“这个技能的目的是……”之类的描述,后来发现这些内容对模型执行没有帮助,反而占用了上下文。现在我只保留触发条件、步骤、输出规范和边界约束,其他一概不写。
第二个坑是忽略了技能的版本管理。技能是会迭代的,今天改一版,明天改一版,如果没有版本记录,出了问题很难回溯。我的做法是每个 SKILL.md 头部加一个version字段,每次修改都递增,同时在技能目录里保留最近三个版本的备份。这样即使新版本出了问题,也能快速回滚。
第三个坑是权限边界写得太粗。一开始我只写了“这个技能可以修改订单”,没有细化到“只能修改哪些字段”。结果模型在修改地址时,顺手把订单备注也改了。后来我把权限细化到字段级别,才堵住这个漏洞。权限边界要细到“字段级”甚至“值级”,比如“金额只能改小不能改大”“地址只能改一次”。
第四个坑是没有做技能加载的性能优化。早期每次请求都重新读取所有 SKILL.md 文件,磁盘 IO 成了瓶颈。后来加了一层缓存,把技能元信息缓存在内存里,只有确定要加载某个技能时才读取完整文件。这个优化让平均响应时间从 800ms 降到了 200ms 左右。
5. 技能系统的扩展与演进方向
5.1 从单技能到技能链
当业务逻辑变复杂时,单个技能往往不够用,需要多个技能串联执行。比如“退款处理”可能需要先查订单、再验资格、再执行退款、最后通知用户,这四个步骤分别对应四个技能。这时候就需要一个“技能链”的概念,把多个技能按顺序编排起来。
技能链的实现方式有两种:一种是显式编排,在 SKILL.md 里写清楚“第一步加载 A,第二步加载 B”;另一种是隐式编排,让模型根据当前状态自己决定下一步加载什么。显式编排可控性强,适合流程固定的场景;隐式编排灵活,适合需要根据中间结果动态调整的场景。
我的建议是,核心流程用显式编排,异常处理用隐式编排。这样既保证了主流程的稳定性,又保留了应对意外的灵活性。
5.2 技能的市场化与复用
当技能数量积累到一定程度后,自然会想到复用。有些技能是通用的,比如“日期解析”“金额格式化”“地址标准化”,可以在多个项目之间共享。这时候就需要一个技能市场或技能仓库,让不同团队可以发布、发现、引用技能。
技能复用的关键问题是接口标准化。如果每个技能的输入输出格式都不一样,复用成本就很高。我的做法是定义一套基础的输入输出协议,比如所有技能都接受一个context对象作为输入,返回一个result对象作为输出,具体字段由技能自己定义,但外层结构统一。这样调用方不需要关心技能内部实现,只需要按协议传参和取结果。
5.3 技能效果的度量与迭代
技能上线后,需要持续度量效果。我通常关注四个指标:触发准确率、执行成功率、输出合规率、用户满意度。触发准确率衡量技能该加载时有没有加载、不该加载时有没有误加载;执行成功率衡量技能步骤有没有按预期完成;输出合规率衡量最终输出是否符合规范;用户满意度则通过反馈或评分收集。
这四个指标里,触发准确率是最基础的。如果触发都不准,后面的指标就没有意义。我一般会先花两周时间专门调触发规则,把准确率做到 95% 以上,再开始优化执行和输出。
迭代时,我建议每次只改一个变量。比如这周只调触发关键词,下周只调步骤描述,不要同时改多个地方。否则出了问题很难定位是哪个改动导致的。
5.4 一个值得关注的趋势
最近我注意到一个趋势:越来越多的团队开始把技能定义和模型解耦。也就是说,同一套 SKILL.md 可以适配不同的模型,换模型时不需要重写技能。这背后的逻辑是,技能描述的是“做什么”和“怎么做”,而不是“怎么让某个特定模型理解”。只要技能描述足够清晰、结构化,不同模型都能执行。
这对技能编写提出了更高要求:不能依赖某个模型的特定行为或提示词技巧,而要用通用的、明确的结构化描述。好处是,当新模型出现时,你可以快速切换过去,而不需要把整个技能库重写一遍。我在最近一个项目里试过,同一套技能在三个不同模型上运行,触发准确率差异在 5% 以内,执行成功率差异在 10% 以内。这个结果让我对技能解耦的方向更有信心了。
提示:如果你正在设计技能系统,建议从一开始就把技能定义和模型调用分开。技能文件只描述逻辑,模型调用层负责适配不同模型的接口差异。这样后续换模型或加模型时,工作量会小很多。
5.5 技能系统的边界与局限
说了这么多技能系统的好处,也得说说它的局限。技能系统不是万能的,它适合“流程相对明确、步骤可枚举、边界可定义”的任务。对于那些高度依赖创造性、没有固定流程、需要大量常识推理的任务,技能系统反而会限制模型的能力。
比如让智能体写一篇营销文案,你很难用 SKILL.md 定义清楚“怎么写才算好”。这时候长提示词或者少样本示例可能更合适。技能系统的价值在于“把复杂流程标准化”,而不是“替代所有提示词工程”。
我的判断标准是:如果一个任务你能画出流程图,那它就适合用技能系统;如果画不出来,或者画出来之后发现分支比主干还多,那可能就不适合。技能系统解决的是“可控性”问题,不是“能力上限”问题。它让智能体在明确边界内稳定运行,但不会让智能体变得更聪明。
在实际项目中,我通常会把任务分成两类:流程明确的部分用技能系统,需要灵活发挥的部分用提示词或示例。两者结合,而不是互相替代。这样既能保证核心流程的稳定性,又能保留一定的灵活性。