资讯动态

Agent-Skills设计实战:从LLM智能体到可复用业务技能封装

发布时间:2026/10/8 4:57:57 来源:尧图企业网站定制
上个月帮一个做电商售后系统的团队梳理客服智能体对方的困惑很典型模型已经很聪明了但一旦要让它真正操作业务系统就完全不是那么回事。那段时间我正好在整理一套自己的 agent-skills 设计思路也就是把智能体能干的复杂动作沉淀成可复用、可评测、可迭代的独立技能模块。这篇文章就是把那套思路完整展开讲清楚 agent-skills 到底是什么、如何设计、怎么落地以及我在真实业务场景里踩过的那些坑。如果你正在做智能体应用或者想把 LLM 接进具体的业务流程这篇文章可以当一份避坑参考。1. 一个典型需求如何变成 agent-skills 的落地场景没有场景的概念讨论都是空谈。我习惯用一个内部复盘过很多次的需求来开场售后工单处理。1.1 需求背景客服工单处理为什么不能只靠提示词客户的售后工单主要有四类退款申请、物流查询、发票补开、投诉升级。看起来都是标准操作但真的交给一个裸智能体去处理时问题立刻暴露。团队最初的做法是写了一个非常详细的系统提示词把售后流程、规则、门店列表、可承诺的补偿额度全部塞进去然后让模型直接生成回复或操作指令。效果怎么样呢退款金额超权限的时候模型照样对客户承诺“可以全额退”客户问物流卡在哪里模型不知道去哪查快递接口就凭训练数据里的大致印象编了一个状态更危险的是有用户只是问了一句“你们是不是发错货了”模型直接开始走投诉升级流程把工单状态改掉了。这些问题不是模型能力不足而是交互方式错了。对话式智能体擅长的是理解和表达比如判断用户情绪、总结诉求、生成回复。但涉及外部系统动作时它需要一个稳定的“动作接口”而不是凭感觉自由发挥。单纯追加强力提示词等于要求一个实习生仅凭一张流程说明就操作 ERP他当然会经常出错。那段时间我们统计过裸提示词方案下工单处理的一次通过率不到 60%而且每次失败都要人工介入修正状态比全人工还累。问题的核心在于把“处理工单”这个大目标直接交给模型模型要在一次推理里同时解决意图识别、业务规则匹配、数据查询、动作执行四件事任何一环出问题整个工单就废了。1.2 为什么最终选择技能化封装而非自由工具调用找到问题后团队里出现了一个分歧有人建议直接把订单查询、退款、发票开具、物流跟踪这些企业内部 API 全部以 function calling 的形式开放给模型让模型自己决定调哪个。听起来很灵活但上线后有更大的麻烦。内部系统稍微成熟一点API 数量就是几十上百个。模型面对 60 多个工具函数的候选列表时工具选择的准确率会明显下降。这不是模型厂商没优化好而是候选空间太大之后函数描述之间的微小差异很容易被模型忽略。比如 getOrder 和 queryOrderDetail 这两个函数在真实业务里可能一个查订单头信息、一个查订单明细行但模型根本分不清该调哪个。更麻烦的是很多业务动作不是单个 API 能完成的。比如“审核退款”这个动作可能需要先查订单、再核对售后单、再调用退款接口、最后给用户发通知四步拆开来让模型自己编排每一步都可能出错而且错因根本无法追溯。我给的方案是技能化封装。也就是把“审核退款”这一整条链路包括前置校验、多步调用、结果聚合打包成一个技能。智能体在运行时看到的不是 60 个函数而是 6 个技能查订单、查物流、审核退款、开票、升级投诉、查售后单。模型只需要在少数几个业务目标之间做选择而不是在上百个底层函数里大海捞针。这背后其实是认知负荷的问题。对 LLM 而言选择空间越大误选率越高而把底层细节隐藏起来暴露“完成业务目标”的接口选择空间就小得多。这也解释了为什么 agent-skills 的粒度设计如此关键——它的本质是把复杂度折叠起来让模型在业务语义层面做决策而不是在函数签名层面做决策。2. 技能化改造的关键设计定义、参数与触发边界确定了要走技能化路线之后下一个问题就是一个技能到底长什么样我见过很多团队把技能写成一大堆字段的 JSON然后描述写得乱七八糟模型根本看不懂。技能定义本质上是一份给模型看的“接口使用说明”它有两个用户一个是执行引擎一个是模型本身。执行引擎看字段模型看描述文本。2.1 技能定义文件应该长什么样我习惯用 YAML 来定义技能因为可读性好也方便团队评审。一个典型的技能定义长这样name: apply_refund description: 当用户明确表达申请退款诉求且订单状态为已签收时使用该技能。 若订单尚未签收不要调用先引导用户确认是否收到货。 该技能会校验退款金额是否在权限范围内超出权限时返回需要人工审批。 input_schema: type: object properties: order_id: type: string description: 用户提供的订单编号 reason: type: string enum: [发错货, 质量问题, 不想要了, 其他] description: 用户选择的退款原因 required: [order_id] output: type: object properties: status: type: string enum: [approved, rejected, needs_review, invalid_order] refund_amount: type: number message: type: string这个定义看似简单但有几个字段容易被忽略。最容易被忽略的是 description 里的否定句“若订单尚未签收不要调用”。我后来发现描述里正面写“什么时候可以用”的效果远不如同时写“什么时候不能用”的效果。模型对边界条件的理解很大程度上决定了技能会不会被误触发。output 的定义同样重要。很多团队只定义输入不管输出结果技能执行完返回一个自由格式的 JSON下一环节根本没法用。输出的结构决定了后续技能是否能链式调用。我一般要求每个技能的输出都有 status 字段而且 status 的取值是封闭枚举这比靠关键词去猜结果靠谱得多。2.2 触发条件描述怎么写才不会被模型误用技能描述是给模型看的说明书而不是给人类看的文档。这两者的写法要求差异很大。人类可以容忍一句“处理退款相关请求”模型不行它在遇到模糊描述时会随机选择或者干脆把多个相似技能都试一遍。真实案例“申请退款”这个意图下我们最初做两个技能一个叫 create_refund_request描述是“创建退款申请单”另一个叫 cancel_order_refund描述是“取消退款申请”。结果用户说“我不退了”模型有 40% 概率去调用创建退款申请因为它识别到了“退款”两个字。后来我把描述改成了create_refund_request当用户要求退钱、退款、退学费等表达退款意愿时使用。若用户表达的是“不退了”“取消退款”严禁调用本技能。cancel_order_refund当用户说“不退了”“取消退款申请”时使用。若用户仍在表达退款诉求严禁调用本技能。改了之后误选率大幅下降。这件事给我的启发是触发条件描述要像给实习生写交接说明一样不仅要写“遇到什么情况做什么事”还要写“遇到什么情况不要做”而且要使用业务原生的词汇。用户说的是“退学费”“退钱”不是“创建退款申请单”。技能的描述应该贴着用户的语言习惯来而不是贴内部系统的命名习惯来。2.3 参数清单的松紧取舍技能定义的第三个关键是参数设计。这里我见到的两种典型错误方向完全相反。一种是把所有参数都设为必填。order_id、reason、amount、supplier、channel 全都要求模型先填全再执行。结果是用户只说了一句“我要退款”模型就开始连环追问一个问题接着一个问题用户体验非常差而且很多参数根本不需要前端收集执行引擎内部通过 order_id 就能查到。另一种是完全不设校验规则。所有参数都 optional类型也不管模型传一个字符串也好、传一个 null 也好执行引擎都照单全收。结果脏数据进入业务系统轻则报错重则直接把退款金额写成负数。我的经验是必填参数控制在 3 个以内最好只有一个因为大模型从自然语言里提取参数本身是有失败率的每多一个必填参数执行失败的几率就高一截。能通过内部查询推导出的字段就不要放到参数清单里。比如 refund_amount 其实可以从订单和售后单算出就不需要模型传reason 如果是枚举就在 schema 里用 enum 限定让模型只能在几个选项里选而不是自由发挥填一个“我就是不想买了”这种无法匹配的文本。日期参数也要给定明确格式我们发生过模型把“2025年3月2日”识别成“3月2日2025年”导致日期解析失败的情况。参数越松模型越容易成功提取但校验规则一定要紧兜底好脏数据入口这两者并不矛盾。3. 从零实现一套最小可用的技能编排系统技能定义只是静态描述真正让它跑起来还需要一个能注册、路由、执行、容错的运行时。很多团队卡在这一步觉得自己写不出来其实最小可用的实现远没有想象中复杂。3.1 技能注册与路由让模型在运行时选对技能我在项目里用的是 Python 实现的注册中心思路非常简单用装饰器把技能函数注册到一个全局字典里启动时自动扫描。SKILL_REGISTRY {} def skill(name): def decorator(func): SKILL_REGISTRY[name] func return func return decorator skill(apply_refund) def apply_refund(params: dict): order_id params[order_id] # 内部编排查订单、校验权限、调用退款接口 return {status: approved, refund_amount: 99.0}运行时最关键的部分是路由。我会把注册表里所有技能的名称和描述拼成一段候选清单注入模型的上下文让模型在候选清单里做选择而不是自由发挥。当前可用的技能如下 1. apply_refund当用户明确表达退款申请意愿且订单已签收时使用…… 2. query_logistics当用户询问包裹当前物流位置时使用…… 3. issue_invoice当用户申请开具发票时使用…… 请根据用户最新请求选择最合适的技能并返回技能名称和参数JSON。 如果用户请求不在任何技能范围内返回no_skill。这段 prompt 不需要写得太复杂关键是候选技能数量要少。我强烈建议同一时刻注入上下文的技能不要超过 10 个。超过这个数量模型的选择准确率会肉眼可见地下降而且每次请求的 token 开销也会变大。如果你的技能库已经很大就按业务场景分组比如“售后域”的技能有 8 个、“销售域”的有 5 个运行时根据用户意图先粗分类再只注入某一组的技能清单。3.2 技能执行层的任务拆分与结果回填路由完成后就是技能的真正执行。执行层要处理三件事参数校验、调用外部依赖、结果规范化。参数校验不能只靠模型自觉执行层要再兜一道。模型说参数填好了不代表真的符合要求至少要用 JSON Schema 校验一次不符合的就直接返回参数错误让模型自己补。这一步能拦掉大量脏数据。结果回填有两种常见模式。第一种是简单场景技能执行完直接把结果拼进回复不需要再调模型。第二种是复杂场景技能执行结果还要作为下一轮的上下文让模型继续决定是否需要调用别的技能。比如用户申请退款第一个技能返回“该订单关联的物流仍在运输中系统不支持在途退款”这时候模型需要调用另一个技能 query_refund_policy 查询在途订单的退款政策然后给用户解释。这种链式调用是 agent-skills 最核心的编排方式。为了支持链式调用技能输出必须结构化。我要求每个技能返回固定字段status 表示结果状态成功、失败、需人工等message 表示给模型看的解释data 表示后续可用的结构化数据。有了这个规范编排逻辑就变得很简单result execute_skill(selected_skill, params) if result[status] needs_review: next_step route(result[message], context) result execute_skill(next_step, result[data])3.3 失败重试与兜底策略技能执行不可能永远成功至少三种失败场景必须有预案。第一种是模型路由失败也就是模型选错了技能。我们的做法是让执行引擎验证技能参数和业务前置条件比如 apply_refund 内部会校验订单状态如果发现订单根本不存在直接返回 invalid_order模型看到这个结果会重新选择技能。这比在路由层强行要求模型每次都对要稳妥得多。第二种是外部依赖失败比如订单接口超时。这里的核心是幂等性。重复发起退款请求会不会产生两笔退款如果接口不是幂等的就得在技能内部维护一个操作记录表重复请求直接返回上次结果。重试次数建议控制在 2 次以内超过就标记为 failed转人工不要无限重试。第三种最容易被忽略模型没把握时的兜底。我发现很多团队的设计里模型在候选技能里找不到匹配项时会硬选一个然后执行引擎就真的去执行了。更好的做法是让模型有能力返回“无法处理”然后触发一个 ask_clarification 的兜底技能回复“暂时还不能为您办理需要再确认一下订单信息”。宁可让用户多等一轮也不要让系统做一个可能错误的业务动作。一句话当模型不确定时主动承认不确定比瞎猜要安全得多。4. 实测中反复出现的五个翻车点理论设计再完整一上真实流量就会现原形。我把自己在 agent-skills 落地过程中踩过的五个坑整理出来每一个都是真实发生过、并且花了不少时间才定位的。4.1 描述相似的两个技能互相抢占我们最早期的技能清单里有两个一个是 query_order查询订单基础信息包括订单状态、金额、收件人另一个是 query_logistics查询物流轨迹。用户问“帮我看看我那个手机到哪了”模型有时候调 query_order有时候调 query_logistics因为两者的描述里都有“查询”和“订单”这些高频词。定位问题靠的是调用日志。我们发现同一句话在不同时间被路由到不同技能原因就是描述区分度不够。解决方案有两个一个是合并如果两个技能本质上都在解决“订单状态查询”干脆合成一个返回内容里同时包含基础信息和物流轨迹另一个是强化描述差异在描述的开头就强调“物流轨迹查询专门针对快递流转位置不包含订单金额等信息”。后来我把所有技能的描述都做了一次“差异度审查”把描述里出现频率最高的十个词标出来确保没有两个技能的高频词集合过度重合。这个方法很笨但非常有效。4.2 技能粒度过粗导致不可复用创业团队刚开始做技能时很容易把需求描述得又大又全。比如我们本来只需要“查询退款进度”结果交付的技能叫“处理退款全流程”里面包含创建退款单、查询退款进度、修改退款金额、导出退款记录。听上去很方便但实际调用时问题很大。问题在于技能内部逻辑被硬编码成了一个长流程先创建退款单再查进度再尝试改金额。但真实场景里用户往往只问“我的退款怎么还没到”这种情况下根本不应该创建退款单。模型选中这个技能后执行引擎硬着头皮跑完了前半段产生了一个根本不存在的退款单只能再去系统里把它作废。粒度设计的正确原则是“一个技能完成一个业务动作”。查询是查询创建是创建修改是修改分别独立。这样模型的选择压力更小技能复用的可能性更高。你可以用“一句话能不能说清这个技能的输出”来判断粒度是否合适如果说一句说不清那就说明它承担了太多职责。4.3 上下文窗口被技能说明占满我第一次做技能库的时候觉得技能写得越多越好结果一口气注册了 30 多个。然后发现每次对话的 token 消耗暴涨甚至在没有用户输入的情况下光是技能清单就已经占了一万多 token。更严重的问题是技能清单太长会导致路由准确率下降。模型需要在一份很长很长的说明书里找到当前任务对应的段落注意力会被其他无关技能的描述干扰。比如一个用户问发票问题模型从 30 个技能里翻找很可能被“退款”“物流”这些高频词吸引过去。解决办法是动态加载。把技能按业务域分组对话开始后先做一次粗粒度意图识别判断当前命中的业务域只注入该域的技能清单。我们实测下来动态加载后路由准确率提升明显token 消耗也降了一个数量级。不要把所有鸡蛋放在一个 prompt 里。4.4 有状态技能在跨会话场景下的失效我们早期设计技能时默认无状态每个技能只根据当前传入的参数执行。但实际业务很快出现一个问题用户前一天申请了退款第二天又来问进度。新会话里模型没有“前一天已申请”的记忆于是调用了 apply_refund 而不是 query_refund_progress又重新发起了一次退款申请。这个问题的根源在于混合了状态需求和且无状态设计。解决方法是把所有状态外置用会话 ID 作为关联键存入一个记忆库。每次技能执行时先从记忆库读取该会话的历史操作记录把上下文拼进技能参数。apply_refund 内部要有幂等检查如果发现同一个订单已经存在退款申请单直接返回“该订单已提交退款申请”而不是新建一张单。有状态设计会增加实现复杂度但业务上根本绕不开。我的建议是不要在技能内部自己维护状态而是交给统一的状态管理组件技能实现只做“读状态、按状态决策、执行动作、更新状态”这四个步骤。4.5 评测只看“最后结果”掩盖调用错误最后一个坑是评测方式的问题。团队最初验收技能效果时只看一个指标用户问题有没有被成功解决。结果发现通过率挺高但日志里调用链简直惨不忍睹。典型的情况是用户问“怎么取消订单”模型先调了 cancel_order又调了 query_order最后因为 cancel_order 根本没生效但 query_order 返回了订单信息模型硬凑了一个答案给用户。从结果上看用户得到了一段“看起来合理”的回复但业务根本没执行。这个问题靠最终结果评测永远发现不了。后来我们引入了“调用路径审计”每个会话都记录完整的技能调用序列。评测时不仅看最终结果还要看调用了哪些技能、顺序是否正确、有没有多余的调用。这一步直接驱动了后续的技能描述优化因为在分析“多余调用”时你能发现模型的真实误选原因而不是靠猜。5. 用评测集守住技能质量指标与回归方法技能写得好不好不能靠感觉。我最终建立了一套评测机制三组指标加上一套回归流程每次改变技能定义都能快速知道是变好了还是变坏了。5.1 三组核心指标成功率、精确调用率、单任务成本指标计算方式作用成功率端到端任务中最终正确完成的占比衡量整体业务效果精确调用率调用日志中路由到正确技能的比例衡量技能定义清晰度单任务成本平均 token 消耗和执行时延衡量系统效率成功率最容易理解但也很容易自欺欺人前面说过了结果对了不代表过程对了。所以精确调用率反而是我更看重的过程指标。如果一个任务的最终结果正确但中间多绕了两次技能调用说明技能描述存在歧义早晚会在更复杂的场景里出问题。单任务成本用来发现“技能描述过长”“动态加载失效”这些隐蔽问题。同样的任务token 消耗突然涨了 50%大概率是注入的技能清单变长了或者模型在反复横跳。成本指标往往是架构问题的先行信号。5.2 评测集的构造与增量回归评测集是我在 agent-skills 项目里投入时间最多的东西。每个技能至少准备 20 到 50 条真实历史样本不仅要有正常情况还要有边界情况。以退款技能为例评测集里至少包含用户明确要求退款、用户询问能否退款、用户说“不退了”、订单未签收要求退款、退款金额超出权限、订单不存在等。建立评测集之后任何一次技能定义的修改都必须跑全量回归。流程很简单运行评测集记录每次路由结果和最终状态对比这次修改前后的精确调用率。如果一项修改让某个技能的精确调用率下降超过 5 个百分点就直接回滚哪怕它让另一个指标变好了。有人问过我用不用 LLM 作为裁判自动评估结果。我用过但结论是LLM 打分器可以筛掉明显不合格的输出但不能完全替代人工抽检。我保留了一个 50 条规模的小样本每周人工审核一次专门看那些模型裁判“拿不准”的边界案例。这个成本不高但每一次都能抓出一两个定义层面的漏洞。6. 后续演进从手工技能库到技能自举当技能数量过百、团队超过三个人之后一个新的瓶颈出现了技能库的管理和增长完全依赖人来驱动。每个人都在手工写技能描述、调 schema、加样本速度跟不上了。这个阶段的演进我开始尝试更结构化的做法。6.1 技能市场的共享与版本管理技能也是代码必须用代码的方式管理起来。我们为每个技能建立了独立的目录包含定义文件、实现代码、测试样本三件事通过 git 做版本管理。技能有语义化版本号修改描述或 schema 必须升级版本不能原地覆盖。这么做的好处是技能可以在团队内部“市场化”。每组业务线把自己沉淀的技能发布出来其他组直接复用只需要传入自己的参数和执行逻辑。比如“订单查询”这个技能技术中台维护一次所有业务线都能用而不是每组都重新踩一遍 prompt 调优的坑。有一点要特别注意分享技能时描述和输入输出 schema 是公开接口内部实现、API 端点、数据库字段不能暴露。然后我们要像对待公共 API 一样对待技能任何破坏性变更都要提前通知依赖方否则下游业务线的智能体可能一夜之间全部失灵。6.2 让智能体自己写技能再进一步的方向是“技能自举”。我观察到一个规律每周的调用日志里总有那么几个失败是重复原因造成的。比如“用户问发票能不能发到指定邮箱”这个需求每周出现十几次但现有技能库里没有对应技能模型每次都在自由发挥效果时好时坏。这类重复失败就是新技能的信号。我的做法是设计一个“技能提案”流程当模型连续多次遇到未覆盖的请求类型时它可以把这类请求的共性描述、期望参数、典型样例整理成一个技能建议报告。人工审核后快速沉淀为新技能。这个流程在初期不能全自动因为模型生成的技能定义经常有幻觉但作为“半自动挖掘工具”非常好用。真正让模型全自动创建技能并投入生产我认为还需要很长的路要走。至少在现阶段全自动生成技能的风险还太高一个粗暴技能很容易在边界场景里做出危险动作。我的态度是让模型做发现者人做审核者分工明确效率和安全都兼顾。6.3 我的实际体会与建议回到开头那个售后客服团队。后来他们的工单一次通过率从原来的不到 60% 提升到了接近 90%靠的不是更强的模型而是更合理地把业务能力封装成 agent-skills。这个过程让我有几个很深的体会。第一先建评测集再写技能定义。我见过太多团队先花两周写了一堆技能然后发现没法回答“你这个技能到底有没有做好”这个问题。评测集先行的好处是你的每次修改都有尺子不会凭感觉做事。第二技能描述是面向模型的 UI值得反复打磨。就像你给用户设计按钮文案一样需要斟酌用什么词、怎么排优先级、怎么给边界条件。不同模型的指令遵循能力不同同一个技能在两个模型上的表现可能差异很大你需要在具体模型上做调优。第三宁可技能少而精不要大而全。5 个高质量的技能比 30 个模糊技能更靠谱。判断标准很简单如果模型连续一周都没有主动调用某个技能要么这个技能是多余的要么它的描述根本没有被模型理解。最后再分享一个小技巧。每次上线新技能前我都会手动构造 5 条绝不会被正常用户问到的边缘case比如“用户发了一个表情包”“用户要求英文回复”“用户把订单号说成发货单号”看模型会不会误触发新技能。误触发是技能上线后最大的风险这一轮边缘测试能帮你拦住绝大部分事故。

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

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

免费获取报价 →
↑