☰
Agent技能化实战:从Function Calling到多技能编排的完整指南
2026/10/14 8:21:49 网站建设 项目流程

最近半年,我明显感觉圈子里聊 Agent 的方式变了。去年大家还在比谁的 Agent 能"多轮对话不跑偏",今年已经开始讨论"这个 Agent 拥有哪些技能、技能怎么编排、技能效果怎么评估"——一个叫agent-skills的话题在讨论中反复出现。

说白了,现在做 Agent 已经不再是一个大模型硬撑所有任务的阶段了。你让一个模型自己"悟"出怎么查数据库、怎么写代码、怎么操作浏览器,既不靠谱又难迭代。更务实的做法是:把 Agent 的能力拆成一个一个可以被独立开发、独立测试、独立复用的“技能”模块,然后像拼乐高一样把它们组装成一个完整的智能体系统。这篇文章我就围绕agent-skills这个方向,把我最近踩过的坑、总结出的设计原则、以及一套可以直接落地的搭建方案完整拆给你看。

不管你是刚接触 Agent 开发的新手,还是已经在生产环境里被各种边界问题折磨过几轮的工程师,这篇文章都能给你一些可复用的思路。

1. 为什么 Agent 开始强调"技能"而不是"能力"

1.1 从单模型到技能组合的演进逻辑

先聊一个转变。去年我接手一个内部客服项目时,第一个版本的做法非常简单:把用户问题、相关文档、历史工单全部塞进上下文,让一个大模型直接生成回答。初期效果还不错,因为问题类型相对集中。但一到多业务线、多场景之后就崩了——不同业务线的术语体系不一样,回答风格要求不一样,连可用的外部工具都不一样。每次加一个新业务线,整个 Prompt 都要重写一遍,维护成本直线上升。

后来我换了一个思路:不要求一个模型"什么都会",而是把能力拆成最小单元。比如"查订单状态""计算运费差价""生成退款审批单""判断用户情绪是否需要升级人工"……每一个最小单元就是一个技能。每个技能拥有自己独立的 Prompt、独立的工具配置、独立的评测用例。Agent 要做的事情,从"回答所有问题"变成了"根据当前上下文,挑选并执行合适的技能"。

这个转变本质上跟软件开发历史上的"单体应用拆微服务"是同构的:单体大模型不是不能工作,而是当业务复杂度上去之后,一个模型内部什么都耦合,你改一个功能就要重新全量评测,风险不可控。技能化之后,每一个技能的改动都可以独立验证、独立灰度、独立回滚。

1.2 技能化带来的工程收益与复杂度代价

技能化的好处非常直观:

  • 可组合:同样一个"查天气"技能,既可以放在旅行规划 Agent 里,也可以放在日程管理 Agent 里,不用重写逻辑。
  • 可单独优化:某个技能效果差,只需要调整那个技能内部的 Prompt 或工具配置,不会影响其他技能。
  • 可独立评测:每个技能有自己的通过率和失败率指标,效果好坏一目了然。
  • 可复用:团队之间可以通过技能市场共享技能,避免重复造轮子。

但代价也很现实:系统复杂度会明显上升。你多了一个"技能注册中心",多了一套"技能路由与调度机制",还多了技能之间的依赖管理问题。如果你只有三个技能,这个过程不太值得;但当你走到 15 个以上技能时,这种复杂度付出是划算的——我自己在实际项目中体会到的临界点是 12-20 个技能。少于这个数量,单体设计反而更快;超过这个数量,单体设计的维护成本会指数级上升,技能化才开始真正发挥价值。

2. 技能体系的核心组成与关键设计原则

2.1 工具调用层:Agent 的"手"从哪来

技能的本质,是让 Agent 在特定场景下调用特定的工具完成一个具象任务。而工具调用层就是技能的"手"。

我的实践是用一套统一的函数调用接口(Function Calling)来规范化所有工具的输入输出。把每个工具封装成一个 JSON Schema,描述它的名称、参数、返回值、错误码,然后让模型根据 Schema 决定要不要调用、传什么参数。这个设计的好处是:新增一个工具只需要注册一个 Schema,不需要改任何核心代码。

举个我实际用过的 Schema 例子:

