资讯动态

智能体技能设计实战:从工具封装到模型调度与质量保障

发布时间:2026/10/20 1:43:46 来源:尧图企业网站定制
做了大半年智能体相关的工作一个感受越来越强烈很多人把 Agent 想得太玄真正上线后的翻车现场十有八九出在一个最朴素的环节——技能skill没做好。这里的技能不是一句提示词也不是一个简单的工具函数而是给智能体准备的一套“能干什么、怎么干、什么时候别干”的完整封装。模型能力再强面对一堆描述模糊、边界重叠的技能照样会变成选择困难症左试一个右试一个最后给你一个四不像的结果。这篇文章不讲论文里的理论纯粹是把我在 agent-skills 从设计到上线的实操经验整理一遍。包括技能和工具到底差在哪、一份能打的技术描述怎么写、模型是怎么从候选池里选中技能的、我在实测中踩过的三座大山以及如何把技能当成正经代码来做版本、测试和观测。适合正在做 Agent 应用的人也适合刚接触智能体、想让自己的 Agent 别再乱调工具的人。1. 先理清概念Skills、Tools、Plugins和Workflow到底差在哪1.1 四者的边界别靠感觉划分我见过不少项目一开始叫 tool后来叫 plugin再后来又说自己在做 workflow其实代码里就是同一个函数列表。概念混乱的后果是架构也跟着乱有人把业务规则写进了 tool 描述里有人把编排逻辑塞进了 plugin 的加载过程最后谁都不敢动一改就崩。我习惯用下面这张表来给团队对齐认知维度Tool 工具Skill 技能Plugin 插件Workflow 工作流本质一个可调用的函数接口能力单元的完整封装一组能力的打包发布形态多个能力的编排顺序消费方模型直接调用模型先理解再调用宿主程序按需加载模型或脚本按步骤执行自带约束只有入参出参有触发条件、示例、禁忌、兜底有依赖与装载逻辑有分支、重试、终止条件典型形态接口 Schema描述文档 实现安装包 注册信息状态机或步骤图这张表的重点不在名词而在“谁在消费它”。Tool 是给模型一个“怎么调用”的接口Skill 是给模型一个“什么时候调用、为什么调用、什么算调用成功”的完整上下文Plugin 解决的是“怎么分发、怎么装载”Workflow 解决的是“多个技能之间以什么顺序和条件衔接”。四者可以组合使用但不该互相替代。1.2 技能是把“能力”从“接口”里解放出来举个生活化的例子墙上的电源插座是一个 Tool它定义了电压、电流和插孔形状任何人插上去都能通电。但一台洗衣机不只是插座它还有说明书、洗涤模式、禁忌不能洗羽绒服能但得选模式、异常处理脱水不平衡会报警。Skill 就是给这台电器配齐了“说明书 模式开关 禁忌表 异常兜底”的完整包装。落到代码上一个 Skill 通常由这几块拼成自然语言说明书什么时候用、什么时候不用写给模型看参数 Schema入参的格式、枚举、默认值写给接口看示例对代表性的输入输出写给模型做少样本参考兜底行为参数缺失时怎么反问、执行失败时怎么降级元信息技能ID、版本、负责人、权限级别写给系统看把“能力”从“接口”里解放出来的意思是同一个后端接口在不同场景下可以包装成多个技能。比如“查订单”和“查物流”底层可能都调同一个订单服务但它们的触发条件、话术、参数约束完全不同。如果你只暴露一个 tool模型很难区分用户到底想问什么如果你拆成两个 skill每个技能都能把边界讲清楚。1.3 什么时候才需要引入技能层不是所有项目都要上技能层。我的判断标准很简单当下面任何一条成立就该认认真真设计技能体系了。工具数量超过 10 个模型开始出现明显的选择混淆同一个接口在不同场景下需要不同的话术、权限或参数约束你发现自己在不停地改 tool description却又不敢随便改因为怕影响别的场景团队超过两个人维护同一套 Agent 能力需要明确的归属和版本如果只是两三个接口老老实实用 Tool Calling 就行技能层是复杂度到了一定程度之后的组织手段而不是炫耀架构成熟度的装饰品。我见过最离谱的项目一共 3 个工具硬是套了一个技能注册中心加一个检索模块延迟翻了一倍正确率一点没变。复杂度必须跟着问题走别反过来。2. 技能描述是真正的“产品文档”决定模型能否选对的一半权重2.1 一份能被模型读懂又能被检索命中的描述长什么样很多人以为技能描述就是给 description 字段写一句话比如“查询订单”。这句话对检索系统来说信息量约等于零对模型来说更是灾难——它根本不知道什么时候该用。我目前比较顺手的写法是把每个技能写成一份结构化的 YAML而不是一段自由文本。给你看一个实际在用的模板skill_id: order_status_query version: 1.2.0 name: 查询订单状态 description: 当用户询问已下单商品的当前处理进度如待发货、运输中、已签收时使用。 when_to_use: - 用户提到订单号、快递单号、订单状态 - 用户问“我的东西到哪了”且上下文存在订单 when_not_to_use: - 用户只是问怎么下单或退货规则 - 用户还没完成购买仅有购物车内容 parameters: order_id: type: string description: 用户提供的订单号格式为 8 位数字或字母组合 scope: type: string enum: [all, pending_shipment, in_transit, delivered] default: all examples: - input: 我昨天买的键盘到哪了 output: {order_id: A1234567, scope: in_transit} fallback: 当缺少订单号时先询问用户提供订单号不要猜测注意几个细节。description 字段写的是“当……时使用”而不是“本技能用于查询”前者是触发条件导向后者是功能导向。when_to_use 和 when_not_to_use 是给模型划边界用的尤其是在两个技能容易混淆的时候这个字段能救命。examples 不是摆设模型在少样本场景下对示例的信赖程度远高于描述一个与描述矛盾的示例比描述本身写错更致命。2.2 描述质量的三个致命伤和三剂解药我在审查别人的技能库时反复看到三类问题基本可以覆盖九成的选错事故。第一是描述泛化。有人写“处理用户问题”有人写“提供帮助”。这类描述等于没写模型只能靠猜。解药很朴素把动词和对象写具体把触发场景写清楚。“查询订单当前物流状态”比“处理订单问题”好用十倍因为模型在做意图匹配时需要的是可观测、可判断的触发信号而不是业务部门挂在嘴上的抽象功能名。第二是关键词堆砌。不少人听了“描述要丰富”的建议就在 description 里疯狂塞同义词比如“查询、查找、搜索、看看、了解一下订单”。结果就是两个技能同时命中模型在候选之间反复横跳。解药是放弃堆词改用 when_to_use 和 when_not_to_use 做正交化区分。每个技能的触发条件之间要有清晰的“分水岭”而不是靠词频抢生意。第三是示例与描述不一致。我见过一个查库存的技能描述里写“仅查询实时库存”示例却给了“用户问什么时候到货”返回库存不足建议补货的例子。模型看到这个示例会认定这个技能也能处理到货时间的问题于是该调物流查询的时候调了库存查询。解药只有一句话示例是给模型看的契约必须和描述严格对齐宁可少写两个例子也不要写一个带偏的。2.3 给参数写描述不只是给模型看参数描述经常被当成给后端开发看的技术注释这是误解。在 Agent 场景里参数描述是模型抽取参数时的“填空提示”它决定模型能不能从用户一句口语里准确抽出结构化的入参。我在参数描述上吃过亏。一开始我写的描述是“订单号”模型老是把“昨天”抽成订单号。后来改成“用户提供的订单号格式为 8 位数字或字母组合请从用户原话中提取完整订单编号”准确率明显上来了。规则很简单参数描述要包含三样东西——是什么、什么格式、从哪来。如果是日期参数一定要给出格式示例比如“格式为 YYYY-MM-DD”否则模型会理直气壮地给你返回“昨天”两个字。如果参数有枚举值就把每个枚举值的含义写清楚模型才有依据做映射。3. 注册、索引与调度把技能给模型之前要想清楚的几件事3.1 技能注册表统一收口而不是撒得到处都是技能多了以后最先崩的往往不是模型而是工程组织。今天 A 同学在配置文件里加了一个技能明天 B 同学在代码里又注册了一个同名技能后天两人同时改了同一个技能的不同版本合并时直接冲突。所以第一步永远是统一收口所有技能都从同一个注册表登记、审核、发布。注册表里的每条记录至少要有这些字段技能ID全局唯一一旦发布不可改变、名称、版本号、归属人、状态开发中/已上线/已停用、权限级别、依赖的服务列表。有了归属人和状态你才能回答“这个技能还能不能用”和“出了问题找谁”这两个最基础的问题。没有注册表的技能库本质上是一堆无人认领的定时炸弹。3.2 上下文窗口不是无限的自助餐这是很多人忽略的一个数学问题。假设平均每个技能的描述加示例占 500 个 token你有 20 个技能光是把候选列表塞进上下文就要吃掉 10000 个 token。这还没算对话历史、系统提示和检索结果。上下文窗口再大也不该用来做技能列表的展架。我的经验是分档处理技能总数在 10 个以内可以考虑全量常驻让模型每轮都能看到所有选项超过 10 个就必须引入检索预筛否则模型不是在思考而是在 9000 个 token 的列表里翻找。不要迷信大窗口窗口越大模型对长列表尾部的注意力越差这是结构性问题不是模型变笨了。3.3 两种实用的调度模式目前我实际用下来靠谱的调度模式有两种。第一种是全量常驻模式。所有技能描述直接拼进系统提示模型自己选。优点是简单、无额外延迟、模型能看到全局缺点是一旦技能超过 10 个就开始稀释注意力。适合技能少、调用频次高、场景固定的项目。第二种是检索预筛模式。先走一层检索把几百个技能里最相关的 Top N我一般控制在 5 到 8 个挑出来再让模型从这 5 到 8 个里选。检索可以用向量召回加关键词召回混合最后合并去重。优点是可以支撑大规模技能库缺点是多了一层检索意味着多了一个可能的失效点检索质量差时正确答案根本到不了模型眼前。另外还有一招可以搭配使用规则预筛。有些技能天生带状态约束比如“未登录用户不能用的技能”“管理员专属技能”。这类约束不该让模型判断应该在调度层直接过滤掉既省 token 又避免误用。规则能挡掉的绝不让模型去纠结。4. 实测中的三座大山技能碰撞、参数幻觉和上下文稀释4.1 技能碰撞两个技能抢同一个触发词这是我最常遇到的事故类型。有一次在某客服场景里用户对智能客服说“查一下我买的键盘到哪了”模型的回答时对时错——一会儿去调“查订单”技能一会儿去调“查物流”技能。表面看是模型抽风其实是两个技能的触发条件高度重叠。排查链路是这样的先翻日志发现两个候选技能的相关性得分非常接近0.82 对 0.79再对比两份描述发现“到哪了”“订单”“快递”这几个关键词两个描述里都有最后检查负向约束两个技能都没写 when_not_to_use。根因很清楚技能拆分粒度有问题且描述里缺少边界说明。修复分三步走。第一步在“查订单”技能里加 when_not_to_use“如果用户仅询问物流轨迹交给物流查询技能”。第二步在“查物流”技能里加 when_to_use“当用户询问包裹当前位置、运输进度、预计送达时间时使用”。第三步加一个组合技能的示例引导模型在同时需要订单信息和物流信息时先查订单拿到订单号再查物流。改完之后日志里这个场景的选择正确率从七成左右提到了九成以上。4.2 参数幻觉模型编造了schema里不存在的字段有一次在做某个智能助手时我发现模型调用技能时传了一个参数叫 timezone但技能 Schema 里根本没有这个字段。后端因为白名单校验直接报错模型收到报错后不是反思而是再编一个别的参数继续撞墙整个调用链路就这么卡死了。这个问题的根子在于参数描述里的一句话。我在参数说明里写了“系统会按用户时区处理时间”模型读到这句话理所当然地认为 timezone 是个可传参数。这就是典型的“描述暗示”型幻觉。修复方法有三个层面。第一参数描述里只写“这个参数是什么、什么格式”绝不要写“系统拿到它之后会怎么处理”把能力暗示从参数描述里彻底赶出去。第二调用层做严格 Schema 校验不认识的字段一律拒绝并把错误信息明确告诉模型让它基于拒绝信息重新生成。第三落库时再做一次白名单过滤双保险。从那以后我对所有参数描述都养成了一个习惯写完通读一遍专门找“暗示性”的表述。4.3 上下文稀释技能越多智商越低这是我花最长时间才真正相信的一件事。技能少的时候一切正常等技能涨到 20 个、30 个你会发现连最简单的查询都开始出错。不是模型坏了是上下文被稀释了。我在一个模拟项目里做过一次粗测固定相同的对话样本只改变暴露给模型的技能数量。5 个技能时任务成功率约 92%15 个技能时掉到 84%30 个技能时只有 71%。这个数字不精确但趋势极其稳定技能越多无关信息对注意力的干扰越强尤其是那些描述相似、边界模糊的技能会成片地拖累整体表现。应对措施有几个。一是给每个技能加“活跃度”标签连续一个月没有命中记录的技能自动转为 disabled 状态不参与候选。二是走检索预筛时把召回数控制在 5 到 8 个不要因为窗口大就多给。三是定期做技能裁剪功能可以合并的合并触发条件没差异的干脆删掉。技能库的维护跟衣柜整理是一个道理定期扔东西比不断买新衣服重要得多。5. 把技能当代码管版本、测试和可观测性5.1 技能的版本改了描述等于改了行为很多团队把技能描述当成可随时改的文案这是大忌。对模型来说描述就是行为的来源改描述就等于改行为。你改了一个词可能就让某个场景的选择结果完全反转。所以技能必须像代码一样做版本管理。我参照语义化版本做了个简化版规范主版本号在触发条件、参数结构变化时递增因为这会改变模型的决策边界次版本号在增加示例、优化措辞时递增因为这会改变模型的行为倾向补丁号在修正错别字、修正示例错误时递增。每次发布都要留 changelog记录改了什么、为什么改、影响了哪些场景。技能 ID 保持稳定不能跟着版本变但实现上要存一份 source_hash方便后续做回放对比。5.2 回归测试用历史对话当考试卷技能改完不是上线就完事而是要先过回归。我的做法是攒一套黄金数据集从历史真实对话里抽出各种场景的样本每条样本标注期望命中的技能和期望抽出的参数。每次改完技能描述把这套数据跑一遍回放对比新结果和旧结果。回放的做法不一定非要重跑整套模型有个取巧的办法把历史上下文里的系统提示换成新技能描述看模型在相同对话下的选择是否发生变化。重点盯三类情况以前对的变成了错的、以前错的变成了对的、以及模棱两可的边界样本有没有被推过线。回归测试的价值在于它把“感觉好像变聪明了”这种主观判断变成了一张可以对比的差异表。没有这张表你根本不敢改任何描述。5.3 可观测性先看到模型为什么选错再谈优化最后也是最重要的一点必须让“模型为什么选了这个技能”这件事变得可见。我现在的日志体系里会记录四样东西候选列表及每个候选的相关性得分、最终选择和置信度、用户原句和改写后的意图、调用成功或失败的重试轨迹。有了这些数据排查问题就变成了翻日志而不是猜。给每个技能做一张统计表会很有帮助技能名被选次数执行成功率被拒绝率平均耗时查询订单状态132094.2%3.1%1.2s查询物流轨迹98691.5%5.4%1.4s修改收货地址21488.3%11.7%1.9s这张表能帮你区分两类问题如果某个技能被选次数少但执行成功率很高说明是描述不够突出检索或注意力没给到它如果被选次数很多但执行成功率低说明技能自身的实现有问题或者说它的触发边界画得太宽把不该接的请求也接进来了。先定位问题在哪一层再决定动描述还是动代码这是排查效率的分水岭。我现在的习惯是一个新项目上线技能数量控制在 5 个以内跑通全流程再根据真实日志逐周添加。每添加一个技能都要回答一个问题它解决了哪个现有技能解决不了的真实请求答不上来就不加。写技能描述的时候就当自己在给一个新同事写交接文档——把该做的、不该做的、容易踩的坑全部写进去。模型和新人一样给的信息越具体犯的错就越少。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