ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent技能体系落地实践:从工具调用到技能编排

Agent技能体系落地实践:从工具调用到技能编排 做Agent项目几个月我越来越确信一件事大部分团队做出来的东西之所以像聊天机器人套壳不是模型不够聪明而是压根没给Agent建立一套真正的技能体系。agent-skills这个词字面上看是智能体技能本质上是把Agent能执行的每一个原子操作——查数据库、调接口、写文件、发消息、算数据——抽象成结构化、可管理、可编排的技能然后让大模型像人一样按需取用。这篇文章不聊概念只聊实操。我会从技能体系的定位、技能描述怎么写、调度机制怎么选、技能怎么编排、仓库怎么管理、以及我实际踩过的那些坑完整走一遍。适合正在做Agent应用、项目已经跑通Demo但想往生产环境推的团队参考。1. 先搞清楚Agent的技能体系到底在解决什么问题1.1 没有技能体系的Agent长什么样很多团队第一版Agent是这样的把用户的prompt拼接进一个大System Prompt里面写了一些你可以调用以下工具然后把几个函数的定义塞进去让模型自己发挥。这种方案Demo阶段完全够用但你一旦让它干稍微复杂点的活问题就来了。举个例子我有个朋友做过一个智能助理核心任务是帮用户订会议室、查日程、发邮件。第一版他把所有功能都塞在一个超长的工具列表里模型经常搞混明明用户说下午3点开会模型却去调用了发邮件的工具查一下我的日程这个请求模型有时走日程查询有时又去翻联系人列表每次对话都要带上一大串工具定义token消耗高得离谱响应还慢。这是典型的没有技能抽象的问题。工具之间职责边界不清晰、没有统一的能力描述规范、模型选择工具时全凭提示词里那几句不痛不痒的话猜。1.2 技能和工具、插件之间的本质区别很多人觉得技能就是工具换了个说法这是个误解。工具tool是按函数组织的比如get_user_info(user_id)、send_email(to, subject, body)。函数是给开发者用的它的可读性和语义性对模型并不友好尤其是参数复杂、有大量可选项的时候模型很容易填错。技能skill是按能力组织的它描述的是一件事查询用户信息发送邮件创建日程。每个技能内部封装了具体的实现对外暴露的是一份结构化能力说明书技能叫什么、干什么用、需要哪些输入、会返回什么、有什么限制。两者最大的区别是工具是面向调用的技能是面向选择与编排的。你给模型的不是一个函数列表而是一份能力菜单。1.3 一个技能化后Agent的典型样子我们重构之后同一个智能助理的技能分为三层基础技能查日程、查联系人、发通知一个技能对应一个明确的原子能力业务技能订会议室内部会调用查空闲房间、占会议室、发通知三个子能力但在外面看来就是一个技能编排层由Agent根据用户意图组合上面两种技能。改完之后最直观的变化是同样的请求技能命中率从不到70%提升到了92%以上参数错误率也大幅下降。后面我讲到的每一步都是围绕这个重构展开的也是agent-skills这个思路的落地过程。2. 技能描述的建模写清楚这四件事模型才不会猜技能的建模是整个体系的地基。你给大模型看的技能描述质量决定了它的选择准确度。一份完整的技能描述我认为至少要有四部分标识与命名、能力说明、输入输出契约、边界约束。2.1 name命名要短、要像英文变量名技能的name在模型眼里是这个技能的唯一ID。好的命名习惯是动词开头、不超过三个词、全小写加下划线。推荐query_stock_price、create_calendar_event、send_group_message不推荐handleUserRequest像函数、股票价格查询服务中英混排易出错、tool_1无语义这里有个实操细节模型的tool调用结果里name字段是精确匹配的你哪怕多一个下划线、大小写不一致都会导致调用失败。所以命名规范必须靠代码层面的约束保证不能靠自觉。2.2 description决定模型什么时候选它description是技能描述里最关键的字段也是最容易被写砸的字段。它是模型判断这个请求该不该用你的依据写得好不好直接影响技能召回率。我总结了一个三段式写法第一句一句话说明这个技能是干什么的包含触发场景词第二句说明技能的核心能力和主要用途第三句说明它不能干什么避免误召回。举个例子一个get_stock_price技能的description我推荐这样写Fetches current and historical stock price data for a given ticker symbol. Use this when the user asks about a stocks price, market performance, price trends, or any query that involves real-time or historical market data. Do not use this for cryptocurrency prices, index values, or portfolio recommendations.注意几个细节Use this when...这一段是在给模型划清触发边界Do not use this for...是在做负向限定能明显降低误召回率关键词密度要高但别堆砌。我测试过不同description风格对调用准确率的影响实测下来负向限定能提升5~8个百分点的准确率。原因是模型在模糊场景下倾向于多试几个工具负向限制能帮它排除明显不合适的选项。2.3 输入输出的SchemaJSON Schema是底线技能参数的声明建议不要自己发明格式直接用JSON Schema。原因有三大模型对JSON Schema的训练数据覆盖率高理解准确参数校验可以直接复用现成的校验库能和OpenAPI、Function Calling等标准格式无缝转换。一个技能的输入Schema要有三个要素type: object顶层必须是对象properties里对每个参数写清楚类型、含义、取值范围required明确哪些参数必填。这里最大的坑是别把所有参数都设为必填。模型在信息不足时如果发现一个必填参数无法填出来它有两条路要么猜一个值要么直接放弃调用。猜值比不调用更可怕——你可能给用户推了一个错的行程、发了一封内容错误的邮件。输出的Schema同样重要但我见过很多项目根本不定义输出Schema。没有输出约束的后果是技能返回的是自由文本下游步骤的模型只好从文本里脑补结构化数据错误率会急剧上升。后面编排章节我会用一个案例详细讲这个。2.4 边界约束让技能知道自己什么时候该拒绝边界约束是容易被忽略但极其重要的一块。我把它理解为技能的使用说明书里必须写清的注意事项权限边界这个技能能操作哪些数据不能访问哪些资源参数约束数值范围、格式要求、必填条件副作用提醒比如这个技能会真实发送邮件调用前必须经过用户确认失败条件什么情况下技能会返回错误错误码是什么含义。边界约束建议写在description的reminder字段或单独的限制字段里不要让模型从执行代码里去理解边界——它执行不了你的代码。3. 技能调度的核心机制大模型到底怎么决定调哪个技能技能建模做完了下一步是调度——当用户说了一句话系统怎么决定调哪个技能、怎么填参数、怎么处理失败。3.1 调度方式一Function Calling这是目前最主流的方式OpenAI的tool call、Claude的tool use都基于这个思路。它的原理是在模型推理时你不是只让模型输出文本而是给模型额外提供一批工具定义模型在理解用户意图后会选择调用某个工具并返回结构化的调用参数。Function Calling对技能选择这件事是最稳的因为它把选择过程变成了受限解码的一部分——模型在输出JSON格式的参数时结构是强约束的不会出现格式错误。但Function Calling也有明显的局限单一会话内能带的工具数量有限。我实测超过30个技能定义时选择准确率开始下降超过50个则严重劣化技能定义里的描述和参数Schema都会消耗上下文一次完整调用可能吃掉2000~4000 token。所以当你的技能库膨胀之后纯靠Function Calling硬塞是不可行的需要引入技能检索层也就是我后面会讲的路由。3.2 调度方式二ReAct提示词策略ReAct是让模型在对话中自己完成Thought思考→ Action行动→ Observation观察循环。它比较灵活适合技能数量少、步骤简单的场景。但我的经验是ReAct在小规模场景里可以作为Function Calling的降级方案规模一上来就非常不稳定。因为模型的思考环节没有强约束有时候它会凭空捏造一个Action的名字——比如你的技能叫send_email它却输出了一个sendEmail然后系统报找不到工具。所以如果走ReAct一定要在提示词里加一条强约束Action必须严格从技能列表中选取不允许创造新技能名不允许修改技能名的大小写和格式。3.3 调度方式三混合路由检索LLM决策当技能数量超过20个我强烈建议你怎么选择都不用纠结直接上技能检索LLM决策的混合路由。思路是先对技能建立索引——用Embedding把技能描述向量化用户输入进来后先做向量检索召回Top 5~8个候选技能把候选技能塞给大模型做精排和参数填充模型从候选里选一个或几个执行。这套方案的效果实测比一股脑全塞要好得多不仅准确率不降响应速度还更快因为每次会话里带进上下文的技能定义少了。需要注意一个点Embedding召回对技能描述的质量很敏感。描述里没有和用户query对应的关键词这个技能很可能召不回来。所以技能描述的第一句话一定要刻意埋入高频触发词。3.4 技能选择的防错策略调度机制再好也不可能100%选对。我的防错策略有三道第一道相似技能消歧。如果两个技能语义很近比如查周报和查日报在description里互相写如需日报请使用另一个技能给模型一个明确的区分信号第二道上下文二次确认。高风险操作发邮件、付款、删除数据在执行前让Agent把将要执行的动作复述给用户确认第三道兜底策略。如果模型选择技能时的置信度很低或者连续两次验证失败就让Agent主动承认我没有找到合适的能力来处理这个请求而不是硬调一个似是而非的技能。4. 用技能编排拆解复合任务链路设计的实际案例单个技能再完善也只能做原子操作。真正让Agent变得有用的是编排——把多个技能串起来完成一个复合任务。这一节我拿一个实际案例从头拆一遍。4.1 案例从帮我安排一个本周五下午的面试看编排假设Agent的技能库里有这些技能search_free_room查空闲会议室create_calendar_event创建日程send_webhook_notification发送群通知query_user_schedule查目标用户的日程用户输入帮我安排一个本周五下午3点的面试会议室要大一点的。Agent要完成这条指令完整链路是这样的理解意图需要一间大一点的会议室→ 触发search_free_room参数提取时间是本周五下午3点大小要求是大一点的→ 转换成search_free_room的参数{time: 2025-06-06T15:00:00, capacity: large}执行搜索拿到候选会议室列表可能需要向用户确认选哪一间确认后触发create_calendar_event创建日程日程创建成功后再触发send_webhook_notification通知参会人。这个案例里有几个关键点值得展开。4.2 编排的三种模式我把技能编排总结为三种基础模式顺序链上一个技能的输出直接作为下一个技能的输入。最简单也最常用适合流程固定的场景并行扇出一次请求触发多个独立技能同时执行。比如查一下本周日程、天气、路况就是三个无依赖技能并行执行条件分支根据上一个技能的返回结果决定走哪条分支。比如查会议室失败后是自动改查另一个时间段还是直接反馈失败由条件分支决定。模式不是写死在代码里的而是由Agent运行时根据意图动态组合。对我们来说需要做的是在框架层面同时支持这三种模式的执行能力。4.3 状态管理中间结果怎么传递复合任务里最头疼的不是第一步怎么调而是第一步的结果怎么交给第二步。我见过太多方案是让上下文对话历史越长越好结果token越堆越长最终模型被淹没在历史里。我的实践是提出一个轻量状态池概念——把每一步产生的结构化结果存到当前任务的状态池里Agent在推进后续步骤时只在必要的时候引用状态池里的数据而不是把所有历史都塞进prompt。具体的做法是每个技能执行完把结构化输出不是自然语言输出写入状态池的键值对中下一步技能的description里如果它需要依赖某个上游数据描述里直接写明该技能需要依次执行上一个技能后的结果Agent的提示词里明确说明可以从状态池中读取数据但不要编造状态池里不存在的信息。这套方式比把结构化数据翻译成自然语言再传给模型准确得多也节省了大量token。4.4 局部重试与失败恢复复合任务最怕的是第二步失败了整个流程从头再来。我的处理方式是把编排链路做成可断点续跑的。比如search_free_room成功了create_calendar_event因为没有该会议室的权限失败了这时不能让用户重新说一遍需求而是要让Agent明确知道日程没建成功的原因是权限问题然后尝试两个方向换一个会议室重新搜索保留原结果改为向用户说明权限限制。最终要保证Agent不隐瞒失败、不假装成功、不重复执行已经成功的步骤。这一点在执行链路长、涉及真实业务动作比如发钱、发邮件的场景里尤其重要。5. 技能仓库与版本管理多人协作下的Agent能力底座技能体系做到这一步已经不是一个写代码的问题了而是一个工程管理和协作问题。当技能数量超过20个、团队超过3个人你就需要一套技能仓库来管理它们。5.1 技能包的目录结构我们团队内部对每个技能是一个独立目录核心文件包括manifest.json技能元信息name、version、author、description入口description.md人类可读的技能描述也是生成description给模型的素材schema.json输入输出JSON Schemaexecutor.py技能的实现逻辑负责真正干活test_cases.json技能的冒烟测试样例集。这种结构的好处是技能是独立交付的不同人负责不同技能互不干扰技能信息全部集中在一个包里便于自动化校验和发布。5.2 manifest里必须有的元信息manifest.json是技能的身份证我会追踪以下字段name/version/description基础信息author负责人方便故障时候找到人tags技能分类标签用于检索和权限控制permissions技能执行需要的权限级别retention技能运行时的数据保留策略dependencies该技能依赖的其他技能或服务version管理推荐用语义化版本号SemVer。技能的行为发生非兼容性变化时version必须升级主版本号否则下游编排逻辑可能悄悄跑偏。5.3 技能发布的校验流程技能不是写了就能上线的。我们的每个技能在合入仓库前必须通过四道校验Schema校验输入输出Schema合法参数校验逻辑通过指令集校验生成的技能描述能通过一套预置的测试输入准确触发Mock执行校验在mock环境下执行技能验证返回结构回归校验把历史真实用户的代表性请求跑一遍确保新技能的上线不影响其他技能的召回和选择。最后一道回归校验最容易被人忽略。我见过不止一次新增了一个send_sms技能结果用户的帮我发一条消息这条请求从原本的发群通知被错误路由到了send_sms上。因为你新增加技能后所有description同时进入了模型的选择池原来的选择分布会被扰动。5.4 多环境的技能发布技能的发布路径我建议做三套环境dev环境技能开发自测用可以随便折腾数据集是mock的staging环境和真实环境能力一致但会拦截所有会对真实世界产生副作用的操作发邮件、写数据库prod环境完整能力开放所有操作都会被审计日志记录。从staging到prod的发布需要经过上面四道校验全过才行。这样做的好处是出问题时你能快速回滚到上一个稳定版本而不至于让整个Agent能力栈跟着崩。6. 实测中踩过的坑技能调用失败的五个典型场景最后这部分是这段时间做agent-skills项目最值得分享的内容。下面五个坑每一个我都真实踩过而且排查过程不轻松希望你看完能绕开。6.1 坑一description写得像营销文案我最早写description的习惯是越详细越好写了一大段包含各种术语、同义词、示例。结果模型根本抓不住重点选择准确率反而下降了。后来我明白了模型的工具选择行为像一个关键词配对人它需要的是清晰直接的触发词不是完美的文笔。查询股票价格输入股票代码返回最新价格这种平铺直叙的写法效果远好于本技能旨在为广大投资者提供准确、实时的证券市场行情信息。建议description里动词前置名词用具体词不用形容词和副词装饰。6.2 坑二参数Schema限制过死导致模型填不出参数有个技能是查询用户订单参数里有个status字段我把它做成了枚举类型只允许pending、paid、shipped、completed四个值。结果用户说查一下我退货的订单模型的参数提取直接失败——它找不到退货对应的枚举值又不愿意填一个不在枚举里的值最终整个调用失败。定位方式是从日志里看模型的tool_call输出发现它在参数填充环节停了。优化方法是两个把枚举放宽为自由字符串在技能内部做归一化映射或者给枚举类型的字段增加一段说明如果用户的描述无法匹配到具体枚举值请选择最接近的一项并在备注字段里说明原始表述。记住Schema是用来帮助模型准确传参的不是用来挡用户请求的。6.3 坑三技能输出没有结构后续编排全靠脑补有一段时间我们的query_travel_info技能返回的是大段文本比如北京本周六天气晴气温20-28摄氏度适合出行。下游要去安排行程的Agent看着这段文本自己解析适合出行这个结论然后安排了一个户外活动。后来用户反馈Agent推荐的行程跟他实际偏好完全不符。排查后发现问题不在于推理能力而在于上游技能的输出文本丢失了大量结构化信息。比如原始返回里本应有降雨概率35%这种量化字段但被自然语言输出省略了下游模型只能靠猜。改了技能的输出Schema让它返回结构化JSON下游Agent基于字段做判断问题立刻解决了。经验技能的输出一定要结构化、机器可读。下游编排的模型只能可靠地依赖结构化字段不能依赖自然语言里的暗示。6.4 坑四技能互相调用时token和权限双双失控实际项目中一定会有技能A调用技能B的嵌套情况。我们早期为了让Agent能灵活组合允许技能执行器内部再调用另一个技能。结果出现了一个很尴尬的现象一次查股票并生成研报的请求两个Agent实例互相嵌套调了7次token直接用爆而且中间有一次触发了没有权限的数据接口返回了一场权限错误把整条链路带崩了。现在我们的约束是编排层的技能调用最多两层第三层开始必须复盘流程设计考虑是不是该把子技能封装成成一个更粗粒度的复合技能技能执行器内部原则上不允许再嵌套调用其他技能如果需要必须显式声明依赖关系并经过权限审批。6.5 坑五没有日志链路技能调用过程全靠猜最后这个坑是最伤筋动骨的。一开始我们在技能系统里的每个函数加了个print觉得这就是日志了。直到某一次线上事故用户的请求被自动执行了一个删除缓存技能我们翻了半天日志完全没找到是谁、在什么环节、因为什么理由调用了这个技能——因为print只打在本地控制台服务器上一片空白。后来我们搭建了完整的技能调用审计日志记录以下字段请求ID、会话ID、用户ID用户原始输入命中的技能名称、触发方式输入参数、输出结果摘要每一步的耗时和token消耗权限校验的结果。有了这套日志技能有没有被调用为什么被调用调用后发生了什么一目了然。排查故障的时间从小时级降到分钟级也终于敢把技能系统放给更多业务方用了。这套agent-skills体系的构建大体上就是上面这些内容。最后想说的是技能系统本质上是一个持续演进的过程它不是一个做完就完了的项目而是会随着业务需求不断增加技能、调整描述、优化调度策略。保持对线上调用数据的敏感定期分析哪些技能召回率高、哪些技能一次都没被调用过、哪些技能的参数错误率偏高然后针对性地优化比什么都管用。
返回列表