☰
智能体技能体系实战:从工具调用到agent-skills注册与编排
2026/10/14 9:40:31 网站建设 项目流程

做智能体这么久,我最强烈的感受是:真正拉开项目差距的,往往不是模型选得多强,而是你围绕模型搭的那套"技能体系"到底靠不靠谱。之前我们团队开发过一个客服知识助手,一开始只是把工具函数硬编码在业务代码里,模型想调就调、调错了也没人管,上线不到两周就被各种诡异行为折磨得焦头烂额。后来把项目重构为以 agent-skills 为核心的技能注册与编排模式,整个系统的稳定性和可维护性才真正立了起来。这篇文章就是这次重构的完整复盘,包括概念辨析、注册表设计、编排模式、踩坑记录,以及一套可以直接套用的评测指标。无论你是刚开始接触智能体开发,还是已经在生产环境里被工具调用折腾过,这篇都值得参考。

1. 先从最实际的困惑说起:技能和工具到底有什么区别

1.1 一次失败的"工具集成"给我的教训

我们第一次做工具集成时,想法很简单:把外部能力封装成一个个函数,塞进大模型的工具调用接口里,让模型自己选、自己调。

刚开始确实跑得通,模型知道在某些问题上调用某个函数。但很快问题就来了:同一个函数在不同场景下的行为预期完全不一样。比如"创建工单"这个动作,在用户投诉场景里需要选择紧急程度,在售后回访场景里要附带客户满意度记录——同一个函数,参数需求不同,失败后的处理策略更不同。我们只能不断在函数体内堆 if-else,函数越来越臃肿,模型也越来越容易选错。

这时候我才意识到,工具是死的,技能是活的。一个真正的"技能",应该是"能做什么 + 什么条件下做 + 怎么做 + 做砸了怎么办"四位一体的封装,而不仅仅是暴露一个可以被调用的函数签名。

1.2 技能 = 工具 + 约束 + 行为策略

我用一张简单的对比表来说明这个区别:

维度普通工具函数技能(Skill)
描述一句话说清功能说清功能、适用场景、触发条件、边界情况
参数定义松散的字典严格校验的 Schema,带枚举和依赖规则
执行反馈成功或抛异常结构化回报,包含节点信息和恢复线索
失败处理调用方自己 try技能内部定义降级策略和重试边界
状态管理无支持会话级上下文和跨步骤记忆
评估方式看单元测试看触发率、成功率、误用率、恢复率

所以把"工具库"升级成"技能库",并不是换个名字,而是把模型外部的执行能力当成一个需要设计、测试、运营的产品来对待。

2. 技能注册表:全项目里最容易被低估的那张"地图"

2.1 注册表里应该写什么

技能库的核心是一张注册表。我们初期以为注册表就是个名字和函数的映射表,重构后才发现,这张表的字段设计直接决定了模型能不能在正确的时间选中正确的技能。

我们最终使用的注册条目结构包括以下几个关键字段:

  • skill_id:唯一标识,比如order_refund_v2,命名建议带领域和版本。
  • name:给人看的短名称,也是模型在推理时最容易抓取到的语义锚点。
  • description:触发说明。这块不是给开发者看的,是给模型看的。必须写清楚"什么时候用、什么时候坚决不用"。
  • input_schema:严格按照 JSON Schema 规范定义,包含类型、必填项、枚举值、字段间依赖关系。
  • execution_scope:该技能适用的会话阶段或业务域,比如"售前阶段""售后阶段""内部复盘"。
  • side_effects:执行后会对系统外部产生的副作用,比如"会发送短信通知""会扣减库存"。
  • fallback_strategy:执行失败后的行为,包括重试次数、降级路径、是否需要人工确认。
  • visibility:可见性控制,有些技能只对特定模式开放,有些需要隐藏。

我从踩坑中得到的体会是:description 和 side_effects 是注册表里最关键的字段,但也是最容易被写废的字段。如果你在 description 里只写"处理退款",模型根本分不清它和"处理退货""处理补偿"有什么区别;如果你不写明 side_effects,模型可能会在用户还没最终确认时,就默默调用了扣款的技能。

2.2 为什么要"运行时的能力声明"

注册表本质上是一份"元数据地图",但真正让技能体系变得可扩展的,是运行时动态获取能力列表,而不是写死在代码里。

做这件事的时候,我们只是把"能够被调用的技能集合"设计成一个过滤器:根据当前会话的模式、用户的身份、业务的阶段,动态决定哪些技能对模型可见。

