1. 为什么越用越觉得技能才是Agent的灵魂1.1 从一段挫败的对话说起上个月我调试一个内部使用的客服Agent不管怎么调提示词它面对帮我查一下上个季度的退款单这类需求时表现总是飘忽不定。有时候能准确调用查询接口有时候却自作主张地编一个查询中然后开始道歉。后来我把日志拉开一看发现问题根本不在大模型而在于我给了它一堆零散的函数却没有告诉它查询退款单这件事在什么条件、什么流程、用什么参数去完成。那一刻我意识到真正缺的是一层叫技能的东西。大概从2024年开始agent-skills这个概念在AI应用开发圈里被反复提起。它指的并不是某个单一的开源项目而是一种把Agent能力模块化的设计思想把特定任务的执行方式、调用条件、参数规则、内部步骤组织成一套可以被模型识别、选择、调用的技能单元。你可以把它理解为给Agent装上一只工具箱而不是让它每次碰到问题都从零开始抡锤子。1.2 技能、工具、工作流先分清这三件事很多人一开始会把技能Skill、工具Tool和工作流Workflow混为一谈我在早期也犯过这个错误。当时我做的设计是把每个API封装成一个Tool然后让模型自己决定怎么组合。听起来很灵活实际上一跑就露馅——模型经常在组合时迷失方向一会儿以为A接口返回的字段可以直接塞进B请求里一会儿又想跳过校验步骤。后来我把三者的关系重新梳理了一遍用大白话讲就是Tool是最小的原子能力比如发送HTTP请求读取文件执行Shell命令它只做一件事不知道上下文。Skill是围绕某个业务目标组织的能力包里面可以包含多个Tool的调用逻辑、参数转换规则、前置校验、错误处理甚至还可以内嵌一小段提示词来指导模型如何执行。Workflow是更大粒度、相对固定的流程编排通常由多个Skill按顺序或分支组合而成适合那些步骤基本不变、不需要模型频繁发挥的场景。区别在于Tool是手Skill是会用手完成一件事的熟练工种Workflow是整个车间的一条生产线。手可以换熟练工种可以跳槽但生产线一旦铺好它的稳定性靠的是流程而非个人。从工程实现角度看技能体系的核心收益有两点。第一它把模型要学会做什么转化为系统能替模型准备好什么显著降低了对模型推理能力的依赖也让行为更可预期。第二它把可复用的业务逻辑从对话上下文里剥离出来放到结构化的代码和配置里便于测试、版本管理、权限控制。这两点在我实际项目里带来的改变是肉眼可见的同样是处理语义模糊的用户请求技能化改造后关键路径的成功率从大概65%提升到了接近90%。1.3 我在什么场景下开始认真对待技能体系如果你只是做一个demo让模型调用一两个在线API那确实不需要考虑技能设计。但我的项目跑起来之后需求接连涌过来要接订单查询、要接退换货流程、要接物流轨迹、要接客服备注模板。每个能力单独看都不复杂但放在一个Agent里就乱套了。有时候模型把订单号当成物流单号传给物流接口有时候它把用户的话当成系统指令直接执行有时候它接完一个技能后完全忘了前面对话里已经问过的关键信息。真正促使我去整理技能体系的导火索是一次线上事故。用户问我的羽绒服洗了一次就起球能退吗Agent调用了退货政策查询技能返回的结果是需要收货时间在7天内但Agent忽略了一个更重要的隐藏条件贴身衣物和已洗涤商品不能退。政策技能返回的是文字模型没有系统性地去核对所有条款就下结论说可以退。这个问题的根子在于技能只回传了一段文本而没有把判断是否可退的逻辑本身承接过来。所以后面我在设计技能时强制要求每个技能必须回答清楚四个问题什么条件下能被调用、需要模型提供哪些信息、执行后返回什么结构化结果、如果执行失败模型该怎么向用户说明。这四个问题就是agent-skills设计的骨架。2. 先把骨架搭对技能的定义、输入输出与边界2.1 一份能落地的技能定义长什么样我见过很多团队给技能文档定的规范动辄几十页最后没人看。我的做法比较务实用一份YAML头部加一份Python实现来定义一个技能YAML给模型看调用约定Python给系统看执行逻辑。一个最简技能定义大致长这样name: order_refund_check description: 核对订单是否符合退款条件。当用户询问退款、退货、是否可以退、 能不能退、退钱等意图时使用。需要订单号或用户的购买信息。 如果用户只问退款政策但不涉及具体订单请使用 refund_policy 技能 不要使用本技能。 arguments: order_id: type: string description: 订单编号通常为SO开头的10位字符来自对话上下文或用户提供。 required: false user_id: type: string description: 用户唯一标识用于关联查询购买记录。 required: true returns: eligible: type: boolean description: 是否满足退款条件。 reason: type: string description: 判定结果的详细原因供Agent组织自然语言回复。 policy_items: type: array description: 命中并核对过的政策条款列表。这份定义里description写得特别关键。我在早期版本里只写了用于查询退款条件结果模型在用户抱怨这衣服质量也太差了吧的时候就触发调用返回的结果和用户情绪诉求完全不匹配。后来我把描述改成了核对订单是否符合退款条件仅当用户明确要求退款或退货时使用误召回率明显下降。技能描述本质上是在教模型做意图路由它的重要性不亚于参数设计。2.2 输入输出Schema技能和Agent之间的契约输入输出Schema是整个技能体系里最不该省事的部分。有些开发者在定义参数时为了省事所有技能一律收一个query字符串然后靠技能内部去解析。这在实验阶段很爽但一旦技能数量上来你根本没法判断模型传进来的query到底哪个信息够用、哪个信息是编的调试成本会越来越高。正确的做法是宁可让模型多传几个字段也要让每个字段语义清晰。比如上面的order_refund_check我要求user_id是必填。为什么因为模型很容易从上下文里提取用户名字或者手机号但底层退款系统只认user_id。如果Schema里没有约束模型就可能传一个张女士进来技能服务端拿到后还得做一层模糊匹配匹配错了又是一条线上事故。输出的结构化同样重要。不要让技能返回一大段渲染好的文本因为模型会把这当成金科玉律直接念给用户而不会去对照里面的逻辑。让技能返回eligible、reason、policy_items这种字段模型只需要把它们组织成符合当前对话语气的回复即可。这里有一个我踩过的坑返回的reason字段如果写成订单已超过7天不在退货范围内模型可能一字不改地告诉用户语气很生硬正确做法是让reason记录的是事实要素比如下单时间2025-02-10退货截止日期2025-02-17当前日期2025-02-20由模型组织成按退货政策您这笔订单已超过可申请退货的截止时间就自然多了。参数校验和边界条件应该放在技能内部完成而不是指望模型。模型提参经常丢三落四所以我在技能实现里会做一个前置校验发现order_id缺了就去上下文里再找找找不到就返回一个专门的prompt_needed状态告诉Agent去追问用户。这个设计能让Agent表现得很聪明它知道自己信息不够而不是硬着头皮去猜。2.3 边界问题技能越薄越好还是越厚越好这是我在团队里和同事争论过好几次的问题。薄技能只封装一次API调用厚技能把一整条业务流程都包进去比如从校验权限、查询订单、计算退款金额、生成退款单一直到通知财务系统。我的结论是技能薄厚取决于业务逻辑的稳定性。如果流程基本不变厚技能能大幅降低模型的决策负担也让出错面更小。退款流程就是个典型例子——它涉及多个系统的联动中途一旦让模型自由发挥它可能跳过审批环节直接调退款接口。但是厚技能也会带来麻烦一旦某个子环节需要调整你得把整个技能重新测试一遍迭代速度立刻慢下来。折中的做法是分层。底层的Atomic Skill保持薄只做单系统的事上层再做一个Composite Skill编排底层几个Atomic Skill。模型对外只暴露Composite Skill减少选择数量而底层Atomic Skill可以单独测试、单独复用。这样既照顾了稳定性又没有放弃灵活性。我在实际项目里把技能数量控制在顶层不超过15个底层可以有几十个Agent的选择压力小很多命中率也就上去了。3. 实际开发中的技能编排几个值得抄的模式3.1 顺序编排把固定流程写成技能链技能编排是agent-skills里最有意思的部分。最简单的模式是顺序编排适用于流程固定不变的场景。以我的客服Agent为例用户申请退款时标准的处理链路是身份确认 - 查询订单 - 校验退款政策 - 计算退款金额 - 提交审批。每一步依赖上一步的输出中间不需要模型做太多判断只需要沿着链路走下去。在实现顺序编排时我倾向于在技能内部用状态机去驱动而不是把每一步都开放给模型任意调用。模型只负责在入口处调用一次退款处理技能技能内部自己维护步骤流转每完成一步更新状态并向模型反馈当前进度。这样做的原因是一旦某个环节失败状态机可以精准定位问题出在哪一步模型也可以根据状态信息向用户给出准确回复您的身份已确认订单查询发现该商品属于特价清仓品按规则不支持退货是否需要帮您登记一次售后检修而不是含糊地说系统开小差了。顺序编排的关键在于每一步的输出要能顺畅地成为下一步的输入。这里我犯过的一个低级错误是订单查询技能返回的金额单位是分政策校验技能判断免运费门槛时用的是元结果一个满199免运费的订单被判断成不满足条件差点引发客诉。现在我的技能约定里强制执行一条规范所有涉及金额、日期、数量的字段必须在Schema中标注单位并在技能内部完成单位统一后再传递。3.2 条件分支让模型来判断该用哪一步不是所有场景都能预先铺好一条固定链路。比如用户进线后可能是问订单可能是问物流可能是投诉也可能是想改地址。这种场景下如果一个技能直接承载所有可能性逻辑会变得非常臃肿。我采用的方式是设计一个意图分流技能它不做具体业务只负责根据对话内容判断下一步调用哪个技能把决定权交给模型。def route_by_intent(context): 意图分流的实现思路实际会通过LLM调用完成。 intent detect_intent(context.get_recent_messages()) route_map { refund: order_refund_check, logistics: logistics_track_query, address_change: order_address_modify, complaint: after_sale_complaint_register, policy: refund_policy_query, } if intent in route_map: return {next_skill: route_map[intent]} return {next_skill: fallback_clarification}这种模式的好处是我们只需要保证每个子技能足够专业而不需要训练一个什么都会的巨型技能。缺点是需要处理模型误判的问题。我处理误判的方法是在分流技能里加了置信度阈值如果模型给出的意图置信度低于0.7就返回一个澄清问题让用户确认而不是强行进入某个技能。用户多回答一句的成本远低于错误执行一条业务流的成本。3.3 子技能复用小心万能技能陷阱技能数量做到一定程度大家都会想着把公共能力抽出来复用比如获取用户信息查历史订单发短信通知。这是好事但要注意别做万能技能——一个技能接了八九种参数、服务了十几个上层技能最后内部逻辑全是if-else分支谁都怕改它。我个人的经验是复用的粒度应该控制在同类输入、同类输出的范围。比如获取用户信息这个技能虽然被很多上层技能依赖但它的输入永远是user_id输出永远是用户基本信息结构这就很健康。反之如果一个技能既要查订单又要算优惠价还要下发通知那就该拆了。拆的依据也很简单看上游调用方是不是经常只需要其中一部分能力如果是这个技能就该拆成更小的几个。复用还有一个容易被忽略的点权限边界。并不是所有上层技能都有资格调用底层技能。举例来说普通用户资料查询不需要看到员工内部备注但售后处理技能需要。我在技能框架里给每个技能加了一个access_level字段调用链路的有效权限取当前链路的最小值低于门槛的直接拒绝。这个设计在初期看起来多余但技能一旦铺到几十个、上百个权限混乱会带来很大的合规风险。4. 让技能真正可被调用描述、参数与提示词的写法4.1 技能描述不是写给用户看是写给模型看很多开发者写技能描述时下意识地站在人类视角本技能用于查询订单退换货相关信息。这种描述对模型来说信息量严重不足。模型需要知道的是什么时候用、什么时候不用、用户怎么说的时候要联想到它、需要配合什么输入。我总结了一个相对好用的描述公式触发场景 明确行为 排除条件 所需信息。例如当用户明确表达退款、退货、不想要了、退钱、申请售后等诉求 并且涉及具体订单时使用本技能查询退款资格。 如果用户只是询问退款政策而不涉及具体订单请改用refund_policy技能。 如果用户表达的是对商品质量不满但没有提到退款请先用情绪安抚话术回应 并询问是否需要登记检修不要直接触发退款查询。这段话看起来啰嗦但在模型语义理解里它能把误召回和漏召回同时压下来。像不想要了这种口语化表达是我在用户语料里挖出来的高频说法写进描述后召回率提升了一大截。技巧是尽量收集真实对话里的同义表达而不是靠想象。4.2 参数设计宁可多校验不可少约束技能参数的坑我讲讲几个真实遇到过的。第一个坑是歧义字段。一开始我把用户给的任何数字都填进order_id里结果用户说我的手机号是138xxxx订单是SO20250201xxxx麻烦查一下物流模型把手机号填进了order_id。后面我除了给order_id加正则校验还在描述里明确写订单号通常以SO开头并在前置校验里检查格式不符合的直接返回需要澄清的状态。第二个坑是枚举值。对于退换原因这类字段如果没有限制枚举值模型可能传一个衣服质量不好进来而售后系统只认识字符串枚举QUALITY_ISSUENOT_AS_DESCRIBEDLOGISTICS_DAMAGEOTHER两边对不上就直接报错。我现在的做法是所有枚举字段都在Schema里列出可选值并在技能描述里特别标注从给定枚举中选择不要发明新值。第三个坑是默认值。我给查询历史订单技能的limit字段设了默认值5结果测试时发现模型在用户明确说查一下我去年所有的订单时还是传了limit5因为模型看到有默认值就不从用户话里提取了。后面我取消了这个默认值改为必填op参数由技能内部根据对话判断是否需要重置。参数这块我的原则很明确能枚举的不自由输入能正则校验的不只靠类型检查关键参数宁缺毋滥——信息不足可以追问信息错了则很难挽回。4.3 技能内部提示词既要指挥模型也要兜底技能内部那段用来指挥模型执行任务、解析结果的提示词是agent-skills最容易被低估的部分。它不像系统提示词那样需要包罗万象但要足够聚焦把这个技能执行时模型应该注意什么说清楚。以我的订单退款校验技能为例内部提示词大致包含四块内容一是步骤约束先查什么、再核什么、最后返回什么二是数据口径金额单位、时间格式、哪些状态属于有效订单三是异常兜底接口超时怎么办、订单不存在怎么办、政策未命中怎么办四是安全底线哪些退款必须走人工审批、哪些场景禁止模型直接承诺结果。其中安全底线那条是我在出了几次事之后硬加的。之前模型在退款金额计算时把已使用优惠券的部分也算进了可退金额用户听了很高兴结果实际退款少了又是一轮客诉。加入提示词约束后模型执行时会更加保守遇到拿不准的计算会在返回里带一个need_manual_review标志让人工介入。这块不能完全指望模型自觉也需要代码侧的双重校验兜底。5. 踩过的坑与调试方法5.1 最常翻车的三类问题把技能体系上线到生产环境之后我归纳了一下最容易翻车的其实是这三类第一类是调用链路的上下文丢失。模型在连续对话中先问了订单又问了物流接着又冒出退货想法时经常忘记订单信息在几步之前已经给过。它要么重复追问用户一次让体验变差要么把物流信息里面的运单号当成订单号传给退款技能。针对这个问题我在Agent框架层维护了一个技能上下文池把最近对话中已确认的实体信息订单号、运单号、用户ID统一管理技能参数抽取优先从上下文池里取模型自己拿不准时就不允许瞎填。第二类是技能返回结果过于宽松。如果一个技能返回的是符合条件的相关退款记录模型在组织回复时就可能掺入一些不存在的细节。我后来统一改为只返回结构化事实禁止返回解释性文字模型看不到技能编的废话自然就没法念给用户。当然这也要求技能的返回字段设计得足够细能让模型看懂。第三类是多个技能之间的输出字段命名冲突。比如订单查询技能返回status字段表示订单状态物流查询技能返回status表示包裹当前位置状态模型从两个技能拿到信息后容易直接拿物流的status去判断订单能不能退。解决方式是在技能返回的字段名前加上技能前缀例如order_status和logistics_status并把每个字段的含义在Schema中写清楚。这个改动很基础但对防止模型混淆特别有效。5.2 调试技能的一线方法影子调用与日志追踪技能开发完不经过充分调试就上线基本等同于把AI应用当抽奖。我在调试阶段最依赖的方法有两个影子调用和全链路日志追踪。影子调用做的是不把新技能直接交给线上Agent而是让线上流量复制一份到新技能的执行链路中比对两边的结果差异。比如新版退款校验技能在影子模式下跑了三天我发现它对已发货订单的处理和老版本有出入——新版本严格按照物流签收时间去计算退货截止日老版本用的是发货时间。虽然新逻辑更严谨但这属于预期内的行为变更需要和业务方对齐后再切换。日志追踪方面我是把从收到用户消息、意图分流、技能命中、参数抽取、技能执行、返回组装到模型生成回复整条链路上每个节点的输入输出全部记录在案并给一次会话分配一个trace_id。出了问题直接按trace_id把链路捞出来一眼就能定位是模型选错了技能、参数抽错了还是技能内部逻辑报错、回复组装阶段跑偏了。没有这套追踪调试技能会变成纯粹靠猜。5.3 评测一场技能的及格线技能做得好不好不能靠感觉要有一套反复可跑的评测集。我给自己的技能体系建了一个评测集大概有100条真实历史对话和对应的期望行为分成四类单技能正常调用、多技能连续调用、信息不足需追问、完全不该调用技能。评测指标我关注五个技能命中率、参数抽取准确率、流程完成率、人工介入率和不该调时调了的误触发率。每一项都有及格线命中率和参数抽取准确率我要求大于90%流程完成率大于85%误触发率低于5%。每次改技能跑一遍评测集分数不达标坚决不上线。这套评测机制看起来很笨但能把Agent开发从感觉还行变成一个可度量、可回归的过程长期价值非常大。当然评测集也不是一成不变的。每两周我会从线上日志里抽一些失败案例补充进去不断让评测集跟上真实分布的变化。这也是技能体系能持续进化而不是越用越差的关键。6. 从几十个技能到技能治理规模化过程的一些体会6.1 命名、版本与归属技能数量超过30个以后最先扛不住的其实是人类自己——开发者已经记不清某个技能是干嘛的了。我经历过为了查禁运品类判断逻辑在某调研目录里翻了半天最后发现它被放在一个叫misc_utils的目录底下。那之后我定了一套命名规范技能名采用业务域_动词_对象的格式比如payment_query_refund、logistics_track_lastmile、policy_check_return。目录按业务域划分禁止出现misc、utils这类垃圾桶式目录。版本管理也要跟上。技能的输入输出Schema一旦变化会影响所有上游调用方所以我给每个技能加了一个版本号重大变更通过接口注册中心同步通知不允许静默修改。归属方面每个技能指定一个明确的owner由owner负责它的评测、迭代和下线。没有owner的技能我会强制在一周内指定否则直接标记为弃用防止孤儿技能越积越多。6.2 技能之间的依赖关系当Composite Skill越来越多地依赖底层Atomic Skill时依赖管理就成了问题。一个底层技能被改了内部逻辑可能影响七八个上层技能的行为。我在技能注册中心维护了一张依赖关系表每次技能变更都要跑一遍所有依赖方回归测试。这个过程虽然麻烦但它逼着你谨慎改动底层技能也逼着你把稳定的东西不断往下沉不轻易改。依赖的方向性也需要控制。我尽量让依赖呈现上层依赖下层的树状结构避免出现循环依赖。循环依赖一旦出现轻则调试混乱重则推理死循环。技能框架层我在初始化时做一次依赖图检测循环依赖直接报错不允许注册。6.3 几个未来的扩展方向技能体系做到现在还能再往前走几步。一个方向是技能自动发现让Agent面对一个没有命中任何技能的任务时自动从技能库中检索相似技能并提示开发者补齐而不是直接给用户一个我不太会的答复。另一个方向是技能复用集市同一个公司内部不同业务线之间把一些通用技能共享出来避免每个团队都重新造轮子。还有一个方向是技能生命周期管理结合线上使用频率、成功率、人工介入率这些指标自动给技能打分定期标记出低质量技能提醒开发者去优化或下线。回到文章开头那个让我焦头烂额的客服Agent在完成技能体系改造后它的行为边界清楚了很多。用户问羽绒服洗了一次起球能退吗退款校验技能会核对洗涤状态、退货时限、品类规则不再只凭一句模糊话术乱下结论。我自己的体会是做agent-skills重点不在要不要用而在怎么把技能切到刚刚好的粒度让模型在该做选择的地方做选择在该走流程的地方走流程。把握好这个度Agent就能从一个偶尔聪明、经常失控的demo变成一个过程可控、结果可预期的工程产品。
