做智能体这一年多,我最深的感触是:“agent-skills”不是给代理加一堆功能,而是给代理一套可管理的能力边界。大多数项目失败,不是模型不够聪明,而是把功能全堆在系统提示词里,最后变成一坨谁都不敢动的“巨型指令集”。这篇文章是我在几个智能体项目里把能力拆成独立技能包的完整复盘,包括技能文件结构、触发机制、踩坑记录和评测方法。如果你正在做AI代理、Prompt工程或者自动化工作流,这篇应该能帮你少走不少弯路。
先说一个反直觉的结论:技能不是越多越好,很多情况下“砍技能”才是提升效果的关键。下面我从第一次翻车开始讲。
1. 从“系统提示词堆功能”到“技能层”:我踩过的第一个大坑
1.1 那个“什么都往里塞”的崩溃现场
我在做某知识库问答代理时,初期版本非常原始:所有功能——文档问答、内容总结、翻译、表格提取、周报生成、日程提醒——全部写进系统提示词。当时觉得,提示词反正支持几千字,多写点规则总没坏处。结果上线一周,问题接二连三地冒出来。
最典型的一个场景是:用户只问了一句“这个文档讲了什么”,代理却会莫名其妙地执行“把文档中所有表格转成Markdown”的步骤,或者回答到一半突然开始生成周报。我一开始以为是模型抽风,后来把对话日志拉出来看才发现,系统提示词里那些功能描述相互纠缠,模型根本分辨不出“用户当前意图”对应哪条规则。
更麻烦的是,提示词越长,模型越容易“忘了”开头的核心约束。有一次我加了八条新规则,结果第二天代理开始频繁在回答里附带上“根据内部规范,我不能回答这个问题”之类的内容——它把某个技能里的限制条件,当成了全局禁令。
这个教训很直接:系统提示词本质上是静态文本,模型每一轮都要全量通读它。当指令数量超过一定阈值,注意力就被摊薄了。指令与指令之间的边界一旦模糊,模型就会在错误的时间执行错误的动作。所以后来我把思路从“把规则写全”转成了“把规则拆开”,才真正解决了问题。
1.2 技能库与“提示词膨胀”的分水岭在哪
很多人问我,技能库和“把功能写进系统提示词”到底有什么本质区别?我用一张表就能说清楚:
| 对比维度 | 系统提示词堆功能 | 独立技能层 |
|---|---|---|
| 职责边界 | 无边界,全局可见 | 每个技能有明确触发条件 |
| 上下文开销 | 每轮全量加载 | 按需加载,平时只有技能清单 |
| 复用性 | 复制粘贴,改一改就崩 | 独立文件,多处可挂 |
| 维护难度 | 动一处牵全身 | 改单个技能不影响其他 |
| 可测试性 | 很难单独验证一条规则 | 可以逐个技能跑测试集 |
| 冲突处理 | 靠模型临场判断 | 靠触发条件+优先级显式控制 |
这并不意味着系统提示词就该彻底删掉。我的做法是:系统提示词只保留“我是谁、全局约束、输出规范”这三类内容,凡是涉及具体操作流程的,全部外置成技能。技能库里每个技能处理一类“怎么做”的问题,系统提示词负责“什么不能做”和“整体调性”。两者各管一段,互不越界。
这个分水岭说起来简单,但真正动手设计技能文件时,又会遇到新问题——技能文件本身该怎么组织,才能让代理在几十个技能里准确选到对的那一个?这是下一节的重点。
2. 技能文件的核心结构:一份能被代理读懂的“任务说明书”
2.1 五要素模板:从“经验描述”到“可执行定义”
我最早写的技能文件,其实就是一段中文描述加上几个步骤,效果很随机。后来参考了各类工具定义的思路,把技能文件固化成了一个五要素模板,结构如下(以JSON为例):
{ "name": "weekly_report_generator", "description": "根据用户提供的工作日志或项目进度,生成结构化周报。", "when_to_use": [ "用户明确要求生成周报/周总结", "用户提供了多条工作记录并希望整理成周报" ], "when_not_to_use": [ "用户只是想修改某一条记录", "用户没有要求汇总,只是闲聊工作内容" ], "steps": [ "1. 收集用户提供的所有工作记录,逐条归类到‘进行中/已完成/问题风险’三个分类", "2. 对每个分类生成3-5条摘要,保留关键数字和决策", "3. 输出周报模板,包含本周完成、下周计划、风险与求助三项" ], "examples": [ { "user": "这周做了首页改版,还有接口联调,下周打算开始做性能优化", "assistant": "(周报示例内容...)" } ] }这个模板之所以有用,是因为它强迫我回答三个问题:代理什么时候用这个技能?(when_to_use)什么时候绝对不能用?(when_not_to_use)用的时候具体做什么?(steps)很多技能设计失败,就是因为只写了“怎么做”,没有写“何时做”。
when_not_to_use的设计尤其重要。它就像一道否决门,能在代理误触发的边缘把它拉回来。我后来在大量评测中发现,加了这个字段之后,误触发率平均能下降一半以上。
2.2 描述信息是最难写的东西:要让代理知道“什么时候该用我”
技能文件里,description和when_to_use这两个字段,决定了代理能不能在正确时机选中这个技能。我见过太多人把这两个字段写成功能说明书,比如“本技能用于周报生成,可以生成周报、支持定制周报模板、支持导出PDF”——这种描述看起来没问题,但代理很容易在用户问“今天有什么安排”时,因为出现了“周报”两个字就误触发。
我的经验是,描述信息要回答一个核心问题:在什么对话场景下,这个技能是用户此刻想要的效果?有两条原则可以参考:
用触发场景开头,而不是用功能名称开头。坏写法:“用于周报生成”。好写法:“当用户提供多条工作记录并要求整理成周期性总结报告时,使用此技能生成周报。”
显式声明不适用场景。坏写法:“适用于所有文本整理场景。”好写法:“仅在用户明确要求‘周报’或‘周总结’时使用;用户修改单条记录或询问工作内容时,不要使用本技能。”
还有一个小技巧:在examples里提供一组用户可能说的话,尤其是容易混淆的边界情况。比如“帮我把这段改成过去式”这种和技能无关的话,也可以放进when_not_to_use的示例里,让代理从具体案例中学到边界。
2.3 技能粒度怎么选:不是越细越好,也不是越粗越好
技能粒度是我反复调试过的问题。以“自动生成周报”为例,最简单的做法是把整个过程做一个技能。但后来我发现,如果“数据收集”和“报告结构生成”在很多场景下都要独立复用(比如日报、月报都需要数据收集),那把它们拆成两个技能就更合理。
我的判断标准有四个:
- 复用频率:这个步骤会不会被多个场景共用?会,就拆。
- 参数复杂度:技能需要输入的参数是否超过三四个?超过,说明职责太杂,拆。
- 目标一致性:整个技能执行过程中的每一步,是不是都朝着同一个目标?如果中途要切换目标,拆。
- 失败模式:技能半途而废时,失败点集中不集中?失败原因五花八门,说明里面混杂了多个子任务,拆。
但我一般不建议一上来就拆得很细。我的节奏是先做粗粒度技能,把流程跑通,再根据调用日志逐步拆分。一上来拆成十几个小技能,代理光选技能就要选半天,反而得不偿失。
3. 技能装载与触发机制:让代理自己在正确时机调用技能
3.1 全量注入 vs 按需加载:上下文空间是稀缺资源
技能文件设计好之后,下一个问题是:这些技能怎么进入代理的上下文?
最土的办法是把所有技能定义一次性拼进系统提示词。但这样做的坏处很明显:假设你有20个技能,每个技能定义800字,那上下文里光是技能描述就占了16000字,留给对话历史和工具返回结果的空间所剩无几。更糟的是,大量不相关的技能描述会造成“提示词噪音”,模型的选择准确率反而下降。
我一直采用的做法是按需加载:代理平时只看到一份“技能清单”,清单里每个技能只保留一行简介(名称+一句话触发场景)。当代理从用户输入中判断出可能需要某个技能时,再把该技能的完整定义(步骤、示例、约束)拉入上下文。
这个机制和手机App的“后台不常驻”思路很像:图标在桌面上,需要用的时候才启动,用完就清理内存。技能的完整内容相当于“安装包”,平时不占运行内存,调用时才加载。
3.2 触发决策链路:从技能清单到最终执行
为了让你更直观地理解按需加载的过程,我把代理从收到用户消息到调用技能的完整链路写在这里:
- 读取技能清单:系统提示词中只保留技能清单,每项一行,包含技能ID和一句话描述。
- 初步筛选:代理根据用户消息,从清单中选出候选技能。这一步通常会有1到3个候选。
- 读取完整技能定义:代理把候选技能的完整文件内容读入上下文,然后逐一比对用户输入与
when_to_use/when_not_to_use的条件。 - 裁决与调用:如果某个技能的条件匹配且没有触碰否决条件,代理开始按
steps执行。 - 执行监控:执行过程中,代理要时刻对照“该技能的目标”,如果发现用户的需求已经变成另一类任务,应该中止当前技能并回到第2步重新选择。
- 释放临时上下文:技能执行完毕后,将技能详情从上下文中移除,只保留对话摘要。
这个链路里最容易出问题的,其实是第2步和第3步之间。代理在初步筛选时,如果因为description写得模糊而漏掉了正确技能,后面就全偏了。所以我会把第3步设计成“双重确认”:看着候选技能的完整描述,再问自己一次“用户这句话真的是让我做这件事吗?”。
3.3 不依赖模型“自由发挥”的技能选择评分思路
纯靠LLM凭感觉选技能,优点是灵活,缺点是不可控。同一个问题,今天可能选技能A,明天可能选技能B。为了提升稳定性,我后来给技能选择加了一个轻量评分机制:
- 语义相关度:把用户输入和技能的
when_to_use文本分别做向量化,计算余弦相似度,作为基础分数。 - 关键词命中:技能的关键触发词(如“周报”“总结”)出现在用户输入中时,给一定加分。
- 否决条件:一旦命中
when_not_to_use中的任意场景,直接将该技能否决,不参与排序。 - 优先级加权:如果多个技能分数接近,则调用次数少、通用性高的技能优先,避免冷门技能被频繁误选。
这个机制不复杂,甚至不需要单独的模型服务,用现成的Embedding接口就能跑。它的价值在于:让“是否调用这个技能”变成一个可计算、可回归、可检查的决策,而不是每次看模型的随机发挥。实际跑下来,我用评分机制替代纯自然语言选择后,技能选错率大约下降了30%。
4. 实测中翻车最多的三种情况:误触发、上下文污染、技能爆炸
4.1 误触发:描述太模糊,代理逮谁都是“能处理”
误触发是技能体系最常见的问题,没有之一。
我之前在某工单分类代理里写过一个技能,名字叫“data_cleanup”,描述写的是“整理用户提供的数据”。听起来没什么问题对吧?结果用户问“帮我把这段对话整理一下,去掉重复内容”,代理直接触发了这个技能,把用户的话当成数据清洗任务做了一遍。用户其实只是想要一个文本去重,根本不是数据清洗。
后来我查了一下,问题出在两个地方:一是技能描述里的“整理”是个高频词,和很多用户的日常表达重叠;二是when_not_to_use没写任何边界条件。修复方式也简单:把技能名改成“structured_data_deduplication”,描述改成“当用户提供的是表格化或多条结构化记录,且需要按字段去重时使用”,并在when_not_to_use里明确写“用户要求修改非结构化文本时,不适用”。
这个案例给我的教训很深刻:技能描述里的每个词,都要假设模型会对它做最宽泛的理解。一旦发现误触发,第一时间不是换模型,而是检查描述里有没有那些泛化动词(“整理”“处理”“分析”),然后用更具体的动词和宾语替换掉。
4.2 上下文污染:技能说明太长,把对话窗口挤没了
技能描述写得太详细,同样会出问题。我做过一个数据分析代理,其中“数据可视化”技能包含了大段规则、三个示例模板、各种图表类型的适用条件,总共接近2000字。这个技能本身写得没问题,但问题在于它一旦被触发,就会把2000字内容全部加载进上下文。
有次用户先聊了很长一段业务背景,然后才说“帮我画个折线图”。代理加载完技能定义后,对话历史被压缩得只剩几条,之前的业务背景全被挤出了上下文窗口。结果它画的图虽然格式没问题,但完全脱离了用户前面说的业务场景。
处理办法是把技能的“执行细则”和“技能定义”分开存储。技能定义只保留核心步骤和触发条件,控制在300字以内;详细的图表规范、模板示例则放在外部文档里,技能执行到对应步骤时,代理再按需读取外部文档的指定段落。这样既保持了技能的指导性,又避免了上下文被一次性占满。
4.3 技能爆炸:20个技能一起上线后,选择困难比想象中严重
第三个大坑不是单个技能的问题,而是技能数量上来之后整体变乱。
某项目一开始只有5个技能,代理表现稳定。后来团队热情高涨,一个月内加到了20个技能,结果调用准确率反而下降了。我最初以为是新技能的质量不行,后来看日志才发现,问题是技能清单本身太长了:代理光在“判断应该用哪个技能”这一步就要消耗好几轮思考,偶尔还会在两个相近技能之间反复横跳。
解决之道是“技能分组+数量控制”。
- 技术分组:每个技能加一个domain字段(如
billing、content、data),代理先判断对话属于哪个领域,再在该领域的子清单里选技能,而不是面对全部20个技能。 - 数量控制:同一时间对一个代理开放的技能不超过7到10个。超出的话,要么合并相近技能,要么把不常用的改成“手动触发”——就是让用户明确说出某个指令才会调用。
这个做法牺牲了一点自动化程度,但换来了选择稳定性和可维护性,我认为非常值得。
5. 技能评测与迭代节奏:既要会加技能,也要会砍技能
5.1 用固定测试集给技能打分,而不是靠感觉上线
很多团队评估技能效果,方式是“我试了几个例子,感觉还行”。这种评估在技能少时勉强够用,一旦技能变多,就会出现“按下葫芦浮起瓢”的情况:改了A技能的描述,B技能的选择准确率掉了5个点。
我给每个代理维护了一份固定测试集,每个测试用例包含:
- 一段模拟用户输入(覆盖典型场景、边界场景、易混淆场景)
- 期望行为(应该调用哪个技能,或者不应该调用任何技能)
- 期望输出(如果涉及生成内容,则记录关键要素)
每次改动任何技能,我都跑一遍全量测试集,记录三个指标:技能选中准确率(选没选对)、执行完成率(步骤有没有按预期走完)、多余动作率(有没有执行不该执行的步骤)。
下面是一张我常用的记录表样式:
| 测试用例 | 期望技能 | 实际选中 | 是否命中 | 备注 |
|---|---|---|---|---|
| 用户要求生成周报 | weekly_report_generator | weekly_report_generator | 是 | 3步骤全部执行 |
| 用户询问本周进度,未要求周报 | 无 | none | 是 | 未误触发 |
| 用户要求处理Excel重复行 | dedup_tool | weekly_report_generator | 否 | 误触发,需修描述 |
这份测试集不用很多,20到30条就够。关键在于把那些历史上真实发生过的误触发和漏触发都收进去,保证每一次改动都能被回归验证。
5.2 一次技能重构的完整记录:从7个技能砍到4个
讲一个我自己印象很深的重构经历。某客户工单分类代理上线时,我设计了7个技能:意图识别、字段提取、紧急程度判断、工单类别划分、合规检查、回复建议生成、工单转交规则。结果运行两周后,我发现“紧急程度判断”和“工单类别划分”这两个技能几乎每次都是一起触发的,而且用户根本不会只问其中一项。
与其让代理花两次决策去调两个技能,我把它们合并成了一个“工单画像”技能:一次调用同时完成紧急程度、类别、涉及字段的识别。同时,“工单转交规则”被并进了“合规检查”,因为它们都是约束型判断,适用场景高度重叠。
重构之后,技能数量从7个变成4个:工单画像、合规检查、回复建议生成、历史工单检索。触发准确率从82%提升到94%,平均每次对话的调用轮次也下降了接近三分之一。
这次经历让我强烈意识到一件事:技能维护的核心动作,不是“新增”,而是“合并”和“删除”。一个技能如果长期处于“已加载但很少有用”的状态,它不是备用能力,而是选择噪音。
5.3 我现在坚持的几条技能维护习惯
最后分享几条我现在一直在用的实操习惯,它们帮我省了很多事:
- 新技能先进“草稿区”。新技能写好之后,不直接开放给代理全部用户,而是先用一周时间收集日志,看看它在真实对话里的触发频率和误触发比例,确认稳定后再转正。
- 每周看一次调用日志。我通常会筛选出“被调用超过10次但用户明确表示不满意的技能”和“上线一周零调用的技能”,前者优化,后者考虑删除。
- 技能要带版本号。任何技能改动,我都会在文件里加一个version字段。否则,你根本不知道“上周效果很好”和“这个版本改了描述”之间的因果关系。
- 避免“技能依赖技能”。我早期设计过“周报技能”内部调用“数据收集技能”,结果代理经常在执行到一半时找不准到底该用哪个。现在我的原则是:一个技能尽量自包含,如果需要共享逻辑,那就把它做成独立的全局工具函数,而不是让技能之间互相嵌套。
我在实际项目中越来越觉得,agent-skills的本质,不是给模型准备更多的答案,而是给模型准备更好的“选择路径”。技能文件每多一个字段、每多一条否决条件,都是在帮模型降低一次决策成本。把精力花在描述、触发边界和评测上,远比追求技能数量更值得。