“agent-skills”,我第一次看到这个名字的时候,正在为某个内部Agent平台的能力边界发愁。模型推理很聪明,可到了真干活的时候——查订单、算数据、调接口、生成图表——它什么都做不了,因为它没有“手”。后来我们把注意力全部放在了技能层,前后重构了两版,终于从一锅乱炖的if-else变成了可注册、可编排、可观测的技能系统。这篇文章就是那段过程的核心记录,讲清楚agent-skills到底该怎么设计、怎么落地,以及那些文档里不会写的坑。
我默认你是有一定基础的Agent开发者,可能已经在用某个主流框架,也可能在自研Agent平台。看完这篇文章,你至少能回答这几个问题:技能和工具调用的区别是什么?技能定义里最容易踩的坑是哪个?为什么技能数量一多,模型就开始“选择困难”?以及,怎么让一个技能系统在连续接了几十个技能之后依然稳定可控。
1. 先搞清楚:Agent Skill到底是什么
1.1 从“大模型很聪明”到“Agent能干活”
市面上关于Agent的讨论多到让人眩晕,但剥开所有概念外壳,一个Agent要真正完成一项任务,本质上只需要三件事:感知、决策、行动。感知是接收用户意图和上下文,决策是让大模型推理出该怎么做,行动则是落实为真实世界的一次操作。前两项在大模型时代门槛极低,真正卡住所有人脖子的,是第三件——“行动”。
行动能力靠的就是技能。一个没有技能的Agent,就像一位满腹经纶却四肢瘫痪的学者,你问他“今天天气怎么样”,他能说出影响天气的十七个因素,却没办法帮你打开天气页面看实时数据。而一旦挂上“查天气”这个技能,模型收到问题后,就会自动生成对应的调用参数,执行器去请求天气服务,把结果带回对话里。整个过程看起来像是模型“学会”了查天气,其实模型什么都没学,它只是被我们赋予了调用外部工具的“手”。
这个思路的关键在于:技能不是模型的参数,而是模型外部的可执行单元。模型负责理解意图、生成结构化的调用请求,系统负责找到对应的技能、解析参数、执行并返回结果。这种“模型脑+技能手”的架构,就是agent-skills最核心的形态。你不需要为了一个新能力去微调一次模型,只需要把新技能注册进来,然后在提示词里告诉模型“你现在还会干这个”,这就搞定了。
1.2 技能、工具、插件与Action,到底有什么区别
我在和很多开发者交流时发现,大家经常把技能(Skill)、工具(Tool)、插件(Plugin)和Action混为一谈。四者确实相关,但设计逻辑完全不同。工具是最底层的一个能力原子,比如“发送HTTP请求”“执行一段Python代码”;插件是一组工具的封装,通常带着固定的配置和依赖,比如“数据分析插件”里含有读取CSV、清洗数据、生成报表三个工具;Action更像是一个业务动作的语义化名称,比如“下单”“退款”,它的内部可能串联了多个工具调用。
技能则是站在Agent视角提出的概念,它描述的是“模型在什么情境下、应该如何调用什么能力”。一个技能往往包含三个部分:给模型看的描述文本、给执行器用的参数规范、以及真正干活的回调逻辑。比如“查询订单状态”这个技能,描述文本负责让模型知道“用户问订单到哪了的时候用我”,参数规范规定了需要订单ID还是手机号,回调逻辑则是真实的接口请求。
换句话说,工具是“能不能干”,插件是“怎么打包”,Action是“叫什么名字”,而技能是“什么时候用”。当你设计一个Agent系统时,脑子里必须时刻记住这个分层。如果一上来就把工具和技能混在一起管理,用不了多久,技能列表就会变得臃肿且命名混乱,模型在意图识别时也会频繁选错。
2. 技能系统的整体设计思路
2.1 一条主线:从意图到执行的完整链路
一个技能系统不管表面多复杂,内部的主线永远是同一根:用户输入进来,系统判断需要哪个技能,抽取参数,执行技能,把结果播种回对话。搞清楚了这根主线,后面所有设计都是围绕它做精细度提升。
在我的实现里,完整的链路是这样的。第一步,解析器把用户传入的文本和历史消息组装成完整的对话上下文;第二步,系统根据上下文召回候选技能——不是把所有技能描述都塞给模型,那样上下文会爆炸,而是先用检索过滤出可能相关的十几个,再用模型去精排;第三步,模型判断当前是否确实需要调用技能,如果需要,输出一个结构化调用请求,包括技能名和参数;第四步,执行器根据调用请求找到技能实现,完成参数校验后真实执行;第五步,执行结果被包装成一条模型能读的消息,塞回对话流,模型基于结果生成最终回复。
这五步每一步都有对应的模块,分别是技能注册表、召回器、意图决策器、执行器、结果回填器。很多失败的Agent项目,问题不在某一个模块,而是模块之间的接口定义模糊。比如召回器返回的技能描述格式过于冗长,模型在决策时抓不住重点;又比如执行器在参数校验失败时只回一个“报错了”,模型完全不知道该不该换个参数重试一次。所以做技能系统,第一步要锁定的不是某个模块怎么做,而是整条链路的数据流长什么样。
2.2 技能定义规范:让模型看得懂、调得对
技能定义是整个系统的地基,也是最容易被低估的一环。我见过很多团队把技能定义写成“给工程师看的API文档”,字段齐全、注释精确,但大模型根本不买账。原因很简单,模型不是靠编译器理解你的字段,而是靠自然语言去匹配意图。技能定义里的每一句话,都是写给另一个“智力极高但没有常识的执行者”看的操作手册。
我的经验是把技能定义拆成四个字段来写。第一个是技能名,必须用稳定的动词+名词结构,比如query_order_status,不要用create、get这种过泛的词,否则相似技能一多,模型就会选择困难,比如query_order和query_order_status同时出现,模型大概率会混淆。第二个是描述文本,这是给模型看的核心信息,需要写清楚“什么情景适合用”“什么情景不要用”,比如“当用户询问订单当前所处状态或物流进度时使用本技能,如果用户只是想看订单列表,请使用query_order_list”。第三个是参数定义,用JSON Schema描述,必须把每个参数的类型、含义、是否必填写清楚,能约束枚举值的就尽量约束。第四个是执行配置,也就是超时时间、重试次数、并发限制这类运行参数,这部分是给人看的,不影响模型。
我在实践中发现,描述文本写得详细与否,直接决定了技能被准确调用的比例。同一个技能,描述只写“查订单”时,准确率可能只有六成;写清楚触发场景和排除场景后,准确率能稳定在九成以上。模型很吃这一套——它本质上是在做文本匹配,你的描述和用户问题的语义越近,它越容易选中你。
2.3 技能注册中心:有序管理大规模技能
当技能数量从个位数涨到几十上百时,一个集中式的技能注册中心就变成了刚需。注册中心要承担三个职责:所有技能的元数据集中管理、运行时按条件快速检索、版本与状态的可视化控制。
我做注册中心时走了不少弯路。第一版简单粗暴,用一个Python字典把所有技能全量加载到内存,每次请求都全量扫一遍。技能少的时候没问题,技能到五十个以后,每次构造提示词都要多花几百毫秒,而且模型面对五十段描述,决策质量明显下降。后来我换成了基于关键词和向量双路召回:先做关键词过滤,把明显无关的技能去掉,再用Embedding检索相似度,选出最相关的十到十五个送入模型。这样既控制了上下文长度,又保证了模型只在小范围候选里做选择。
注册中心的每条记录,除了技能定义本身,还必须记录状态信息:上线、灰度、下线、禁用。这很重要,因为Agent技能不像后端接口那样可以随时下线,正在运行的对话流可能已经携带了技能调用的下一步计划,如果你突然把技能删了,对话就直接卡死。正确做法是先禁用新调用,等存量调用结束后再完全下线,中间用状态字段精确控制。
3. 技能调度的核心实现
3.1 意图识别与技能匹配
技能调度最关键的一步,是让模型在正确的时机选择正确的技能。这里有个经典误区:很多人以为只要把技能描述全部拼在System Prompt里,模型就会自动选对。实际上,候选技能一多,模型会表现出明显的“选择困难”,它不仅会选错,甚至会在无关的场景里强行调用某个技能,给出奇怪的参数。哪怕是当前能力很强的模型,面对二十个以上的技能描述时,决策准确率也会显著下降。
所以我的做法是把匹配拆成两段。第一段是召回,目的不是找到“唯一正确的技能”,而是快速排除掉八成明显不对的选项。我用最轻量的方式做召回:先基于预设关键词映射,比如技能发布时手动维护几个触发词,再叠加Embedding向量检索。两路结果取并集后截断到十五个以内。第二段是精排,把召回后的技能描述压缩成一个简短的候选列表,交给模型做最终决定。这一步是典型的送分题,模型无论如何理解语境,自主选择自己的节奏与时机,我这边只要保证候选集干净,它就能输出稳定的正确调用。
这里有个很实用的手段:在技能描述前面加上一个“触发提示符”。比如“当且仅当用户明确表达查天气需求时,才调用weather_query技能”。这类提示符本质上是一道闸门,让模型对“该不该调用”有更清醒的边界感,而不是为了展示能力去瞎调用。我实测下来,加了这行话以后,无业务关系的误调用率下降了一半左右。
3.2 参数抽取与校验
技能调度中第二个常见的翻车点,是参数抽取。模型从用户的话里抽取参数,本质上是在做信息提取,看似简单,实操中却全是坑。典型的问题有三个:用户没给全参数、给了不同类型的参数、以及给了同义词。
以“查询订单状态”为例,用户可能说“帮我看看我上一个订单到哪了”,订单ID完全没有出现在这句话里。模型如果严格遵守参数Schema,就会直接报缺参。这时最该做的不是让模型硬造参数,而是让它先输出一个“参数不足”的信号,再由系统主动追问。我的实现是在技能定义里增加一个required_params和optional_params的标签,模型判断必填参数缺失时,会输出专门的ask_for_params请求,携带缺失的字段名,系统拿到后转成自然语言追问用户。
参数校验同样要做在执行之前。不能过分信任模型输出的类型,模型把“2024-02-30”当作合法日期传入,接口大概率直接报错。所以每个技能执行前统一过一道校验器:类型转换、枚举值核对、正则匹配、范围检查。校验失败时返回一个标准错误结构,里面写明是哪个字段、期望什么格式,这段信息也会被送回模型,让它有据可循地决定是修改参数重试,还是转人工。
3.3 执行与结果回填
技能真正执行时,要考虑的问题已经从“模型能不能选对”切换到了“系统能不能活得久”。首先是超时控制,我通常给外部API调用设置十到十五秒的超时上限,超过就主动中断,返回超时错误。其次是重试策略:网络类错误可以重试一到两次,业务类错误(比如订单不存在)绝不重试,直接返回业务错误语义。这个区分非常重要,如果对业务错误也盲目重试,既浪费资源,还会让用户感受到明显的延迟。
执行完成后,结果不是简单丢给模型就完事。回来的原始数据往往是JSON,直接塞进去,模型也未必能准确提炼出用户要的关键信息。更稳妥的做法是先做一步“结果整理”,把冗长的JSON压缩成几句话的核心摘要,或者转成更适合阅读的表格文本,然后再作为一条工具结果消息回填给模型。这一步能显著降低模型复述时的胡编概率。
4. 技能的编排与组合
4.1 串行、并行与条件分支
单个技能调用只解决最简单的问题,实际业务里更多是需要连续调用多个技能才能完成一个目标。比如用户问“帮我看看哪些订单超时了,然后把超时原因整理成报告发我”,这里面至少包含三个技能:查询订单列表、分析超时原因、生成报告,而它们之间有明确的前后依赖关系。这种依赖关系就是技能编排的起点。
我的做法是在Agent流程里引入一个轻量的编排层,支持三种基础模式。串行模式最简单,前一个技能的输出作为后一个技能的一部分输入,比如查询到订单列表后,把订单ID列表传给分析模块。并行模式适用于多个相互独立的查询,比如同时查天气和查机票,可以并发请求,整体耗时由最慢的那个决定。条件分支则依赖中间结果,比如订单查询结果为空时走“无订单”话术分支,不为空时继续走后续分析分支。
实现时不需要一上来就上重型流程引擎,我用的是一个有向图模型,每个节点代表一个技能调用,边代表数据传递依赖。执行器从入度为零的节点开始按拓扑序调度,能并行的节点放入线程池并发跑,整个调度过程全部显式地记录trace。这套方式足够轻量,又能覆盖绝大多数场景。等业务复杂到需要循环、回退、人工审批时,再考虑引入状态机或者工作流引擎也不迟。
4.2 复合技能的拆分与复用
有一个经验我想推给所有人:把复杂技能拆成原子技能,然后通过编排组合出复杂能力,而不是直接写一个大而全的“万能技能”。大而全的技能就像一个写了两千行的函数,看着功能丰富,实际上每次调用热的只有一个小分支,剩余大部分代码都在拖慢响应、引入Bug、增加模型理解的熵。
拆分的粒度参照很简单:一个技能只做一件事。查询订单是查询订单,分析异常是分析异常,发送通知是发送通知。然后通过组合层把它们编成“分析订单异常并通知”这种复合流程。这样做的好处是每个原子技能都可以被单独测试、单独复用,A业务用完的查询技能,B业务可以直接拿来组合进新流程,不用重写。长期来看,技能库会越滚越厚,而复用率也会越来越高。
复合技能本身的定义也值得沉淀下来。我把复合流程描述成一个配置化的DAG Schema,里面声明了依赖哪些原子技能、节点之间的数据如何映射。这样“分析订单异常并通知”就变成了一条可查询、可审计的流程配置,而不是散落在代码里的业务逻辑。
4.3 技能冲突与优先级
当多个技能在功能上存在重叠时,调度就会面临另一个棘手问题:优先级。我参与维护的一个项目里,最初有三个技能都能完成“查积分”这件事,一个直接查数据库,一个走用户画像服务,还有一个会附带推送营销信息。三个技能出现在候选集里时,模型的选择完全是随缘的,用户有时候会收到带营销内容的积分查询结果,体验很差。
后来我们给每个技能增加了显式的优先级字段——同一个意图命中的多个技能,优先级高的先被推荐给模型,同时调度器还会在描述文本里注明“当XX技能可用时优先使用”。这种优先级机制真正确立后,不仅用户体验稳定了,排障时也轻松很多,因为命中的技能基本是可控的。技术水平相同的情况下,谁的生命周期管理做得清晰,谁就能在Agent落地时少踩大量莫名其妙的坑。
5. 常见问题与排查实录
5.1 描述不清导致模型选错技能
这是Agent技能系统里发生频率最高的问题,症状是——用户明明在问A技能覆盖的场景,模型却调了B技能。排查时不要急着怪模型,先回去看技能描述。绝大多数时候,问题出在描述写得太“技术化”,比如把技能写成“基于order_id聚合查询带有时间戳过滤条件的订单维度明细数据”,模型看到这种描述,语义离实际情况已千里之远。
我的排查思路是拿一条实际用户输入,去和职业技能描述做语义相似度对比。如果相似度排名靠后的技能被选中,那基本就是描述里缺少了与用户表达相匹配的“口语触发词”。比如用户常说“我的东西怎么还没到”,而技能描述却通篇写着“订单状态查询”,触发词完全对不上。改法很直接:在描述里补上口语化示例,比如“适用于用户询问包裹到哪了、什么时候送达、东西发出没有等场景”。我遇到过最多的性能提升手段,就是给两三个口语触发句。
5.2 参数Schema设计不合理
参数Schema太少会漏信息,太多则会压垮模型。我见过一个团队把“创建任务”技能的参数设计成了二十多个字段,其中还有不少是服务端默认值,模型每次抽取都要绞尽脑汁填完全部字段。结果参数错误率极高,用户说“帮我建个明天十点的会议”,模型却连会议室ID、参会人列表都试图去硬猜。
合理的设计原则是:必填参数绝不超过三个,其余参数全部设为可选,并给出明确的默认值说明。同时,参数类型能收窄的就收窄,比如“会议类型”字段如果只有五类取值,直接用枚举约束,模型的正确率会大幅上升。另外,参数之间可能存在联动关系,比如选择了“线上会议”就必须有会议链接,此时可以用oneOf或anyOf做联合校验,让模型理解这是一个整体。
5.3 技能执行超时与循环调用
技能本身没问题,但执行超时也是高频故障。尤其是那些依赖第三方外部接口的技能,服务方一个抖动,整个Agent对话就卡在那里转圈。不要指望大模型能处理超时异常,它只会傻等。正确的做法是在执行器层兜底:统一超时中断,然后生成一个标准超时提示返回给模型,让模型用自然语言向用户解释“查询暂时超时,请稍后重试”,整个对话流不会崩。
循环调用则更隐蔽。模型在决策时有可能反复调用同一个技能不停歇,比如用户问“查一下我昨天的运动数据”,技能返回了空结果,模型不死心,又同一个参数再调一次,进入死循环。我是在调度器里加了一个调用深度限制,程序往上追两层就会主动终结,然后强制引导模型进入澄清或结束流程。这个限制对生产环境来说属于保命配置,一定不能省。
5.4 上下文爆炸问题
技能越多,上下文越容易失控。每次技能调用结束后,原始返回数据、调用参数、校验报错信息都会一股脑塞进对话消息里。几次调用下来,单轮对话的上下文长度可能就从两万冲到了五万以上,不仅消耗资源,也稀释了模型对关键信息的注意力。
我给出的方案是分层沉淀上下文。原始技能返回不进主对话流,而是先进入一个专门的“执行明细存储区”,主对话只保留一条浓缩后的摘要消息。比如查询结果原本是一千行的订单列表,主对话里只留“共查询到12个订单,其中3个超时”,具体明细通过引用ID指向存储区。模型需要追问细节时,再用一个专门的技能去存储区取明细。这套机制把上下文膨胀的矛头直接掐断了。
6. 从能用走向好用:技能系统的迭代方向
6.1 技能质量评估与监控
技能系统上线后,最重要的事情不是加新技能,而是持续评估现有技能的质量。我维护的这套系统里,每个技能都挂着两个核心指标:调用准确率(选对技能的次数除以被召回的次数)和调用成功率(执行成功且结果有效的次数除以总调用次数)。这两个指标分别反映了模型理解的健康度和后端执行的健康度,缺一不可。
监控数据从哪来?日志是最便宜的来源。每次技能调用都按照标准格式记录一条trace,包括请求上下文、召回的技能列表、模型决策结果、参数抽取结果、执行结果、耗时。把这些trace聚合到看板上,你就能一眼看出哪些技能经常被错误召回、哪些技能参数失败率高、哪些技能响应慢得像老牛拉车。我每个迭代周期都会按照失败优先级杀掉一批表现差的技能,或者拉回重写描述,这比不停堆新技能要重要得多。
6.2 技能版本管理与灰度发布
Agent技能的迭代比普通后端接口更敏感,因为一个小小的描述改动可能直接影响模型在所有场景下的行为。我踩过最大的坑,是把一个技能描述从“查询订单状态”改成“查询订单状态并汇总超时风险”后,所有原本只做状态查询的用户请求,都被模型自动追加了风险汇总流程,用户端体验直接失控。
所以技能系统一定要有版本管理。我现在的做法是:每个技能定义都进版本库,改动一律走“草稿→审核→灰度→全量”的流程。灰度只对百分之五的流量开放,拉出调用准确率和用户反馈对比后正常才上全量。一旦发现异常,可以秒级回滚到上一个版本。这项能力建议在技能系统建设的第一天就引入,不然后期补账会非常痛苦。
6.3 技能自动生成与主动学习
演进到后期,我的目标是让系统具备半自动化的技能生成能力。具体实现是:当模型在对话中发现用户表达了某个重复出现的、但当前技能列表无法覆盖的需求时,系统会记录一条“技能缺口”日志。运营或开发者定期查看这些缺口日志,用我们预设的技能模板半自动生成新技能定义,审核后上线。这相当于用真实用户需求驱动技能库的生长,而不是靠团队臆想未来需要什么技能。
另外,用户的纠错行为也是宝贵的学习信号。用户明确说“我不是要查快递,我是要查退款进度”时,这条消息连同当时模型选中的技能一起被记录成一条负样本,定期汇总后用来微调召回排序策略。这套主动学习闭环是我觉得agent-skills接下来一年最值得投入的方向,谁先把这条闭环跑通,谁就能在Agent能力的可持续迭代上抢到身位。
最后聊点实在的
如果从头再做一次agent-skills,我会把更多精力放在技能描述和评估体系上,而不是急着做复杂编排。描述决定模型的选型准确率,评估体系决定系统能不能持续变好,这两件事是一切的根基。至于框架选型、底层通信、并发模型,都是相对成熟的工程问题,反而是“让模型在正确的时候用正确的手”这件看似简单的小事,最值得反复打磨。
还有一点想特别提醒:技能系统一定要留好可观测性的口子,从第一天开始记录trace。等你调试某个极难复现的“模型为什么不调A技能”问题时,一份结构清晰的全链路trace比任何调试工具都管用。这就是我这次重构最大的心得,也是希望你不用踩一遍才知道的事。