从去年开始,我一直在折腾agent-skills这套东西,起因很简单:手里的智能体模型越来越聪明,但一遇到真实业务就露怯——让它帮我整理一份数据报表,它只会给我一段漂亮的废话。后来我把"技能"这个概念真正落地成一套可复用的系统工程,情况才彻底改观。这篇文章不聊概念包装,就讲讲我踩过的坑、验证过的写法,以及一套可以直接抄走的技能体系搭建方案。适合正在做智能体应用、自动化流程,或者想把大模型接进真实业务的开发者参考。
1. 先搞明白agent-skills解决的是哪一类问题
1.1 从"只会聊天"到"能干活"的跨越
一个只依赖模型自身能力的智能体,本质上就是一个高级聊天机器人。你问它天气,它能编一个;你让它查数据库,它就卡住了。为什么?因为模型的知识截止在训练那一刻,它没有手、没有脚,无法触碰外部系统。
agent-skills这个思路的核心,就是给智能体装上一套"可调用的动作库"。每个技能对应一个真实可执行的函数:查库存、发邮件、算指标、调接口,统统封装成模型能理解的描述和参数协议。模型本身不执行动作,它只负责判断"当前任务该调用哪个技能、传什么参数",真正的执行落在你写的代码里。
我最早做的一个内部报表助手就是这样。单纯靠提示词,模型能说出"应该先获取订单数据再计算环比",但它拿不到数据。把"查询订单表"和"计算环比增长率"封装成两个技能之后,模型会自动编排调用顺序,整个链路一下子从"建议"变成了"交付"。这一步跨越,才是agent落地业务的真正分水岭。
1.2 Skill和普通提示词、插件到底有什么区别
很多人会把技能(Skill)和提示词、插件混为一谈,实际差别很大。
提示词是"教模型怎么说",它改变的是模型的表达方式,不改变模型的能力边界。技能是"给模型一个抓手",让模型能触达原本碰不到的系统。插件粒度一般比较重,往往是一个完整的应用模块;技能则更轻、更原子化,强调单一职责。举个粗糙的例子:提示词让模型"像个财务分析师那样回答",技能则让模型真的能"调用财务接口拿到本月流水"。
从工程角度看,技能体系的价值还在于可复用和可评测。提示词写多了会互相污染,技能却可以按目录管理、按版本迭代,甚至单独写单元测试。这也是我后来坚定地把所有能力都往技能方向收敛的原因。技能之间可以互相组合,形成更复杂的工作流,而提示词很难做到这种结构化复用。
2. 技能设计:第一刀切在能力边界上
2.1 需求拆解:把业务动作翻译成技能清单
动手写技能之前,先别急着敲代码。我习惯先把目标业务完整跑一遍,把每一个"动作"单独拎出来。比如做一个内容发布助手,整个流程可能是:选题、查资料、写稿、配图、排版、定时发布。这里边"查资料"和"定时发布"就是典型的技能点,而"写稿"更适合交给模型原生完成,不需要额外封装。
有个简单判断标准:凡是模型仅靠自身知识就能完成得不错的,不做技能;凡是需要触碰外部数据或产生外部副作用的,必须做技能。按这个标准梳理,我的经验是最终技能清单通常只有全流程动作的三分之一到一半,其余动作属于模型自带能力。
梳理完之后,给每个技能写一行职责说明,再标上依赖关系。这一步做完,你就得到了整个技能体系的地图。别小看这张地图,它直接决定了后续技能命名的清晰度、编排的复杂度,以及排查问题时你能否快速定位是哪个环节出了岔子。
2.2 颗粒度选择:太粗是摆设,太细是灾难
技能颗粒度是我在实际开发中体会最深的一个设计决策。太粗的技能,比如"处理订单"这种大杂烩,模型根本不知道你具体要它干什么,参数也会膨胀到难以维护。太细的技能,比如"把字符串转大写"这种基础操作,会让技能列表冗长不堪,模型选择成本急剧上升,经常选错。
我踩过最典型的一次坑:把"生成周报"做成了一个技能,内部塞了数据拉取、指标计算、模板渲染、格式转换四件事。结果模型每次调用都要传十来个参数,稍微漏一个就报错。后来拆成"查询周期数据""计算核心指标""渲染周报文档"三个技能,配合一个小的编排逻辑,不仅调用准确率上去了,每个技能也都能独立测试和替换。
一个可行的颗粒度参考:技能内部隐含的判断分支不超过两三个,参数不超过六七个。如果突破了这个限制,多半是技能太粗,需要继续拆。同时也别忘了一件事:技能层级可以分粒度,比如底层提供原子技能,上层通过编排组合出复合技能,两层之间互不干扰。
2.3 技能描述怎么写模型才买账
技能描述是给模型看的说明书,它的质量直接决定模型能不能在关键时刻想起这个技能。我见过太多人把描述写成一堆功能罗列,模型根本抓不住重点。
经验之谈,一份好描述通常包含三块:触发条件(什么时候该用)、执行效果(调用后会发生什么)、约束禁忌(什么情况下不能用)。比如一个"查询门店库存"的技能,描述可以这样写:
当用户询问某个商品在指定门店是否有货、剩余数量,或者需要判断某门店某个SKU能否满足订单时,调用此技能。执行后将返回该商品在目标门店的实时库存数量和状态。如果用户询问的是全渠道库存概览,请改用另外一个汇总技能。
注意这里的"触发条件"不要写得太死,要允许模型做推理。比如用户说"明天能去店里买到这款鞋吗",描述里如果写了"当用户明确提到库存时触发",模型就可能不识别。写成"用户询问门店现售情况或需要判断商品可得性时触发"就更接近模型的语义理解方式。
另外,技能描述之间要刻意制造区分度。两个技能如果描述里大量关键词重叠,模型经常傻眼。我处理这类问题的方法是:先写初版描述,跑一组典型问题看模型选哪个,凡是选错的,优先调整描述中的区分性词句,而不是改代码逻辑。
3. 实操落地:一套技能体系的搭建全过程
3.1 目录结构与技能注册机制
一套清爽的目录结构,能让技能体系在规模变大之后依然可控。我目前用的是这种组织方式:
skills/ ├── data_apis/ # 数据查询类技能 │ ├── query_stock/ │ │ ├── skill.md # 技能描述 │ │ ├── schema.json # 参数协议 │ │ └── handler.py # 执行逻辑 │ └── query_sales/ ├── content_ops/ # 内容操作类技能 └── registry.json # 技能注册表每个技能一个独立目录,描述、协议、实现分离。注册表registry.json保存所有技能的元信息,启动时统一加载,这样模型侧的技能列表始终和代码侧保持同步。
注册表里我习惯顺带维护两个字段:version(技能版本)和deprecated(是否弃用)。版本号的作用不只是追溯,更重要的是当模型同时面对新旧两个版本技能时,可以用它做倾向性过滤。注册这个环节看似简单,却是后面做动态热更新的基础,很多人忽略它的重要性。
3.2 参数Schema:把约束写清楚
参数协议是技能最容易被低估的部分。模型生成参数时会"发挥想象力",如果你不做严格约束,它会传一些根本不存在的字段进来。我见过最离谱的一次,模型给日期参数传了"明天",给枚举值传了"随便"。所以参数Schema一定要把类型、取值范围、必填性写得明明白白。
一个参数定义的示例,以查询库存为例:
{ "name": "query_stock", "description": "查询指定商品在指定门店的实时库存", "parameters": { "type": "object", "properties": { "sku_code": { "type": "string", "description": "商品编码,格式为10位字符,必填", "pattern": "^[A-Z0-9]{10}$" }, "store_id": { "type": "string", "description": "门店编号,如ST001,可省略则查询全部门店", "pattern": "^ST\\d{3}$" } }, "required": ["sku_code"] } }这里有两个细节值得注意。第一,描述字段里写清楚格式要求,比单纯依赖正则更有效,因为模型先读描述再决定传什么值,描述越具体,参数幻觉概率越低。第二,把可省略参数在描述里明确标注,否则模型会为每个字段都硬凑一个值。
另外一个我自己摸索出来的技巧:针对日期、金额这类容易被模型瞎编的参数,做一个轻量级的映射逻辑——比如"明天""下周"这类相对时间在代码侧转换为具体日期。把语义解析放在技能执行层,而不是寄希望于模型每次都生成标准格式,能大幅提升成功率。
3.3 动态加载与热更新
技能体系做到一定规模后,你一定会遇到这个问题:新技能上线不能重启服务。我的做法是让技能加载器监听技能目录的文件变更,配合注册表的变更通知实现热更新。
具体流程是:文件变更触发校验(描述格式是否合法、Schema是否能解析、handler是否能导入),校验通过后更新内存中的技能索引,同时给正在运行的对话下发增量变更。这样旧会话不受影响,新会话立刻能感知到新技能。
热更新还有一个额外好处:你可以做A/B测试。同一业务放两个语义近似但写法不同的技能描述,观察一段时间内模型的选择率和任务成功率,留下表现更好的版本。这个做法帮我优化过好几个技能描述,比凭感觉改词可靠得多。
4. 调参与踩坑:实测中反复出现的5类问题
4.1 技能召唤失灵:模型就是不调用
这是最常遇到的挫败场景:技能写得清清楚楚,模型偏偏不用,绕一大圈给你生成一个"伪代码答案"。我排查这类问题通常按三步走:先看技能描述里有没有和用户问法强相关的关键词;再看技能是不是被其他相似技能"抢了流量";最后检查调用结果是不是总失败,导致模型形成了"这技能不靠谱"的负反馈。
有一回我的一个"汇率换算"技能长期无人问津,后来发现是因为描述开头写了"当用户询问外币金额对应的人民币价值时",但真实用户更常说"帮我看看这一万日元能换多少钱",两边表达差太远。改成"当用户以任意外币金额询问人民币等值金额,或反向询问时触发"之后,调用率立刻上来了。
4.2 上下文污染:技能输出灌爆对话窗口
每个技能的返回结果都会进入上下文,成为模型后续推理的依据。结果越堆越多,对话窗口被塞满,模型开始遗忘最初的用户目标。这个问题最隐蔽,因为它不会立刻报错,只会在某个节点突然"失忆"。
我的对策是两个:第一,为每个技能结果做摘要,只把关键信息放回对话,详细数据写入暂存区,模型需要时再以另一个技能读取;第二,给技能结果标注"时效性",对于只影响当前步骤的中间结果,在下一次模型决策前主动折叠清理。这相当于给对话窗口做了定期整理,让模型始终把注意力放在当下最需要的信息上。
4.3 参数幻觉:模型编造了不存在的值
参数幻觉和模型幻觉一脉相承,但在技能场景里它的破坏性被放大了——因为它不只是说错话,而是可能导致真实系统收到脏数据。上面提到的正则约束能挡掉一部分,但真正有效的防线是执行层的兜底校验。
我在执行层里加了一道"宽容处理逻辑":遇到不符合规范的值,不是直接抛错,而是尝试做合理的归一化。比如门店编号用户写成了"上海徐汇店",代码尝试把它映射到门店表的模糊匹配结果上;匹配不上才向模型返回一个清晰的"可用取值列表",让模型二次选择。这道兜底逻辑让我的技能整体成功率提升了大约两成。
4.4 失败重试与幂等:同一个技能被调了两次
真实业务里网络抖动、接口超时都可能导致技能执行失败。但如果你的技能不是幂等的——比如"创建订单""发送通知"这类会产生副作用的操作——盲目重试就是灾难。我遇到过一张订单被重复创建五次的情况,原因就是重试机制没有识别"首次调用其实已经成功,只是返回超时"。
解决思路也不复杂:对每个技能调用生成一个请求ID,执行前先查一下该ID是否已处理过;处理记录保留一段时间,重试直接返回上次结果。对于确实需要重试的失败,做指数退避,而不是立刻重来。这套机制加上之后,重复副作用事件基本清零。
4.5 技能相互踩脚:权限边界模糊
多个技能操作同一份数据源时,权限边界就容易出问题。最典型的是"查询类"技能和"更新类"技能共用一套数据接口,模型在一次对话里先查后改,逻辑上没问题,但如果查询技能的实现代码里飘了一个更新操作,就可能在用户不知情的情况下改了数据。
我的经验是给技能分层设权:只读技能统一走只读连接,写操作单独声明,并在注册表里标记write: true。运行时做一次拦截,凡是未声明写权限的技能,一旦检测到写入意图立即阻断并记录告警。这个拦截救过我一次,否则一条线上促销规则就被误改了。
5. 进阶玩法:多技能编排与组合复用
5.1 串联、并联与条件路由
单一技能能解决单点问题,真正撑起复杂业务的还是技能间的编排。我在项目里实际上用了三种编排模式,按场景混搭。
串联模式最直观:上一个技能的输出作为下一个技能的部分输入。比如"分析竞品价格"这个复合任务,先调用"抓取竞品列表",再对每个竞品调用"获取详情页价格",最后调用"生成对比报告"。模型天然擅长这种顺序推理,只要每个技能的描述清晰,串联基本不会跑偏。
并联模式适用于互不依赖的多路查询,模型会把几个技能调用合并成一次性发起,省掉大量来回。条件路由则是根据前序结果决定后续走哪个分支,我在设计时会在每个分支入口放一个判断型技能,它的返回值就是一个纯指令,比如"go_plan_a"或"go_plan_b",避免模型在分支选择时自由发挥。
5.2 技能评测:怎么知道技能真的靠谱
技能不是写完就完了,得像代码一样有回归测试。我给每个技能维护一个评测集,里面包含三类样本:典型问题、边界问题、易混淆问题。每次技能描述或实现有变动,拿评测集跑一遍,看三个指标:调用命中率(该调的时候有没有调)、参数合法率(传参规不规范)、任务完成率(最终结果对不对)。
这个评测体系最大的价值,不是测出某一次的成败,而是能暴露趋势性问题。比如某次我优化了一个通用技能的描述,单测指标都是好的,但评测集显示另一个技能被抢占调用的比例上升了——这就是描述之间的"互斥性"出了问题。现在我在发版前都会强制跑一轮全量评测,已经养成了习惯。
5.3 从我自己的实践看后续还能怎么扩展
技能体系做稳定之后,我越来越觉得它不只是"让模型多会几个函数"那么简单。它的本质是把你对业务的理解,转化成模型可以稳定使用的结构化接口。顺着这个思路,后续能扩展的方向其实不少。
一个是跨场景复用:把攒下来的技能按行业或业务线打包,团队之间直接共享技能包,新项目起步时技能体系不再是空白。另一个是自动生成技能:对于新增的API接口,写一个半自动工具根据接口文档生成初始技能描述和参数协议,人工只做校验和微调。还有一个方向是技能间依赖关系的可视化,目前我用一个简单的依赖拓扑图来管理,但随着技能数量增长,这块值得做成正式的元信息工程。
最后再分享一个小技巧:技能描述不要锁死在一版。我每隔一段时间就会翻看模型调用日志,找出那些"被反复调用但总是中途打补丁"的技能——这类技能往往是描述里漏了一些真实用户会说的话,补上它们,比新加技能带来的收益更大。技能体系的价值是慢慢积累出来的,第一批写完能用是运气,持续迭代才是常态。