ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

给Agent配一套“技能库”:从工具调用到能力封装的工程实践

给Agent配一套“技能库”:从工具调用到能力封装的工程实践 1. 为什么工具再强也要给Agent配一套技能库1.1 从一次真实翻车开始有工具不等于会用工具先讲一个我自己踩过的坑。去年我做了一个基于大语言模型的智能客服Agent初期功能很朴素你问它天气它调天气API你问它快递它查物流接口。当时团队成员都很兴奋觉得Agent能调用工具了基本能力就有了。结果一上线就翻车了。用户问帮我查一下上周三那笔订单为什么还没发货Agent确实调用了物流查询接口但传参是order_id20231115——把日期当订单号传进去了。用户又问那我退款吧Agent找到退款接口却因为没有确认用户是否有权限、订单是否在退款窗口内直接把退款请求发出去了。最离谱的是用户问你们客服几点下班Agent在工具列表里找了一圈最后调用了一个工作日报生成接口生成了一篇流水账。问题出在哪不是说工具调用链路有问题而是Agent根本不知道什么场景该用什么能力、用的时候该遵守什么步骤、哪些边界不能碰。这就像给一个实习生配了全套办公软件但他不知道汇报要用PPT、记账要用Excel、发会议邀请要用日历。工具是有了技能是零。后来我花了两周时间重构核心思路就一句话把能用什么工具升级为会做哪些事也就是给Agent建一套技能库agent-skills。重构之后同类问题基本不再出现。这篇文章就把我在这套体系上的设计思路、工程细节、踩坑记录完整展开希望做Agent应用的朋友能少走弯路。1.2 技能库的定位夹在工具函数和工作流之间的那一层很多人会把技能和工具混为一谈我的理解不太一样。它们之间的差别用一个场景就能说清楚。假设你在做一个订餐Agent。工具层是search_restaurant()、get_menu()、submit_order()、apply_coupon()这些都是最细粒度的原子操作参数明确返回确定。技能层是推荐附近餐厅、根据饮食禁忌筛选菜品、完成下单并匹配最优优惠。技能不直接对应某个函数它是对一段完整解决能力的封装——技能内部会依次调用多个工具还可能包含判断逻辑、兜底策略、参数转换。再往上工作流层是从问需求到下单完成的端到端编排它把多个技能串成一个长流程。所以技能库的位置非常特殊它比工具高一层又比工作流低一层。工具解决能调什么技能解决会做什么工作流解决按什么顺序做完整件事。Agent如果没有技能层就会陷入我前面说的那种混乱——拿着order_id的锤子看什么都是钉子。从工程实现上看技能库也不是简单地把函数注册一下。它至少要包含技能清单、调用协议、参数约束、依赖关系、权限边界、失败回退策略。这几样东西少了任何一样Agent在复杂场景下都会露怯。2. 技能库的核心概念拆解一次技能调用的底层逻辑2.1 一个技能的本质是什么说白了吧技能就是出卖劳动力和判断力——用固定格式的定义告诉Agent在什么条件下、按什么步骤、用什么工具、产出什么结果。这个概念是所有Agent技能系统的核心不管是自研的还是开源的万变不离其宗。我推荐用一个统一结构来声明技能类似下面这样{ skill_name: order_refund, description: 处理用户的订单退款申请包含资格校验、退款金额计算、提交退款与结果确认, triggers: [退款, 退货, 不想买了, 申请售后], input_params: { order_id: {type: string, required: true, desc: 用户订单编号}, reason: {type: string, required: false, desc: 退款原因} }, steps: [ {step: 1, action: verify_order_owner, desc: 校验订单归属}, {step: 2, action: check_refund_window, desc: 校验退款时限}, {step: 3, action: calc_refund_amount, desc: 计算退款金额}, {step: 4, action: submit_refund, desc: 提交退款请求} ], fallback: 当订单不符合退款条件时向用户说明原因并推荐售后渠道, permission: user_authed }这个结构里的每个字段都不是拍脑袋定的。triggers是给意图识别用的关键词和短句steps定义了内部执行顺序Agent一般来说不能跳步fallback是当步骤执行失败或前置校验不通过时的兜底话术与动作permission标记了这个技能需要的权限等级。后面我会详细讲每一步为什么这么设计。2.2 为什么描述比代码更重要有一个很反直觉的经验在Agent技能系统里技能的描述文本往往比它的实现代码更容易影响成败。因为技能最终不是人直接调的而是大模型看了描述之后决定调不调的。描述写得模棱两可模型就会在多个技能之间犹豫、选错或者生造参数。我见过一个典型的反面例子。某个技能叫get_user_info描述写的是获取用户信息。结果Agent在用户问我上次买的东西到哪了的时候先调用了get_user_info因为它觉得获取用户信息能帮忙找到用户。但实际上应该调query_shipment。如果描述写成根据用户ID获取用户的注册资料、会员等级与联系信息不包含订单物流信息就能大幅减少误用。这是我在试用各种开源框架和自研系统后非常确定的结论。所以建议每个技能的description里都包含三块信息职责范围这个技能负责什么、能力边界它不负责什么、典型场景什么情况下优先选它。比如order_refund的描述就可以写成处理用户主动发起的退款申请适用于订单已支付但未完成或已完成的场景。不包含仅退款不退货的纠纷仲裁也不包含商家主动发起的赔付。当用户要求退货、退款、不想要时优先考虑调用本技能。2.3 技能、工具、插件、Action到底怎么区分聊到这里很多人会问市面上有的叫Tool有的叫Function有的叫Plugin有的叫Action我的技能是不是重复造轮子我系统对比过几类主流Agent框架的命名这也是我日常技术调研的固定动作。各家平台叫法不一但在工程语义上有明确分工Tool / Function原子能力一个函数完成一个动作比如查询天气发送邮件。没有状态没有内部决策人或者Agent都能直接调用。Skill / Skill Set组合能力内部可以编排多个Tool也可以有状态和判断逻辑是一个完整的任务解决单元。Plugin偏生态概念通常指第三方服务提供的工具包比如某个外部平台发布的一组API的封装。Action偏流程概念强调触发条件执行动作结果反馈更像是在工作流语境中的节点描述。所以技能体系不是要取代工具而是建立在工具之上的一层组织与编排机制。工具是砖技能是墙。你要盖什么样的房子取决于你码什么样的墙。3. 技能库的设计规范一套给Agent看的接口文档3.1 技能的粒度控制太大变小脑太小变没脑技能粒度是设计技能库时最重要、也最容易失控的问题。粒度过大比如把处理所有售后问题做成一个技能内部塞满了退款、换货、补发、投诉、开发票等一大堆子逻辑Agent调用一次要连带加载很长的上下文推理容易乱出错了也不好定位。粒度过小比如把校验订单归属单独做成一个技能Agent在一个简单场景里要连环调用七八次技能每次都要走意图识别-技能匹配-参数生成-结果解析的完整链路性能和稳定性都是灾难。我用的一个经验法则是**一个技能应该能在5到8个推理步骤内完成并且只解决一类强相关的用户诉求。**用这个标准来衡量处理所有售后问题不合格得拆校验订单归属不合格得合并进处理退款申请里。拆完之后我的项目里技能数量大概在20到40个之间这个量级对意图检索和上下文管理都比较友好。3.2 技能间的依赖关系与组合策略技能库还有一个容易踩的坑技能之间不是孤立的。比如推荐附近餐厅可能依赖获取用户定位完成下单可能依赖确认收货地址。如果不显式声明依赖关系Agent就会经常漏掉前置步骤直接在参数不全的情况下硬调。我建议在技能定义里增加一个dependencies字段明确写出前置技能或前置工具。执行流程上Agent一旦选中某个技能会先自动检查依赖是否满足不满足就把依赖技能拉出来先跑。这相当于给Agent一个隐式的工作流展开机制非常有用。组合策略上我测试过两种模式一种是线性串联技能A跑完把结果作为技能B的输入适合流程很固定的场景另一种是条件分支技能内部根据中间结果选择不同后续技能或路径适合包含校验和判断的场景。绝大多数实际业务场景都在这两种模式之间编程实现时可以提前预留。3.3 参数声明怎么让大模型不产生幻觉参数参数幻觉是技能调用中非常讨厌的问题。大模型在生成JSON参数时偶尔会生成一个技能定义里根本不存在的字段或者把字段值填错类型。比如技能定义里明明是refund_amount模型给你传一个refund_pricereceiver_id应该是字符串模型给传了数字。根因通常不是模型笨而是参数说明写得太简略。我自己修复这类问题时会把参数定义规范化按这个模板来{ order_id: { type: string, required: true, desc: 订单编号格式如ORD-20231115-001来自order_list接口返回的order_id字段, range: 用户当前账号下的有效订单, example: ORD-20231115-001 } }这里的关键是desc要写明来源来自哪个接口的哪个字段、格式前缀规则、分隔符、范围属于谁、什么状态example则直接给一个规范样例。有了这两条模型生成参数的准确性能提升很多。数据格式要求比较严格的需求比如日期时间、订单号、金额单位建议优先级更高地使用严格的格式约束。越早把这个工作做扎实后面就哭得越少。4. Agent调用技能的完整链路从用户提问到技能执行的工程实现4.1 一次技能调用的完整流程技术实现上Agent调用技能不是一句话的事它埋着一整套调度逻辑。我梳理一下我自己项目里一次完整技能调用的流程供参考会话输入用户输入自然语言带着历史上下文进入Agent主循环。意图识别基于当前轮次和上下文先判断用户意图属于哪个大类比如查询类操作类咨询类。技能匹配从技能库中检索候选技能。这一步通常结合关键词触发、向量相似度、规则路由等多种手段。技能校验判定命中的技能是否需要前置条件登录态、权限、依赖技能不满足则触发前置流程。参数提取从对话上下文中提取参数填入技能定义的input_params。提取不完整时Agent应该主动反问而不是硬调。执行按技能的steps顺序执行每个步骤对应一个或多个工具函数。结果处理把结果格式化为用户能懂的语言。如果执行失败或校验不通过触发fallback逻辑。写入记忆把本次调用的上下文、结果、状态写入短期或长期记忆供后续调用参考。这8个步骤里最容易出问题的就是第3步和第5步——匹配不准、参数不全。这也是我后来重点优化的地方。4.2 多级技能调度意图路由怎么设计技能多了以后每次用户提问不可能把所有技能都塞进上下文让模型选一遍token消耗直接爆炸。所以我采用了两级路由策略第一级是粗粒度意图分类。把技能先按业务域分组比如订单域、支付域、售后域、会员域。用户提问后先用一个轻量级分类器可以用小模型或者规则关键词判断属于哪个域只把该域下的技能送进下一轮。第二级是细粒度技能选择。在该域内再把技能描述、触发词、参数表拼装成候选列表交给大模型选择或者用向量检索做召回。这一级因为有域的限制准确率会明显高于把所有技能混在一起。两级路由结构看起来简单但效果非常显著。我曾在同一个测试集上对比过全部技能平铺让模型选择的准确率大约78%加上域路由后提升到92%。而且每次调用的token消耗也降了不少因为候选列表短了。这里也建议在实际项目里先把所有技能的触发词和描述表建好路由系统的搭建会顺手得多。4.3 技能执行中的错误处理与回退机制技能执行不像本地函数那样要么成功要么抛异常。真实场景中外部API超时、返回数据格式不对、业务状态不满足条件都是家常便饭。如果不做错误处理Agent就会一本正经地编答案那是很糟糕的体验。我从几个线上案例中总结了一组回退策略大家可以按自己的需求参考前置校验失败比如退款资格校验不过不要硬执行后续步骤直接走fallback告诉用户原因同时给出可替代建议比如订单已超过退款时限可申请换货或联系人工客服。工具调用超时重试一次重试仍失败向用户说明系统暂时繁忙并把当前进度保存到会话上下文允许后续继续。返回结构异常对工具返回值做schema校验如果字段缺失触发修复逻辑——用默认值填、从上下文推断、或者告诉用户信息获取失败。步骤间冲突比如计算出来的退款金额和用户预期差很多不要直接提交而是先跟用户确认一次。这类涉及资金、权限、隐私的操作建议一律加人工确认。回退机制的价值不仅是不崩更重要的是让Agent在异常状态下依然保持诚实和可控。项目上线初期这类细节往往决定用户对Agent的信任感。5. 技能库落地过程中的踩坑记录从上线到稳定我都调试了什么5.1 技能数量膨胀后的检索失效问题技能库刚起步时只有七八个技能怎么匹配都准因为候选少。等技能涨到三十多个问题出现了模型经常把一个技能误判成另一个。最典型的案例是生成工作日报和整理会议纪要——都涉及把对话内容结构化输出触发词有重叠结果Agent经常选错。我排查后的结论是不是因为模型能力差而是技能描述里的区分度不够。两个技能描述如果都写将用户对话内容整理成结构化文档模型当然分不清。解决方法是给每个技能增加不适用场景描述比如在整理会议纪要里写明本技能仅适用于会议场景不适用于工作总结、日志、周报等日常汇报内容的整理。这个调整听起来很笨但效果立竿见影误判率直接下降了一半。此后我定了一条铁律新技能入库时描述里必须写明它跟现有相似技能的区别点。5.2 参数幻觉的排查链路一次真实的Debug过程有段时间Agent在调用提交退款技能时频繁出现了参数错误。日志里模型生成的JSON长这样{ order_id: 20231115, refund_price: 199.00, reason: 质量问题 }而技能定义里明确写的是order_id和refund_amount没有refund_price。模型凭空捏造了一个字段名。当时排查了四个方向是不是系统提示词里出现过price这个词查了确实有在另一个场景的说明里提过商品价格price。模型看到相似语义就带过来了。我后来做了全局提示词的词汇隔离不相关的业务词不放同一个上下文。是不是参数描述里的别名不够我把refund_amount的desc改成了退款金额单位元数值类型出自refund_calc接口的amount字段不要与商品价格price混淆模型就没再犯过同样错误。是不是示例不够多我给每个参数增加了一个正例和一个反例。正例展示规范格式反例展示容易混淆的错误写法。这个对模型帮助非常大。是不是JSON Schema没有做运行时校验我在工具执行前加了一层Pydantic校验字段名不合法就直接拒绝执行并让模型重新提取而不是把参数原样传给外部接口。这层校验拦截了大量幻觉参数。排查完这四步参数报错率降了八成以上。这个案例我一直拿来做团队培训核心就一条别指望大模型不犯错要在架构上准备好纠错机制。5.3 技能冲突与权责边界重复技能带来的致命伤还有一类坑特别隐蔽不同业务线各自往技能库里加了功能相似的技能。比如客服团队加了取消订单运营团队加了撤销订单两者内部逻辑完全一样只是名称不同。表面上看是小问题实际问题在于Agent在关键业务上容易行为不一致——同样的用户诉求有时候走A技能有时候走B技能这对商家和用户来说体验很不稳定。我的处理方法是建立技能注册表每个技能入库前做一次查重名称查重是一方面更关键的是做场景query查重——把新技能的触发词、描述和已有的技能逐一比对相似度超过阈值就要么合并、要么明确差异化定位。同时用统一的技能命名规范动词名词结构比如submit_refund_request、query_shipment_status避免同一个语义的不同表达。此后因为技能重复导致的调度混乱基本绝迹。6. 从技能到能力生态如何持续迭代一套技能库6.1 技能的健康度评估与淘汰标准技能库不是建完就一劳永逸的。我每两周做一次技能健康度评估看四个指标调用频率一周内被调用多少次。连续很久为零的技能要么写错了触发词要么根本没被调度器正确暴露。成功率技能执行中无异常、且结果为正常完成的比例。成功率低于50%的技能需要重点排查。误用率被模型选中但实际不合理、需要人为纠正或回退的比例。token消耗每个技能平均消耗的输入token。高消耗低价值的技能要优化描述或者合并到其他技能里。这个评估机制帮我淘汰了不少僵尸技能也让技能库保持在够用但不冗余的状态。技能库的维护和整理和整理自己的衣柜有异曲同工之妙——不定期清理看着满满当当关键时刻什么也找不到。6.2 让技能自我生长的几个方向技能库迭代到一定阶段会迎来一个质变点——从手动添加技能变成让Agent在对话中沉淀新技能。我有几个亲测可行的方向对话回放分析定期抽取真实对话记录找出那些用户诉求明确、但现有技能处理失败或处理得勉强的case人工补充新技能。这是最实用的一条路径基本上每周都能提取出2到3个新技能。技能模板化把技能定义抽成模板比如查询类模板提交类模板确认类模板新技能按模板填参数即可迅速生成。标准化之后技能库的可维护性会上一个台阶。动态技能装载不同时段、不同用户上下文下只装载必要的技能子集避免一次性把所有技能塞进上下文。这套机制做扎实后Agent在响应速度和准确率上都会有明显提升。从我目前的实践经验来看技能库的建设没有天花板它会随着业务演进不断调整。但核心价值始终不变它让Agent从能说会道的聊天机器变成了真正能把事情办成的助手。最后再分享一点个人感触技能系统的调试过程中最大的幻觉就是我的技能定义已经很完善了——每当我这么想下一周必出幺蛾子。所以保持敬畏把每一次线上异常都当作技能库迭代的养料这比任何架构技巧都重要。
返回列表