举个例子,一个访客在未登录状态下问"帮我查一下我的订单",这时"查订单"技能就不应该出现在候选列表里。与其等模型选错了再去拦截,不如在源头就把当前不可用的技能藏起来。这不仅减少了模型的决策空间,还显著降低了幻觉调用的概率。我们把这一步称为"能力门禁",它处于整个技能体系的最前端。

2.3 描述质量直接决定模型能否触发

有时候模型没有调用任何技能,不是因为它笨,而是因为证据不足。我们把一条技能描述拆成了三层:

  • 第一层:功能主句。例如"用于查询订单物流轨迹"。
  • 第二层:触发场景列举。例如"当用户询问包裹到哪了、什么时候送达、物流是否异常时使用"。列举最好带用户话术风格,而不是干巴巴的名词。
  • 第三层:排除项。例如"当用户只是咨询退货政策,并没有下单时,不要调用此技能"。

同样的技能,描述从"查询物流"扩写为上面三段后,模型触发准确率提升了将近三成。不要小看写描述这件事,它是在用自然语言给模型画决策边界,比任何一行代码都更重要。

3. 三种技能编排模式:串行、条件分支与并行聚合

3.1 串行链:最简单的编排,但小心上下文污染

技能并不总是单个调用,更多时候需要多个技能协作完成一个任务。最常见的编排方式就是串行:技能 A 的输出作为技能 B 的输入。

举个例子,一个"工单自动分类"流程:

  1. 调用"文本清洗"技能,把用户来信去噪、提取关键诉求。
  2. 调用"意图分类"技能,识别用户想投诉、咨询还是表扬。
  3. 调用"优先级判定"技能,结合会员等级和问题紧急程度给出处理等级。
  4. 调用"模板回复"技能,生成回复草稿。

串行链实现起来很简单,但一个容易忽视的问题是上下文污染。技能 A 返回的中间结果如果不加筛选地全部塞回给模型,模型后续推理时可能会被无关字段干扰。我们后来给每个串行节点加了一个filtered_output字段,只把后续节点真正需要的数据往下传,模型的表现明显干净了很多。

3.2 条件分支:把路由决策交给模型还是交给规则

有些流程不是简单的一路到底,而是要根据中间结果走不同的分支。这里有一个设计分岔路口的经验:能写规则的不要交给模型,模型只在规则难以穷举时才上场。

比如在"退款申请审核"流程中,判断"金额是否超过阈值"这种事就适合用代码直接分支处理,在并行执行多个互不依赖的技能时,时间收益非常明显。比如一个"新用户尽调"流程,同时调用了"黑名单库检测""注册信息核验""行为风控评分"三个技能,三者之间没有互相依赖,完全可以并行发起。

但并行也带来了新问题——结果聚合。每个技能返回的置信度不同、给出的结论可能矛盾,聚合逻辑做得不好,整个流程就会变成"听最后一个返回的"。我们最终的方案是引入一个"结果仲裁节点",它像一个小组会议的主持人,负责按预设优先级和置信度阈值把并行的结果揉成最终结论,而不会让模型在杂乱结果之间随意选择。

4. 从0到1搭技能库:一份可以直接抄的清单

4.1 从业务场景倒推技能边界

很多团队上手就先罗列一堆功能点,然后硬塞成几十个技能,这种思路在后期会栽跟头。应该从真实业务场景倒推技能边界。

具体做法是先收集高频用户请求,逐条拆解成"能力需求卡片",每条卡片包含:

  • 用户原话示例
  • 完成该请求需要哪些原子能力
  • 哪些原子能力总是同时出现,可以聚合成一个技能
  • 哪些能力之间存在顺序依赖

比如在"客服助手"项目里,频繁出现用户问"我的包裹怎么回事",拆解后需要:查询订单状态、核实物流节点、判断是否超出时效、生成解释文案。这四个能力里,前三者经常一起出现,于是合并为"订单物流诊断"技能;生成文案则单独保留为一个小技能,因为它在投诉处理里也要用。

这样聚合出来的技能边界是以业务场景为锚点的,注册表和场景文档对得上,后续你给模型写描述也会顺手得多。

4.2 写技能描述的黄金句式

我见过很多团队把写描述当填空题,一句话带过。这是一个长期低估的环节。标准化以后,我们的技能描述基本固定为这样的句式:

