智能体技能拆解实践:用agent-skills构建稳定可维护的LLM应用 这两年搞LLM应用我有一个特别深的感受大家都在聊“智能体”但真正把智能体做稳定的人聊得最多的往往不是模型本身而是给Agent配了什么能力、怎么配的。也就是项目标题里这两个词——agent-skills。我在实际项目里见过太多这种情况一个Agent的system prompt堆了两千字把业务规则、对话礼仪、工具说明、兜底话术全塞进去结果模型一遇到稍微复杂点的请求就开始“乱拳打死老师傅”要么答非所问要么在一个错误分支上反复打转。问题的根源不是模型不够聪明而是我们把Agent的能力做成了一锅粥。而agent-skills这套思路本质上就是在回答一个问题怎么把Agent的能力从一锅粥拆成一个个可以独立维护、独立测试、独立调用的技能模块。这篇文章不聊那种玄而又玄的框架理论我就结合自己的落地实践聊聊agent-skills究竟解决什么问题、一个技能文件应该长什么样、怎么让技能真正被Agent调用起来以及我在这个过程中踩过的坑。内容偏向工程实操适合那些已经跑通了一个基础Agent应用、正想往更复杂场景推进的开发者参考。1. 为什么技能拆解比堆Prompt更接近真实需求先从我最近接手的一个智能家居助手项目说起。早期的做法特别朴素把所有设备控制逻辑、状态查询规则、定时任务处理、能耗提醒、节假日祝福……统统写进一段超长system prompt。刚开始没问题设备少、场景单一模型还应付得过来。但加到第三个月噩梦就来了用户说“有点热”模型有六七种理解方式到底该调空调、开风扇还是建议脱衣服在超长prompt里这些场景的触发条件互相交叠模型经常行为漂移。加一个新功能比如“根据电价时段推荐洗衣时间”要在那段两千字的prompt里找一个不冲突的位置改完还得回归测试所有旧场景心累。每次调试都在思考“模型为什么又没走那个分支”——因为prompt里十几个能力块根本说不清模型到底“看没看见”某条规则。最后逼得我换了个思路不再把Agent当成一个什么都该会的全才而是把它当成一个有工具箱的技工。这个工具箱里每一件工具对应的就是一个skill。技工接到任务后先判断该用哪件工具再拿出工具按说明干活。这个朴素的切换解决了我前面说的三个大问题行为可预期每个skill只负责一个职责边界清晰的领域模型拿到对应skill时上下文里没有多余的干扰规则。增量成本低新增能力就是新增一个skill文件不碰历史已经稳定的部分。可单独测试我可以对每个skill做独立的输入输出测试不用每次都在全局跑一遍所有场景。这里有一个非常关键的认知转变之前我总试图让模型“理解所有规则”但模型本质上是一个概率推理器它在超长上下文中对边缘规则的注意力天然会被稀释。而skills的做法是把上下文做成了按需加载——模型每次只需要面对一个范围很小的指令集命中率和稳定性自然就上来了。2. 一个Skill文件的最小可用结构拆解我在项目里落地的skill定义没有套什么复杂框架就是一个目录加一个个子目录每个子目录代表一项技能。目录内部用标准结构组织下面拆开讲。2.1 Skill的目录与文件结构这是我在项目里使用的结构app-skills/ smart-home/ SKILL.md timeline/ prompt/ actions/ weather/ SKILL.md prompt/每个skill的主文件是一个SKILL.md用YAML frontmatter加正文的形式描述能力。看起来像这样--- name: smart_home_control description: 控制智能家居设备包括开关、调节温度和设置定时任务。当用户提到空调、灯光、窗帘或家电时使用。 version: 1.2.0 tools: - device_api - timer_api context: max_turns: 8 requires_devices: true --- # 智能家居控制 ## 适用场景 当用户表达以下意图时使用本技能 - 开关设备开灯、关空调等 - 调节设备状态温度、亮度、风速 - 设置定时任务两小时后关空调 ## 执行步骤 1. 查询用户的设备列表确认是否有对应设备。 2. 如果设备状态查询失败直接告诉用户并停止。 3. 执行设备操作后向用户反馈结果和当前状态。关键的字段是description——它是模型判断该不该用这个skill的核心依据。这个字段的写法很有讲究我后面专门用一节讲。2.2 不只是PromptSkill的三种核心载体一个skill里除了指令文本常见的还有代码动作和参考数据Prompt类型纯粹是指导模型怎么做的文本指令。适合流程判断、文本分析、内容生成这类不强依赖外部系统的技能比如“生成一句话摘要”“判断用户情绪”。Action类型挂载可执行代码。比如“查询设备状态”“调用天气接口”。代码用parameters描述入参模型根据对话内容填充参数后触发。混合类型先调用代码拿数据再按prompt规则处理数据最后生成回复。这类在实操里最常见上一节的智能家居控制就是典型。2.3 参考数据Few-shot样例应该放哪里经验表明直接在SKILL.md里堆样例会让指令文本变得臃肿而且容易干扰模型对指令本身的注意力。我的做法是把样例放在examples/子目录通过SKILL.md里的examples字段引用路径。调用时只在上下文空间允许的情况下才加载样例否则只保留主指令。这里补充一个我自己调试时的基准一个skill主指令控制在600个token以内超过这个量就想想是规则太多还是样例和规则混在一起了。保持短是为了路由后上下文里“噪音”更少——这部分在第四章有实测对比。3. 让Skill真正被Agent调用起来路由、组装与回填文件写好了真正让它跑起来还要解决三个环节怎么选中、怎么喂给模型、怎么把结果落地。这条链路我把它叫作“技能运行时”。3.1 路由策略不等于简单的关键词匹配很多第一次接触agent-skills的开发者会误以为路由就是关键词匹配。实际上生产环境里我测试下来效果最好的组合是“语义召回 规则兜底”把每个skill的description做向量化对用户当前输入做相似度检索取Top K作为候选。用模型自身的判断力做最终裁决。把Top K候选的标识和description拼进一个很小的prompt让模型选一个最合适的。这一步是目前我试过最稳的向量检索保证候选不跑偏模型裁决保证在候选之间做更合理的取舍。两者互补缺一个都会出问题。关于相似度阈值我给一个参考值低于0.55的召回基本可以弃用0.55到0.75之间值得让模型做二选一或多选一高于0.75一般可以直接命中。这个值会随向量模型变化建议在自己的数据上跑一遍再定。3.2 上下文组装只带最需要的部分路由完成后下一步就是把skill内容注入到Agent的主prompt里。这里要克制不要把所有skill的全部内容一股脑倒进去。我在线上的做法是分三个级别必要级SKILL.md中最核心的指令以及执行步骤这部分必须注入。增强级examples子目录里的样例只有当模型置信度在0.7以下时才注入。动作级actions里声明的工具定义按需注入到function calling接口。这样设计的原因是LLM对上下文的利用效率会随长度下降把Agent的注意力聚焦到当前这一步该做的事上是稳定输出的关键。3.3 结果回填与技能内状态skill执行完之后结果要能够回填进Agent的运行上下文让后续的对话能够引用前面操作的结果。比如用户说“把空调调到26度然后告诉我今天比昨天电用得多不多”前半个操作的结果需要被后半个分析使用。我处理这个问题的方案是每个skill在最终输出时除了生成给用户的自然语言回复还可以输出一段结构化状态比如{ status: completed, device: bedroom_ac, action: set_temperature, value: 26, execution_id: 20240607_001234 }这段结构化数据会被存储到会话状态里供后续轮次的skill按需检索。没有这个环节技能之间就是孤岛无法形成真正的协作。3.4 兜底所有技能都不匹配怎么办这是我在真实项目中特别强调的一个问题。系统里十几个技能用户输入又总是天马行空路由经常遇到“一个都没命中”的情况。我的方案并不是让Agent说一句“我没听懂”就完了而是准备一个fallback_skill它负责做三件事判断用户意图是否与现有技能相关如果相关主动询问用户锁定更具体的需求。如果是闲聊或简单问答直接走通用对话能力但会标记出“未命中技能”。把所有未命中样本记录到日志作为后续新增技能的候选来源。这套兜底逻辑非常实用——可以说Agent的70%能力取决于路由命中剩下30%的体验完全取决于没命中时的处理。4. 实测对比装上Skill前后同一个Agent的表现差异为了把skills的价值说得更直观我从之前的智能家居项目里挑了一个真实场景做对比测试。模型用的是同一个只改变能力加载方式。场景是这样的用户连续说了两句话。“下午3点有个线上会议帮我提前10分钟开空调还有到时候把勿扰模式打开。”测试结果如下对比维度全部能力堆在单段Prompt里使用agent-skills拆分意图识别准确性约76%经常把“勿扰模式”和“静音设备”混淆约94%两个skill各自职责清晰上下文窗口占用每个请求约3800个token所有规则常驻约1200个token按需装配两个skill增量修改成本改一个规则全部回归测试只测对应skill成本明显更低失败排查成本日志里全是prompt片段定位困难有明确的skill执行记录和参数回填详细说一下第二个维度的差异。单段prompt方案里模型在判断“勿扰模式”时同一段文本里可能还有“摄像头隐私模式”“门锁静音”“家庭影院模式”等近十余种家电能力描述模型经常被这些相似能力干扰判断置信度被摊薄。而skills方案里“勿扰模式”只出现在与“会议场景”相关的skill中模型面对的上下文几乎零干扰准确率提升是必然的。token占用上其实还有一层隐藏收益更少的token意味着更快的首字延迟和更低的成本。从实测看在同等模型下skills方案的首字响应时间反而更短约200-300ms的差别在对话产品里体感很明显。有对比数据之后我把这个项目彻底迁移到了skills方案并且一直维护到现在。对我来说这已经不是一个优化技巧而是一条必须走的路。5. 架构设计中的关键权衡与取舍讲完了机制和收益接下来这部分可能在文档中很少被提到但恰恰是决定agent-skills系统能走多远的地方。5.1 Skill的粒度设置没有标准答案但有判断标准skill拆太粗会回到老问题拆太细又会导致路由混乱和管理成本急剧上升。我在实践中总结出两个比较有效的判断维度按意图收敛度划分同一个skill内部的用户意图在语义空间里应该聚成一簇彼此间距离较远。比如“开空调”“调亮度”“设窗帘开合”可以归入“设备控制”但“生成今日能耗报告”就应该独立因为意图空间隔得比较远。按工具依赖划分需要调用同一批后端API的操作倾向于放进同一个skill能减少上下文重复注入。网格参数很难给出一个固定值我自己在管理二十多个skill时划分粒度大约是一个skill覆盖三到五个相近意图并且一定是共用同一组tools的。超过这个数说明该拆了。5.2 Description字段怎么写出高辨识度这个是最容易被忽略、但影响力最大的点。两个skill能力相近时description写得含糊路由必然翻车。我的写法遵循一个公式“技能的职责范围 触发信号词 明确排除项”。举个例子description: 控制智能家居设备包括空调、灯光、窗帘等。当用户表达对温度、亮度、设备开关的调节意愿时使用。不用于查询用电统计或生成能耗报告。对比一下糟糕的写法description: 控制家里的智能设备。差别很明显。前者告诉模型“什么情况用”也告诉模型“什么情况不用”路由准确率会有明显提升。我在项目里专门做过一次只优化description字段的测试同一批25个测试用例路由准确率从82%提到了95%左右。5.3 上下文膨胀的隐形杀手样例注入很多开发者在SKILL.md里放大量样例出发点是“few-shot能提升效果”但实测下来在有足够好的指令描述时两到三个样例就够样例不是越多越好。样例太多会造成两个问题一是挤占上下文空间影响真正关键的工具调用约定二是样例与用户请求不一致时模型容易被表面相似的样例带偏反而产生幻觉。我的建议是把样例分为“必带样例”和“按需样例”必带不超过两个按需样例在路由置信度低时才加载。这个度要基于自己的业务调但方向上一定是“克制”。5.4 Skill之间的依赖与冲突当skill数量多起来后一个用户请求可能同时触发多个候选skill。比如“帮我看看周末天气适合洗车吗”就同时涉及天气查询和出行建议两个技能。我不建议让多个skill并行处理同一输入否则回复会显得很碎。更稳的方案是引入一个轻量的“编排层”先让模型基于所有候选skill的描述判断谁是主skill、谁是辅助skill再决定执行的先后顺序。如果两个skill的职责边界确实存在重叠比如“天气查询”和“出行建议”在“周末适不适合出游”这个问题上都有表达空间我的做法是在路由前增加一个意图分类步骤把“天气活动决策”这一类复合意图固化成一个新的独立skill而不是依赖两个skill自行协商。技能职责边界上的重叠必须显式消灭不能留给模型临场发挥。6. 踩坑实录技能路由翻车与系统回滚的全过程最后一部分我想分享一个印象很深的排障经历。这个案例几乎把agent-skills可能遇到的问题都踩了一遍复盘价值极高。6.1 现象新Skill上线后老功能整体失灵事情发生在一次发布后。新加了一个“能耗分析”skill功能本身是好的——单独测试时各种输入都能正确响应。但全量上线半小时后客服侧开始有反馈用户说“把客厅灯调到最暗”助手回了一句“正在为您分析昨日用电趋势”。这个错误很典型用户意图是设备控制但路由却命中到了“能耗分析”。我第一反应是检查新老skill的description果然旧“设备控制”skill的description里有“查询设备用电状态”这个短语而新“能耗分析”skill的description又有“分析设备用电能耗趋势”这样的表述。两者在向量空间里距离极近用户哪怕只是提了“灯”和“暗”向量检索都能把两个skill都召回模型裁决时又因为新skill的description看起来更具体于是误选了它。6.2 排查链路不是模型问题是描述与路由的耦合问题我当时的排查顺序是这样的第一步复现并确认路由结果。在日志里找到了这条请求的路由记录确认实际命中是新skill。第二步检查两个skill的description。这一步是重点我会把两个description放到向量库里跑一次相似度结果相似度达到0.78远超我平时设定的0.55候选线。问题根源基本锁定。第三步修正description。给“设备控制”补上了明确的排除项“如果需要分析历史用电数据或生成报告请勿使用本技能”同时把“能耗分析”更精确地限定在“按月或按周的用电量统计与建议”范围。同时我把两个skill的description相似度作为新增的自动化测试用例以后每次新增skill都要跑一遍与既有skill的描述相似度检查。第四步灰度验证。修正后先让新skill在10%流量下跑了30分钟确认设备控制回归正常、能耗分析也没有误伤再逐步放量到全量。6.3 相似度检查应该写进发布流程这个案例之后我把“skill描述相似度阈值检查”加进了发布CI流程任何新增或变动的skill其description与现有全部在线skill的向量相似度最高值不得超过0.6一旦超过就任务阻断要求开发者调整描述或重新梳理skill边界。这个自动检查上线之后“新skill导致老功能乱跳”的问题基本绝迹了。说句实在话技能系统最大的技术债不是代码写得多乱而是技能描述之间的语义耦合。个人经验与扩展建议如果让我把这套agent-skills体系遇到的所有问题浓缩成一句话那就是技能本身就是一种上下文工程决定它好不好用的不是堆了多少规则而是每一个技能边界是否清晰、描述是否高辨识度、路由是否可控。最后分享一个我后来一直在用的小技巧无论你现在有多少个skill每个季度做一次“技能审计”——把线上所有skill的命中率、平均使用轮数、误路由率拉出来排名对那些连续一个月没有被命中过的skill直接下掉或者合并。很多开发者总觉得技能越多越强大但实际上每多一个命中率不足的skill就是在给路由增加一份噪音反而会干扰那些真正高频核心技能的发挥。做减法、控边界、重描述这三点比任何花哨的Agent框架都管用。