
“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比任何调试工具都管用。这就是我这次重构最大的心得也是希望你不用踩一遍才知道的事。