技能目标的定义:用于完成什么。 适用条件:只有在用户表现出这些特征时才调用。 不适用条件:遇到这些信号时必须拒绝调用。 特殊说明:需要用户确认才能执行、执行后有副作用等情况。

这套句式写起来略啰嗦,但收益极其明显。因为模型在决定"要不要调用"时,本质上是在做一次短文本分类,你给它的正例和反例越多,分类边界越清晰。注意"不适用条件"这点尤其重要,很多人不舍得写,总怕限制了模型,实际上没有负例的描述就是一扇什么风都可以吹开的门。

4.3 参数 Schema:既要严格,也要宽容

参数 Schema 是技能动态执行的入口,格式上的一个低级错误就足以让整个流程中断。我们的经验是:在 Schema 层面敢做严格约束,在解释层面保留适度宽容。

严格约束包括:

  • 明确必填项,禁止模型自作主张给可有可无的字段填默认值。
  • 用enum限定取值空间,特别是状态类、类型类字段。
  • 用required数组做级别控制,避免关键字段缺失。
  • 对多个字段之间的依赖关系(如选了"实物退款"必须填"退款渠道")用规则校验。

宽容解释包括:

  • 模型传参时可能把"2025-03-01"写成"3月1日",解析层需要提前做归一化。
  • 模型有时会漏掉嵌套结构里的某个字段,解析层要能容忍并补默认值。

我建议每个技能至少写一个"参数处理管线":先做格式归一化,再做 Schema 校验,再做业务规则校验,最后才真正执行。前面每个环节失败都能返回结构化的错误信息,而不是抛一堆原始异常。

4.4 技能测试与回归集

技能库是不折不扣的"代码资产",当然要有自己的测试体系。但技能的测试比函数测试复杂,因为它最终要通过模型决策才能被触发,所以我们要测两层。

第一层是触发测试:把一批用户话术输入系统,检查模型是否正确选到了预期的技能。这层测试会逐条记录"应该触发但没触发"和"不应该触发却触发了"两类样本,这些样本是我们后续优化描述的弹药。

第二层是执行测试:直接绕过模型,带固定参数调用技能,验证返回结构、副作用、降级策略是否符合预期。

我们维护了一个"影子测试集",里面收录了几百条真实用户脱敏的提问。每次技能库结构有变动,就先跑一遍这两层测试,再决定是否部署到热环境。这套回归机制为我们节省了大量夜间运维的心力。

5. 运行期最常炸的三个问题:参数幻觉、技能冲突、超时风暴

5.1 一次完整的排查链路:三个问题同时出现

先生动描述一次事故。某个凌晨,我们的技能库流量突然异常,注册表监控显示所有技能的调用量剧烈波动,部分调用直接返回错误。排查链路大致如下:

第一步,观察报警面板,发现失败全部集中在订单中心两个技能上。第二步,查看执行日志,发现大量请求把参数传成了字符串,进一步解析发现模型输出了一个{"order_id": "张三的订单"}这种明显不合理的值。第三步,回溯到提示词上下文,发现上一步的对话里同时存在"订单号"字段和"用户名"字段,模型混淆了。第四步,再往深处看,发现同一个会话里有两个技能都声明了自己是"查订单",模型在两个技能之间反复摇摆,最终选了一个参数要求适配度更低的。

这三个问题串起来,本质上是一次典型的运行期事故:参数幻觉加技能冲突,再叠加超时,形成了一场雪崩。

5.2 参数幻觉的根因与对策

参数幻觉的直接表现:模型调用了技能,但传参明显不准确,甚至把语义完全不同的文本塞进了参数里。这种现象的诱因往往有三个:

  • 提示词上下文中与字段名相似的词太多。
  • 期望值不在枚举列表里,模型没有约束就强行填入自己的推测值。
  • 技能描述和功能上勉强相关,模型在犹豫状态下"硬调"。

我们的对策是多重保险:

  • 在提示词层面给模型对字段的解释增加说明,特别是易混淆字段。
  • 在参数 Schema 层配置更细的校验并配合归一化。
  • 在编排层增加"调用前确认"环节,当置信度低于阈值时,让模型先把待调用的参数组合呈现给用户确认,而不是闭眼提交。

5.3 技能冲突,不只是参数冲突

两个技能回归到同一个业务动作,是项目规模扩大后的必然结果。冲突有两种:一种是"能力重叠",指两个技能都声称自己处理同一类请求;另一种是"意图漂移",指模型的意图分类发生变化后,原本清晰的分工开始变得模糊。