{ "name": "query_order_status", "description": "根据订单号查询某个订单的当前处理状态", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如 SO20250110001" } }, "required": ["order_id"] } }

这里有两个关键点容易被忽略:一是 description 要写得足够具体,包括参数格式的例子,因为模型是靠描述来理解何时调用的;二是 required 字段务必明确,不然后续参数幻觉问题会让你排查到怀疑人生。

2.2 记忆系统:技能执行的上下文底座

很多人在设计技能时只关注"这个技能怎么执行",却忽略了一个前提——技能需要上下文。比如"查订单状态"这个技能本身只需要订单号,但 Agent 怎么知道用户的订单号是多少?可能是用户刚说的,可能是历史会话里提到的,也可能是通过某个前置技能从数据库里查出来的。

因此,一个健壮的技能体系必须搭配一套记忆系统。我把它分成三层:

  • 短期工作记忆:指当前对话窗口里的信息。实现上就是你的上下文管理模块,负责把最近几轮的用户输入、Agent 内部推理摘要、技能执行结果做有效的组织和压缩。
  • 长期语义记忆:指跨会话的信息,通常用向量数据库存储。例如用户上次在哪个城市、习惯用哪种支付方式、历史投诉记录等。
  • 结构化事实记忆:指用户档案、业务字典、权限标签这类强结构信息,我一般单独存放在一个可快速查询的 KV 存储中,不走向量检索。

记忆系统的设计直接决定技能的"准头"。我踩过一个很典型的坑:技能本身写得很好,但因为没有长期记忆支撑,用户第二次进线时 Agent 把所有信息都忘了,不得不反复追问用户,体验极差。加了一层向量记忆之后,重复澄清率下降了接近一半。

2.3 规划与拆解:技能编排的灵魂

有了单个技能,下一步就是让 Agent 知道"什么时候用哪个技能、多个技能怎么串联"。这是规划层要做的事。

当前主流的规划范式有两种。一种是 ReAct 式的边想边做:模型在每一步根据当前观察决定下一步动作,灵活但容易在复杂任务中迷失方向。另一种是 Plan-and-Execute 式的先计划后执行:模型先拆出一个多步计划,然后再逐步执行每一步。我自己的取舍是:任务链路短(少于 3 步)且状态空间小的情况下用 ReAct 式;链路长、状态多的场景必须用 Plan-and-Execute 式,否则中期很容易跑偏。

举一个实际的例子:一个任务"帮用户办理商品退货并申请运费补偿",我拆成了 5 步:

  1. 调用verify_order验证订单是否在可退期内
  2. 调用check_item_status检查商品是否已签收
  3. 调用create_return_order创建退货单
  4. 调用calculate_refund_amount计算退款金额(含运费补贴规则)
  5. 调用notify_user发送结果通知

前 4 步之间有严格的依赖关系——第 3 步依赖第 1、2 步的结果,第 4 步依赖第 3 步生成的退货单号。如果模型不先规划而直接硬跳,很容易出现参数无法补全、流程被跳过等问题。所以我给这套任务内置了一张"技能依赖图",每次执行时先做拓扑排序,确保每一步的前置条件都满足。

2.4 自我反思与纠错:技能闭环的最后一环

一个技能执行失败后怎么办?很多初版 Agent 就直接把错误信息抛给用户,这体验太糟糕了。一个成熟的技能体系必须有自我反思与纠错机制。

我在实际项目里加了一层轻量的反思模块:每次技能返回异常后,Agent 会先做一次失败归因,判断错误类型。

错误类型大致有这几类:

错误类型典型表现处理策略
参数缺失模型没传必填参数根据上下文补充后重试一次
参数幻觉模型传了不存在的参数值用 Schema 校验拦截,重新生成
工具异常上游接口报错、超时降级到备用工具或更换方案
业务规则冲突订单状态不允许退货终止流程,转为人工接管
推理错误模型误判了用户意图回溯上下文重新做意图判断

这层反思逻辑跑通之后,我那个客服 Agent 的技能执行成功率从 68% 提升到了 84%。注意,这里不需要做太重的复杂推理链路,只需要一个轻量级的失败分析和重试判断即可——太重的话会显著拉长单次请求的响应时间。

3. 从零搭建一个技能化 Agent:选型与实操

3.1 技术栈选型:为什么我选了这个组合

先交代一下我的技术选型,再解释为什么这么选。

我采用的组合是:Python 后端 + 一个通用 LLM 框架做对话编排 + 一套自研的技能注册中心和路由调度器 + Redis 做短期会话缓存 + 一个向量数据库做长期记忆存储。LLM 的模型层面,我用了一个支持 Function Calling 的通用模型,没有绑定某个封闭平台的专有接口,方便后续替换。

为什么这样选?原因有三点:

第一,Python 在 AI 生态里的工具链最完整,无论是函数调用解析、向量检索还是数据处理,都有成熟的库,可以让我把精力花在技能逻辑本身而不是基础设施上。第二,通用 LLM 框架解决的是"对话编排"这件事,它帮你处理好会话管理、工具调用循环等基础能力,但技能怎么组织、怎么路由、怎么评测,框架层并没有标准答案,所以我保留自研的空间。第三,Redis 和向量数据库都是经过大量生产验证的组件,稳定性优先级高。

如果你是从零开始,我不建议一开始就用重型的 Agent 编排框架。先手写一套轻量的调用循环,理解每一层在干什么,之后再迁到成熟框架上会更扎实。

3.2 技能注册与调用的最小实现

一个技能注册中心需要管理三个东西:技能元信息、技能实现、技能的依赖关系。元信息描述"这个技能是什么、何时该被调用、参数长什么样";技能实现是实际执行逻辑的代码;依赖关系用于规划调度。

我习惯用一份统一的技能描述文件来管理元信息,格式是 Markdown 加 JSON Front Matter,像这样:

--- name: order_issue_handler description: 处理订单相关的退货、退款、物流查询、售后申请等全链路问题 primary_triggers: ["退货", "退款", "换货", "订单异常", "售后"] requires_skills: ["verify_order", "check_item_status", "create_return_order"] timeout_ms: 15000 ---

这份描述文件会被加载到技能注册中心,然后通过路由调度器在运行时被检索。技能实现的代码无需关心注册逻辑,只需实现一个统一的接口:

class BaseSkill: async def execute(self, context: SkillContext) -> SkillResult: raise NotImplementedError

这样一个最小闭环就出来了:用户发起问题 -> 对话编排层做意图识别 -> 路由调度器匹配技能 -> 技能执行 -> 结果返回给模型 -> 模型组织最终回答。

3.3 让 Agent 学会"挑"技能:路由与匹配策略

技能多了之后,最核心的问题变成:给了一段用户输入,怎么挑出正确的技能?

我的方案是"双通道匹配":第一通道是关键词和内嵌在技能描述里的触发词精确匹配,很轻、很快,能覆盖大多数高频场景;第二通道是语义向量检索,把用户输入和技能描述各自编码成向量,用相似度作为补充召回依据。最后把两个通道的结果合并,按置信度排序,取 Top 1 作为选中技能,Top 3 作为候选让模型做最终裁决。

这里的细节很关键:不要把路由完全交给模型自由选择。模型自由选择在 10 个技能以内还可以,超过之后就经常出现"凭感觉选一个貌似合理的技能",而不是"选择真正正确的技能"。通过对候选列表做约束,把自由选择变成有范围的选择,准确率会高很多。

我实际跑过一组对比数据:纯模型自由选择技能,10 个技能时准确率约 82%;20 个技能时下降到 65%;而用"双通道召回 + 模型在候选里裁决"的方案,20 个技能时准确率还能维持在 85% 左右。

3.4 多技能协作的编排示例

我们用一个稍复杂的场景完整演示多技能协作的编排逻辑。

场景:用户说"我刚买的那双鞋尺码小了,想换一双 42 码的,顺便帮我看下换货后能不能用优惠券抵扣运费。"

这个请求表面上是"换货加运费优惠咨询",但仔细拆解,实际涉及 4 项技能:

  1. query_order:定位用户刚买的那双鞋对应的订单
  2. check_exchange_policy:确认该商品是否在换货期内、是否支持改尺码
  3. get_coupon_balance:查询用户是否有可用的运费优惠券
  4. create_exchange_request:根据前面三步的结果生成换货申请

我的编排器先执行规划,生成步骤图:首先并行执行第 1 和第 3 步(它们之间无依赖),然后执行第 2 步(依赖第 1 步拿到的订单号),最后执行第 4 步(依赖 1、2、3 的结果)。如果第 2 步发现商品不支持换尺码,则整个流程直接终止,转到人工客服。

这个编排过程看起来像是"想当然",但真正上线之后你会发现模型经常漏拆步骤,或者把第 2 步和第 4 步合并成一步执行,结果参数缺失。关键解法是给编排器加一个"必选技能校验"——在第 4 步执行前检查 1/2/3 的结果是否都已经存在于上下文里,不满足就强制补跑缺失步骤。我建议你在做多技能编排时把这个校验机制当作标配。

4. 实测中的意外情况与避坑记录

4.1 工具参数幻觉:Agent 传错参数的根因与修复

这是我遇到的最高频的问题,没有之一。现象是:技能方法名选对了,但传入的参数跟用户描述完全不匹配。比如用户说"帮我查一下上周买的那个耳机的订单",模型却给query_order_status传了order_id = 上周买的那个耳机,而不是在上下文里查找真正的订单号。

根因是:模型没有"参数物化"的能力——它不会自动把自然语言中隐含的信息映射成结构化的参数值。它把用户在句子里的原始文本直接当成了参数值。

我的修复方案是分两步:

第一步,在前置上下文里显式放入一个"实体抽取"步骤,把用户的订单号、商品名、金额等实体先抽取成结构化的槽位,再让技能调用器从槽位中取值。第二步,在技能执行前加一道 JSON Schema 校验,拦截明显不符合格式的参数——比如order_id不符合SO开头加数字的格式时直接判断为幻觉参数,触发重试。

这两步做完之后,参数幻觉导致的失败率从 12% 降到了 3% 左右。但注意,3% 仍然客观存在,线上必须有兜底机制,不能让模型直接把这个报错暴露给用户。

4.2 长上下文下的技能遗忘

另一个让我头疼的问题是:多轮对话进行到中后段,Agent 会开始"忘记"自己已经执行过的技能及其结果,导致重复执行或者引用过期数据。

典型的错误场景是:用户在第 5 轮问"那我刚才查到的那个订单能退吗?",Agent 此时应该直接引用第 2 轮查到的订单号和当前状态继续推理,但它却重新发起了一次查询,拿到了一模一样的数据,还多花了时间。

这个问题的本质是上下文压缩做得不够好。我在实践中摸索出一套方案:每执行完一个技能,就把"技能名 + 关键输出摘要"以结构化形式写回短期记忆的显式区域,并在后续 Prompt 中始终保留这个区域。相当于给 Agent 做了一个“工作台日志”,让它随时知道前面发生过什么。对于更长链路的任务,还可以在每步推理时附带当前步骤号的进度状态。

经过这个改造后,重复执行技能的次数明显减少,响应时间下降了 20% 左右,同时减少了无用 API 调用,节省了一部分成本。

4.3 并发调用时的竞态问题

当多个技能需要并行执行时,竞态问题容易冒出来。最典型的案例是:两个技能同时尝试修改同一个用户的优惠券抵扣状态,结果后执行的那个把先执行的那个状态覆盖掉了,最终用户被错误地扣了两次运费。

这个问题在初版架构中非常隐蔽,因为单看每个技能的单元测试都是通过的。后来我在并发控制层加了一个基于业务键的分布式锁,以用户 ID 加资源类型作为锁粒度。比如user:123456:coupon_state在任意时刻只允许一个技能获取写锁。锁的持有时间设置了 10 秒超时,避免某个技能卡住导致死锁。加完锁之后,这类竞态问题再没出现过。

另外一个经验是:尽量不要让两个技能同时写同一个业务资源,能用"读取-计算-合并"模式就优先用,让多个技能返回中间结果,由一个最终技能统一落库。

4.4 安全边界:权限控制与能力约束

技能化的另一面是攻击面扩大了。以前你只有"模型 + 一套 API",现在你有几十个技能,每个技能都是一个潜在的风险入口。

我列出三个必须处理的安全问题:

一是技能级权限校验。每个技能需要声明自己的最小权限范围,路由调度器在调用技能前先检查当前会话的授权级别。比如query_order只允许读取本人订单,create_refund需要额外的高权限标记。这块不能在技能代码里做,必须在调度层统一拦截,否则很容易出现漏判。

二是技能暴露的敏感数据脱敏。技能返回给模型的结果里,手机号、银行卡号、地址等敏感字段必须先做脱敏处理,再进入模型上下文。模型本身不需要知道完整的敏感信息,它只需要知道"是否有这个信息、是否验证通过"。

三是工具调用的频率与限额控制。一个技能不应该被无限次调用。我做了双层限额:单会话内同一技能的调用次数上限,以及全局的每用户每分钟调用频率限制。防止恶意用户通过构造多轮对话反复触发高成本的技能调用,把你的 API 预算打爆。

这些安全措施不会提升技能的智能程度,但没有它们,技能体系根本走不到生产环境。

5. 技能效果的测评与持续迭代

5.1 不是所有技能都该进生产环境

技能化一个容易被忽略的好处是:你可以非常方便地对单个技能做灰度评估,而不是等到全链路集成后才发现问题。

我在每个技能进入生产环境之前会要求它通过三道关卡:

第一道是单技能评测。用 100 条以上的真实历史问题构造测试集,要求技能单独执行时达到设定的指标线(比如关键字段提取准确率不低于 95%)。第二道是链路评测。把这个技能放进真实的任务链路里,用端到端的测试样例验证它不会破坏上下游其他技能的执行。第三道是影子环境验证。把一个新版本技能部署到影子环境,让它处理线上真实流量的复制流量,对比新旧版本的输出质量。

这道流程看起来繁琐,但确实能把大多数问题挡在生产环境之外。我接手项目早期因为省掉第二道关卡,上线了一个新技能,结果它和旧的订单查询技能在路由上产生冲突,导致有 20% 的流量被错误路由,花了整整两天才排查清楚。

5.2 评测集设计与回归机制

评测集的搭建也有讲究。第一个教训是:不能只用标准示例评测,必须加入"边缘骚扰样本"。比如针对查单技能,你要准备好以下类型的测试输入:

  • 用户给了完整且正确的订单号
  • 用户只给了模糊的购买时间描述
  • 用户给的订单号不存在
  • 用户同时问了两个订单但想做不同操作
  • 用户使用了错别字、口语化表达

第二个教训是:评测集必须能沉淀,并且每次技能迭代后做回归。我维护一个"技能回归样例库",假设一个技能需要修改内部 Prompt,修改后第一件事就是跑全量回归,对比通过率有没有回退。这个机制保证了你不会出现"修好一个技能、弄坏另一个技能"的连锁反应。

5.3 从日志里挖技能短板的方法

最后说一个非常实操的环节:怎么用线上日志持续发现技能的短板。

我的做法是给每个技能的执行结果打上标签,分为success、retry_success、fallback_success、failed、escalated_to_human五档。然后每天对failed和escalated_to_human的样本做一次人工抽检,按失败原因聚类。

常见的聚类结果一般有几类:一类是路由匹配错误——用户意图明明存在对应技能,但调度器没有匹配上;一类是技能内部逻辑问题——比如某业务规则变更后技能没有同步更新;还有一类是上游数据质量问题——比如订单状态字段在某些历史订单中为空,导致技能判断失误。

针对每一类,处理方式完全不同。路由匹配错误需要优化召回逻辑或技能描述;技能内部逻辑问题直接修 Prompt 或业务规则;上游数据质量问题则需要和技术平台沟通,做数据清洗。

这个"日志-聚类-修复-回归"的闭环我每周跑一次,每次都能发现一到两个可以优化的点,迭代速度非常可观。

回到agent-skills这个主题本身。我个人的核心体会是:技能化不是一个新框架,也不是一个新模型,而是一种工程组织方式。它把原本混沌的 Agent 开发过程拆成了可以独立迭代的小部件,让"效果优化"从玄学变成了可跟踪、可评测、可回滚的工程实践。如果你现在的 Agent 项目正处于"一个 Prompt 满天飞、改一处崩三处"的阶段,我建议你参考这篇文章里的思路,尝试做一次技能化拆分。先从 5 个技能开始跑通闭环,再逐步扩展。只要路由、记忆、编排这三个底座稳住了,后面的路就会越走越顺。

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

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

立即咨询