资讯动态

Agent技能:让大模型从工具调用走向能力沉淀

发布时间:2026/10/14 9:31:09 来源:尧图企业网站定制
去年我接手一个内部Agent项目时发现一个很有意思的现象同样一个模型底座A同学搭的智能体能稳定处理十几个业务场景B同学搭的换个场景就崩。两边代码拉齐对比差距根本不在模型参数而在技能这一层。这也正是我想聊的agent-skills——它不是什么新框架而是一种组织Agent能力的方式。简单说就是让模型不再靠临场发挥而是像人一样把高频动作沉淀成肌肉记忆。这篇文章会从技能的定义、设计、实操到排障完整拆开讲适合正在做Agent应用、或者打算从单点工具调用往技能库方向演进的同学参考。1. 技能不等同于工具调用重新理解Agent的能力边界1.1 从Function Calling说起为什么工具抽象不够用不少同学是从Function Calling入门的Agent开发。模型根据用户意图输出一个结构化的函数调用请求代码侧收到后执行真实逻辑再把结果回传给模型。这套机制解决的是模型怎么调用外部能力的问题但它有一个天然盲区它假设每个函数调用都是独立、原子、无状态的。实际业务里根本不是这样。一个查询订单的动作背后可能牵涉到权限校验、租户隔离、缓存命中、数据脱敏、日志埋点还有失败时的降级策略。这些步骤如果全部塞进一个函数里函数会变得臃肿如果拆成多个函数模型就得在一次对话里连续决策好几次每多一次决策就多一分出错概率。更麻烦的是模型并不理解函数内部的逻辑。它只知道调这个函数能拿到订单信息但不知道什么条件下该调、调了之后结果要怎么用、遇到异常该找谁。这就导致开发者在Prompt里写了大量你是一个订单助手当用户想查订单时调用query_order之类的引导语。Prompt越来越长效果却越来越不可控。1.2 技能的本质从能用到会用我理解的技能是工具调用之上的能力封装层。它把模型要做什么和代码要怎么做之间的模糊地带补齐了。一份技能定义不仅告诉模型你可以调用这个能力还要告诉它这个能力适合解决什么问题、在什么场景下不要用、输入输出长什么样、失败了怎么办。打个比方工具调用等于给了模型一把螺丝刀而技能是教会它拧十字螺丝要用十字口、拧的时候要垂直用力、螺丝滑丝了要用钳子处理。后者才是真正的会使用工具。从落地角度看技能至少解决三件事第一把多步操作收敛成单次决策。比如为客户生成一份季度对账单这个动作内部可能涉及查账、汇总、模板渲染、生成PDF、发送邮件五个步骤。如果把这五步封装成一个技能模型只需要做一次决策剩下的流程由技能内部编排完成错误率会大幅下降。第二把专家经验写进可执行的定义里。技能描述里可以写仅当客户明确要求英文对账单时才设置语言参数为en否则使用默认语言这些经验可以被复用和传承不依赖某个开发者在某个特定Prompt里的临时发挥。第三给模型一个安全边界。技能可以声明自己需要哪些权限、不可以执行哪些操作模型在调用前就能感知到边界减少越权行为。我在实际项目里发现技能设计得好不好直接决定了Agent的上限。模型天生是概率输出你没法指望它每次都能从零推理出正确的调用序列但你可以把推理路径缩短把不确定性压缩到可控制的区间里。技能就是用来干这个的。2. 一份合格技能定义长什么样核心要素拆解2.1 技能描述是第一优先级写不好就触发不了技能定义里最重要、也最容易被敷衍的字段是description。很多初尝技能的开发者会随手写一句查询订单信息然后发现模型总是该调的时候不调、不该调的时候瞎调。原因很简单模型选择技能主要是靠语义匹配。它读你的描述判断当前用户意图跟哪个技能最接近。描述写得太短信息量不够写得太泛边界不清晰写得过于具体又会牺牲泛化能力。我自己的经验是技能描述至少要覆盖四个维度能力范围这个技能具体做什么输入是什么输出是什么适用场景什么样的用户问题应该走到这个技能举1到2个典型例子边界声明什么样的请求不应该用这个技能明确说不要比可以更重要关键约束比如权限要求、数据范围、语言偏好、时效性要求举一个实际例子。我最初给某内部数据系统的查报表技能写的描述是根据用户要求查询数据报表结果模型经常把帮我看看上个月的销售额趋势这个本该走分析技能的问题也路由到了查报表技能上。后来改成技能名称query_report 描述查询已生成的历史数据报表支持按时间范围、部门、指标维度筛选。适用于用户明确要求查看某张具体报表、导出报表、核对报表数据的场景。不适用于需要临时聚合、对比分析或预测的场景这类需求应路由到analyze_data技能。注意只能查询当前账号有权限的报表涉及跨部门数据需先确认授权。改完之后路由准确率明显提升。描述里的不适用声明尤其有用它相当于帮模型建立了一个负向边界。2.2 输入协议、前置条件与回退策略描述决定了技能能不能被正确触发而输入协议和回退策略决定了技能被触发后能不能稳定跑完。输入协议方面推荐用结构化Schema来约束参数而不是靠模型自由发挥。比如一个发送通知技能参数里应该明确哪些字段必填、哪些可选、枚举值有哪些、字符串长度上限是多少。模型在处理模糊输入时经常会脑补出根本不存在的参数或者把两个参数搞混。结构化的Schema虽然不能完全杜绝这类行为但至少能把错误限制在可枚举的范围内。前置条件也值得单独拿出来设计。很多技能的失败都不是因为逻辑写错而是因为执行时机不对。比如生成周报技能需要前置条件本周的工单数据已经完成汇总发送合同技能需要前置条件合同已经通过审批流程。这些条件应该以可判断的形式写在技能定义里模型在调用前先做一次条件检查不满足就直接返回当前无法执行此操作因为XX条件未满足而不是硬着头皮去跑。回退策略是被讨论得最少、但对生产环境最要命的一块。技能执行失败之后怎么办常见做法是给技能配置一层兜底重试一次、降级到简化版逻辑、或者返回明确的错误信息让模型重新引导用户。我倾向于在技能定义里写一个failure_hint字段告诉模型如果这个技能返回错误码ERR_403说明当前账号权限不足你应该提示用户联系管理员而不是反复重试。这块设计好了Agent的稳定性会明显上一个台阶。没有回退策略的技能就像一个没有备用方案的人一旦主路径出问题就只能原地卡住。3. 从零搭建技能库的实操过程3.1 需求拆解把业务动作映射成技能直接拍脑袋列技能清单是不行的。我在做技能库规划时会先做一轮动作盘点从真实对话记录和业务需求里提取高频操作。具体做法是找最近一个月内人工客服处理过的工单把每一条工单背后的操作意图抽出来归类统计。比如客户要求退款、客户询问物流状态、客户申请发票这些都是高频动作。然后对每个动作问三个问题这个动作是否需要外部数据或系统支持这个动作是否可以标准化成固定流程这个动作是否经常出现变体比如国内订单和国际订单处理方式不同能标准化、有明确系统支持、且出现频率够高的动作才适合封装成技能。低频但流程复杂的动作也可以封装但要权衡维护成本。一个容易踩坑的地方是技能粒度。粒度太粗一个技能里塞了太多逻辑模型决策容易失控粒度太细技能数量爆炸模型在路由时也会犯迷糊。我个人的经验是一个技能应该对应一个用户可感知的完整服务而不是一个内部函数。填写订单备注和修改订单收货地址可以是两个技能但查询订单和查询订单历史版本做成一个技能会更自然因为用户在表述这两个需求时语义边界往往是模糊的。3.2 技能文件结构与编写模板技能定义最终要落地成文件或配置不同框架的格式不同但核心结构大同小异。我自己习惯按下面的模板来组织你可以直接参考name: query_order description: 查询订单详情包括订单状态、商品明细、金额、物流信息。 适用于用户询问我的订单到哪了、订单详情等场景。 注意 - 仅支持查询当前登录账号自己的订单 - 如果要查询历史订单需要用户提供准确的订单号或下单时间范围 - 不适用于退款进度查询请使用 refund_status 技能 input_schema: type: object properties: order_id: type: string description: 订单号必填 include_logistics: type: boolean default: false description: 是否返回物流轨迹 required: - order_id preconditions: - 用户的登录态有效 - 订单号属于当前用户 steps: - call_order_service - filter_order_owner - enrich_logistics_info fallback: - on_error: ORDER_NOT_FOUND action: return_error_hint hint: 请用户核对订单号是否正确模板里有几个细节值得注意。description里的不适用于描述前面已强调过是路由准确率的关键。input_schema要尽量把必填项和可选项区分清楚减少模型自由发挥的空间。steps并不是给模型看的执行脚本而是给开发者维护用的流程说明方便后人理解这个技能内部发生了什么。还有一点必须提醒技能描述不要使用如果用户大概可能想要这类模糊措辞。模型对概率性表述非常敏感你写得越犹豫它就越不敢触发。描述要自信、明确、直接。3.3 测试与迭代单技能验证和组合验证技能写完不是直接上线的需要一套测试方法。我常用的做法是分三层。第一层是单元验证。针对每个技能准备20到30条典型用户问题跑一遍路由测试看模型是否正确触发目标技能并且参数解析是否准确。这一步能快速发现描述里的歧义点。第二层是组合验证。模拟真实多轮对话用户会在一次会话里连续触发多个技能。比如先查订单再申请退款再查看退款进度。这一层主要验证技能之间的切换是否顺畅、上下文是否被正确保留、有没有技能被误触发。第三层是对抗测试。准备一些边界问题、恶意输入、模糊表述看技能是否会被错误触发或者绕过约束。比如用户问帮我把这个订单标记为已签收但当前用户并不是收货人此时技能应该拒绝执行而不是照单全收。三层跑完之后技能才能进入灰度环境。我在云上搭了一套灰度管线流量先切5%跑一周观察路由成功率、平均延迟和人工介入率没有明显劣化再逐步放量。这套流程跑下来技能库的质量会稳定在一个比较高的水位。4. 多技能编排协作、冲突与上下文管理4.1 优先级与路由设计技能数量一多路由歧义就会冒出来。比如用户说帮我处理一下这个订单订单查询技能、订单修改技能、订单取消技能都有可能被触发。这种时候光靠描述去让模型自己选多半会翻车。我给技能设计了一个显式的优先级字段。数值越高表示当多个技能疑似匹配时优先触发它。比如订单相关的一组技能取消操作的优先级要高于查询因为取消动作一旦误触发代价远大于查错一个订单。还有一种更稳妥的做法是给技能定义alias_list把业务里常见的习惯说法、同义表达都列进去。比如退单是取消订单的别名查物流是查询物流信息的别名。模型做路由匹配时先走alias精确匹配再走语义匹配命中率会有明显提升。不过要提醒一句别在alias_list里堆太多口语化变体。我见过一个团队给查询余额技能写了四十多个别名结果任何跟钱沾边的问题都会被路由过去误触发率高得吓人。别名的作用是消除明确的同义表达不是做语义扩展。4.2 上下文窗口与中间产物管理多技能编排里最隐蔽的坑是上下文失控。每个技能执行完都会产生输出这些输出如果全部塞回对话上下文里几轮交互下来上下文就被撑爆了模型开始遗忘早前的关键信息。我试过几种方案最后稳定下来的是中间产物摘要化策略。技能执行完毕后不把完整结果返回给模型而是返回一份结构化摘要。比如查订单技能执行完返回给模型的不是完整的订单详情JSON而是{ summary: 用户订单 #20241101 显示已签收包含3件商品总价328元, data_ref: result://order_12345, needs_followup: true }模型只需要知道订单已签收、总价328元这个事实就够了。如果后续用户问具体是哪三件商品再通过data_ref去拉取详细数据。这套机制能大幅压缩上下文占用让模型在多轮对话中保持清醒。另一个值得注意的点是技能之间的中间产物传递要靠显式接口不能靠模型自己从上下文里猜。比如A技能生成了一个客户IDB技能需要这个ID那么A技能的返回值里必须显式携带customer_id字段并且要写清楚字段名和取值。模型在编排时才能准确地把数据传递过去。4.3 幂等与重试真实业务中的稳定性技能编排一旦进入生产环境幂等设计是绕不开的话题。什么叫幂等就是同一个技能调用多次结果是一致的不会因为重复执行而产生副作用。比如发送邮件技能如果模型因为网络抖动重试了一次用户就会收到两封相同的邮件这就是不幂等。我在设计技能时会为所有有副作用的能力配备idempotency_key。以创建工单技能为例调用方生成一个唯一key服务端记录这个key对应的工单信息。如果同一个key再次进来直接返回已有的工单而不是重新创建。这个设计在Agent场景里特别重要因为模型的重试行为是不可预测的你没法保证它不会在同一个问题上重复发起调用。还有一个小经验重试策略不要全局统一要按技能单独配置。查询类技能可以重试三次写操作类技能重试一次超过就转入人工兜底。重试间隔要配上指数退避避免技能集体重试把下游系统打垮。这些细节在单技能开发时看不出来一旦技能编排上线遇到一次流量高峰就明白它的价值了。5. 技能失效排查实录常见问题与定位方法5.1 技能没有被触发这是所有Agent开发者最先遇到、也最头疼的问题。用户问题已经摆在那了模型就是视而不见宁可自己瞎编也不调用技能。我排查这类问题时第一步看的是描述语句的语感。描述里如果用了太多的条件从句、转折词、抽象名词模型匹配的置信度就会被稀释。比如该技能可能适用于需要查询订单信息的用户需求场景这句话就有大量虚词模型很难抓住核心语义。改成查询订单信息包括状态、金额、物流这样名词密集、动词清晰的句式触发率会明显提高。第二步是看是否缺少示例。几乎所有的主流Agent框架都支持在技能定义里附带example_questions。这是最直接的调参工具你给模型看三个像模像样的用户问题它就知道什么时候该触发。我通常在技能上线后收集线上路由失败的badcase挑出最典型的几条补充到example_questions里持续迭代。第三步是检查是不是被更高的优先级技能拦截了。多个技能存在语义重叠时模型会优先匹配置信度高的那个。如果A技能优先级设置过高B技能的召回率就会受影响。可以用路由日志定位看模型到底是在A和B之间纠结还是完全没往B那边想。5.2 返回结构漂移技能触发没问题但返回的数据结构不稳定今天返回JSON明天返回Plain Text或者字段名变了。这类问题会让下游解析逻辑直接崩掉。返回结构漂移的根源往往在于技能内部用了不严格的输出协议。我给技能定了一条铁律技能的输出必须经过schema_validator校验不合法就重跑一次解析逻辑还不行就返回固定错误码。这条规则写进代码里之后结构漂移的问题基本绝迹。另外如果技能内部调用了多个子服务一定要在最终输出前做一次统一封装。不同子服务返回的字段名可能不一样有的叫orderId、有的叫order_id、有的叫OrderID。封装层要负责把所有字段归一化模型拿到的永远是一份符合约定Schema的结果。5.3 数据污染与幻觉技能最后一种问题比较隐蔽但一旦发生就是大事。技能在执行过程中因为某个上游系统返回了异常数据模型基于这些数据生成了错误的结论。比如查询库存技能返回了一个负数模型直接告诉用户产品缺货无法下单而实际上这是因为库存服务出现Bug返回了占位符。这类问题靠测试很难全部发现因为异常数据往往是偶发的。我的应对方案是给技能增加一层合理性校验输出结果的关键字段在返回给模型之前先跑一遍规则引擎比如数量必须大于等于0、时间格式必须符合ISO标准、金额必须在约定范围内。校验不通过就走降级路径返回暂时无法获取准确数据而不是把脏数据暴露给模型。另外幻觉技能是我新造的一个说法指的是模型脑补出一个不存在的技能名称去调用或者把两个不相关技能拼接在一起调用。这个问题通常出现在技能数量过多且命名风格不统一的场景。给技能命名时统一用动词名词的格式query_order、create_ticket、update_address能有效减少这类幻觉。6. 技能库的长期维护与评测6.1 评测集与回归测试技能库上线之后不是一劳永逸的它需要像代码一样长期维护。每次模型底座升级、Prompt策略调整、业务逻辑变更都可能导致技能表现波动。我建议每个技能库都配套一个评测集里面存放三类用例历史badcase、典型业务场景、边界对抗场景。每次改动上线前跑一遍评测集对比改动前后的路由准确率、参数解析准确率、任务完成率。指标没有回退才允许上线。评测集不能是静态的它需要跟着业务一起生长。每过一段时间我会从线上日志里抽取新的badcase人工标注后补充进评测集。这个动作看起来不起眼但它是技能质量保持稳定的核心保障。没有评测集的技能库改动全靠拍脑袋迟早会把线上搞崩。6.2 版本管理与灰度策略技能定义本身就是代码应该走版本管理。但跟普通代码不同的是技能变更的效果往往不是即时显现的它要等模型实际调用时才能验证。我的做法是给每个技能打一个version字段变更时在后台保留上一版本。灰度发布时让5%的流量走新版本95%走旧版本对比两边的成功率。确认新版本无劣化后再全量切换。一旦发现异常可以秒级回滚到旧版本。这套机制在单Agent场景下可能有点重但对于已经进入生产环境的技能库来说成本很值。我就经历过一次技能描述改动导致路由大范围偏移的事故幸好当时有版本回滚机制十分钟内恢复了线上服务。之后我再也没有绕过灰度直接改线上技能。最后再分享一个个人习惯每隔一段时间我会把技能库从头到尾审视一遍找出那些调用频率极低、或者描述逻辑已经跟业务脱节的技能果断下架。技能库里堆着一堆没人用的技能不仅浪费维护精力还会干扰模型的路由判断。少而精永远比多而杂更靠谱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