处理的核心手段是在注册表里引入明确的"技能不适用条件"与优先级编号,并定期检查注册表里的技能描述,看是否存在语义重叠。

我们用了一个很简单的聚类工具,把当前技能库里的 description 全部抽取出来做向量对比,凡是相似度超过预设阈值的,都会进入人工审视队列。这是技能库"技术债"的预防性清理方式,非常推荐定期执行。

5.4 超时风暴与容错降级

智能体外部依赖一旦有变动,技能调用就可能卡住。如果基础超时设置得过长,一个节点落后会拖垮整条链。我们最终使用了"分级超时 + 熔断降级"的方案:

  • 基础级外部调用超时设置在一个较短的合理值,例如 2 秒。
  • 编排节点级别的超时设置为外部调用的两倍。
  • 全局链路超时约束明确,超过即刻返回降级提示。
  • 一旦连续失败率上升,自动熔断该技能一小段时间,改为直接走人工或低配路径。

熔断对用户感知的影响要尽量小,比如查订单失败时,可以返回"暂时无法查询,请稍后重试"而不是抛出一个技术错误。这个方案上线后,凌晨故障数量降到了原来的零头。

6. 技能库健康度:我拿这四个指标做周报

6.1 四象限评估体系

技能库不能靠感觉判断健康程度,以下是我目前每周都看的四项核心指标,也强烈推荐给正在做类似项目的团队:

指标定义警戒线说明
触发准确率实际正确触发数 / 应该正确触发数低于 85% 需优化描述检测模型能否在正确时机选对技能
执行成功率技能成功完成一次完整执行 / 总调用次数低于 90% 需要关注检测技能内部逻辑与依赖是否稳定
误用率不应该触发但触发了的次数 / 总调用次数超过 5% 必须处理往往意味着描述边界不够清晰
平均决策延迟从用户请求到技能执行的间隔超过预设阈值需优化包括模型推理和路由时间

四类指标互为约束:只看触发准确率而忽略误用率,模型可能会被调教成"宁滥勿缺",表面上准确率涨了,实际上垃圾调用也涨了。务必一起看。

6.2 指标背后的行动项

只有指标不能解决问题,关键在于指标变化后的行动项。

  • 如果触发准确率低,翻出最近一百条"该触发未触发"样例会显示雷同特征。比如技能描述里缺少用户常见说法。
  • 如果误用率高,多半是"排除项"写得不到位。我会把误用样本逐条补齐进技能描述的负例里,更新后重新跑回归。
  • 如果执行成功率低,优先排查外部依赖的可用性。这通常和技能本身的逻辑实现关系不大,而是下游系统的问题。
  • 如果决策延迟高,考虑压缩上下文、裁剪候选技能列表,或者把部分前置工作从模型切换到规则。

这些指标最好能和真实的会话日志联动。我们的做法是,每一条有效会话都写入轻量的结构化日志,记录当前使用过的技能、触发置信度、执行节点耗时、错误码。周报里既能看到整体趋势,又能一键下钻到异常会话,定位到具体是哪一次对话里模型误用了技能。

7. 写在最后:技能体系的本质是"把不确定性变成可管理"

从工具调用升级到 agent-skills 体系,我们最终收获的不仅仅是稳定性提升了、故障变少了,更关键的是项目复杂度变得可理解、可控制。技能注册表是一张地图,编排模式是交通规则,测试集是体检单,四象限指标是仪表盘——当这四样东西都到位,智能体的行为就不会再是一个黑盒子。

最后分享一个我个人很偏爱的实践习惯:每个技能都有一个独立的"负责人"。哪怕技能只是几百行代码,也必须有一个人对它的描述质量、Schema 草稿、回归集、故障记录全权负责。技能库本质上其实是一个微型的"知识产品"库,每一份技能在发布时都应该有作者、有版本、有变更记录。这样一来,团队协作时可以让技术债和责任心对齐,而不是所有问题都推给"模型太笨"。

如果你正在搭建自己的智能体技能体系,我建议从一个小场景、两三个技能开始,先把注册表、描述标准、回归测试这几个基础桩打牢,再逐步扩到几十个技能。底层根基立稳以后,你会发现技能数量增长本身并没有想象中那么痛苦,真正需要长期维护的,反而是那些看不见的元数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询