
这两年只要在做大模型应用落地的人几乎都会撞上同一个问题模型什么都能聊但真要让它干活儿总差那么一口气。这口气通常就卡在“能力边界”的模糊上。我自己在折腾某跨平台系统的时候也有同感最开始什么工具都往上塞结果任务一复杂模型东一榔头西一棒子整个执行链乱七八糟。后来反复重构把核心逻辑收敛成一个个边界清晰的操作单元也就是所谓的Agent技能agent-skills整个系统的稳定性和可维护性才真正上了一个台阶。这篇东西不聊那些花哨的概念就把我实际摸下来的一套方法拆开讲一套技能到底怎么拆、怎么写、怎么测以及怎么编排它才不会变成一坨新麻烦。适合正在用大模型做自动化任务、搞智能体应用但发现模型执行起来总不够可靠的开发者看。如果是还没入门的同学可以先从文里“技能带契约的API”这个角度理解起后面所有内容都建立在这个思路上。1. 内容整体设计与思路拆解1.1 先想明白技能到底解决什么问题大模型本身是一个推理引擎和语言引擎而不是一个稳定的执行引擎。拿它直接控制外部系统最大的问题不是不会做而是经常用一种“看起来合理”的方式做错比如生成一段非法参数的调用、在该搜索的时候去查了知识库、中途忘掉了之前定的约束。技能化改造本质上是给模型的行动空间画一个围栏模型只能往技能清单里选择动作每个动作的入参和返回有严格契约做对了有明确结果做错了有明确报错。这解决了三个实际问题。第一是可靠性模型不再自由发挥生成任意操作而是只能从预设技能中选择错误路径被大幅收窄。第二是可观测性每一次调用都能被记录成事件方便回溯这对生产环境来说几乎是必须的。第三是可复用性一个写好的技能可以挂到多个Agent上不需要针对每个任务重新调教。我常常用一个生活类比帮助理解大模型就像一个能力很强但不太看说明书的新员工技能相当于你给这个新员工做了一套标准作业卡上面写清楚了每种情况该用什么工具、工具的参数格式是什么、出了什么报错该往哪走。新员工不需要知道整个公司怎么运作只需要按卡片执行。1.2 技能拆解的常见误区与正确思路在建模拟项目X的过程中我总结出了三个非常容易踩的坑。1.2.1 误区一技能粒度越细越好很多人一开始恨不得把函数全切成原子操作全塞进技能列表里。看起来选择多了实际上模型在选择时会不堪重负。技能数量一旦超过几十个模型对每个技能的语义区分度就会下降经常选错技能或者需要多轮试探才能选对。而且技能越细交叉调用的路径就越长出错的概率指数上升。合理做法是控制技能数量在7到12个之间每个技能对应一个“有明确完成目标”的子任务而不是对应一次单一操作。例如“获取用户详情”和“获取用户订单列表”可以合并成一个“同步用户全景数据”内部再由模型决定或直接由代码逻辑串联。1.2.2 误区二技能描述写得太随意这是最容易被忽视的地方。模型本身不读代码它只读描述和参数说明。技能描述如果写得模糊比如“处理用户数据”模型就无法判断什么时候该用它、什么时候不该用它。正确写法应当包含触发条件、执行内容、期望输出、失败场景。用一两句话把触发场景讲清楚比写一大堆参数定义还重要。1.2.3 误区三不考虑技能的运行环境在纯语言环境里写好的技能一旦放到带状态、带并发、带外部依赖的真实系统里就会暴露出大量问题。比如同一个技能被两个Agent调用资源锁没处理技能依赖的第三方接口偶尔超时没有重试策略技能内部需要读取上次调用的上下文结果上下文被另一条任务链覆盖了。技能设计从第一天起就要把运行期的这些因素考虑进去否则后面返工成本很高。2. 核心细节解析与实操要点2.1 技能的五个必要组成要素我习惯把一个技能拆成五个要素来看这个结构后来形成了团队内部写技能的模板要素作用示例名称给模型一个语义锚点search_product描述说明触发条件与用途用户询问某个商品是否存在、价格、库存时调用参数定义明确入参格式和必要性{keyword: string, max_price: number?}执行逻辑技能内部的具体实现调用某个商品检索系统的API并做数据拼装输出协议定义返回数据的结构与错误标识{status, data?, error?}这五个要素缺一不可。名称和描述解决了“什么时候用”参数和输出协议解决了“怎么用”执行逻辑是真正干活的实体。很多人觉得描述是写给模型看的不重要但实际上模型的选技能准确率和你描述的质量强相关。在测试中我把某个技能描述从一句话扩写到三句话并补充了两条典型触发场景后它在相关任务上的召回率提升了近30%。2.2 参数契约设计的两个原则参数层面的设计核心原则就两条一是少而精二是默认值兜底。少而精是指能不加的参数就不加。参数越多模型生成合法参数组合的难度越大。一个技能如果超过四个必填参数就得考虑是不是拆成两个技能或者把不重要的参数下沉到内部逻辑里通过上下文推理获得。默认值兜底是指对于可选项尽量在定义里带好默认值模型显式传出一个错误值时代码逻辑也要有拦截和纠正不要让错误参数一路穿透到外部接口。还有一个细节容易被忽略参数类型一定要严格约束。能枚举的就用枚举能限定格式的就限定格式。大模型生成的参数并不总是符合你预期的字符串格式比如在生成日期时给出“最近三天”而不是具体的日期范围这时候如果你用严格的schema校验并配合二次解析就能平滑处理。2.3 技能描述的标准写法与反面案例技能描述写得好不好直接决定模型会不会用。下面是两种写法的对比。第一种是敷衍版“获取天气信息。”模型看了等于没看完全不知道什么时候该调。第二种是规范版“当用户询问某个地点当前或未来几天的天气包括温度、降水概率、风力等调用此技能。参数city为城市名days为查询天数。如果城市名缺失直接向用户确认后再查。”规范版把触发条件和参数来源都讲清楚了模型在意图识别阶段的决策会非常准确。我自己的习惯是描述里至少包含三个要素触发场景、参数来源建议、异常处理指引。比如“参数来源建议”这层如果你不写模型可能会从用户对话中硬猜一个不存在的城市名而如果你写了“城市名必须来自用户原话不得自行推断”这类幻觉就会明显减少。2.4 异常处理机制技能不可能永远成功。外部接口会超时、依赖服务会挂、数据格式会变这些都不是写代码时能完全避免的关键是技能内部怎么回应失败。我对技能的一个硬性要求是任何一条执行路径都必须有确定性的返回成功返回数据失败返回错误码和可读的错误信息不允许悬空或抛异常到上层让模型自由发挥。3. 实操过程与核心环节实现3.1 用函数封装技能的完整流程下面用一个具体例子来走一遍实操流程这个例子的背景是某跨平台系统里的订单管理模块。第一步是识别任务边界。用户的典型需求是“帮我查一下上个月的订单总额”这背后涉及两个子任务一是筛选订单数据二是对金额做汇总。我在设计时没有拆成两个技能而是合并成一个summarize_orders技能因为“汇总”更贴近用户的原始目标筛选只是内部实现手段。第二步是设计技能接口。名称确定为summarize_orders描述写为“当用户想查看某时间段内订单数量、总金额、平均订单金额等汇总信息时使用。参数start_date、end_date格式为YYYY-MM-DD如果用户只说‘上个月’则由Agent根据当前日期自动推算后填充。”参数定义为开始日期和结束日期两个可选项都带默认值。第三步是编写执行逻辑。这里用Python写了核心骨架本质上是一个对订单系统API的封装def summarize_orders(start_date: str, end_date: str) - dict: orders fetch_orders(start_date, end_date) if not orders: return {status: empty, data: None, message: 该时间段内没有订单} total_amount sum(o[amount] for o in orders) return { status: ok, data: { order_count: len(orders), total_amount: round(total_amount, 2), avg_amount: round(total_amount / len(orders), 2), }, }第四步是注入Agent框架。以常见的function calling方式为例需要生成一份函数声明里面包含技能名、参数JSON Schema和描述。模型在对话中根据用户输入决定是否调用、传什么参数Agent运行时代码拿到调用请求后解析参数并执行summarize_orders函数。第五步是联调测试。重点关注模型是否在正确时机调用技能、生成的参数是否合法、技能返回后模型能否自然地把数字组装进回答里。我实际碰到过一个情况模型在用户只提到“上个月”的时候直接把参数传成了空字符串后来在本地加了一个参数清洗函数检测到空值就用当前日期推导上月区间这个坑才彻底填平。3.2 技能编排几个技能如何协作单技能只是零件真正的智能体要面对的是多步任务用户说“推荐几款适合跑步的耳机预算500以内”这要求先搜索商品再按价格和品类做过滤最后可能还要结合用户历史偏好排序。技能编排要解决的就是链条问题。我用的编排策略是“计划-执行-校验”三段式模型先基于用户目标和技能清单生成执行计划每一步对应一个技能调用然后按计划依次执行每步执行完把结果反馈给模型模型决定下一步是继续还是调整计划。这个策略的优点在于每一步都是可观测的出错了能精确定位到底是模型的计划错了、参数错了还是技能内部执行错了。我在实际系统中给每一步都加了日志输出内容包括技能名、入参、出参和耗时这样排查问题的时候效率高很多。3.3 状态记忆的处理技能调用不能每次都从零开始。用户在一个会话里先说要看昨天的数据接着问和前天的对比这里就需要技能链路能够把上次查询的时间范围作为上下文传递给下一次规划。我通常会在Agent的状态对象里维护一个轻量的字段串记录本会话已经查询过的关键维度并在描述里注明“如用户未明确说明时间范围但对话中已出现过某个时间表示应优先沿用最近一次有效的时间范围”。这个细节看起来很小但对多轮对话体验的提升是肉眼可见的。4. 常见问题与排查技巧实录4.1 模型就是不调用技能怎么办这个问题在初期特别让人头疼。模型答非所问明明给它配了搜索技能用户问天气它却凭空编一个答案。排查思路可以先看是不是意图识别阶段出了问题技能名称和描述写得含糊、触发条件不清晰或者技能列表里有太多相似条目干扰了判断。其次看框架层如果没有把调用结果正确回填到上下文里模型会误以为技能工具不存在。4.2 参数生成总是不合法参数错误是最常见的执行失败原因。比如日期格式生成了“昨天”、枚举值生成了不存在的类别、数字字段给出了文本。针对这类问题单纯靠模型自觉是不可靠的必须加一层校验与修正。我在执行逻辑入口统一写了一个参数清洗函数依次做类型转换、枚举校验、空值兜底和格式校准这层兜底让最终传给外部接口的参数错误率降到了极低水平。4.3 多技能互相干扰怎么防技能列表里的条目越多模型选择时越容易混淆。尤其是某些技能职责边界天然相近的时候比如“查订单”和“查售后单”。我在技能描述里会刻意写明边界条件例如“查售后单仅当用户提到退换货、退款、维修等关键词时调用订单查询仅针对购买记录与物流信息”。必要时还会给相近技能加一个“反面提示”明确告诉模型什么情况下不要用自己这比单纯描述自己干什么更有效。4.4 常见问题速查表下面把我在维护某跨平台系统过程中实际遇到的高频问题整理成了一张速查表方便读者对照排查问题现象可能原因排查方向常规解法模型不调用技能直接编答案描述不清晰或技能被忽略查看模型日志里是否出现技能名重写描述明确触发场景调用时参数为空模型未能在对话中提取参数观察用户原话与生成参数增加参数来源建议说明加入默认值返回结果凌乱无法总结输出协议过弱模型自由发挥检查技能返回值字段是否固定固定status与data字段结构多技能相似导致选错技能边界模糊对比两个技能的描述重叠度增加反面提示细化边界条件技能执行成功但答案错误编排逻辑或取数口径出错查看编排步骤与每步返回值检查技能内部查询条件是否遗漏4.5 几条必须强调的避坑经验最后写几条我实际踩出来的经验这些很难从文档里直接学会。第一技能列表的更新一定要带回归测试不要改完一个技能的描述就上线因为描述变化会影响模型在其他任务上的技能选择行为看似无关的问题可能是被一次隐蔽的描述变更触发的。第二别迷信模型的能力宁可增加参数校验和状态校正逻辑也别赌模型每一步都能生成正确结果。第三技能的日志和指标的收集要在设计初期就做好后面再补会非常痛苦技能调用频率、成功率、参数非法率这三类指标是最基本的运营数据。我个人的做法是每维护一版技能定义都会把模型对典型任务的调用轨迹打印出来过一遍不用看太多挑十条覆盖正常路径、边界路径和失败恢复路径的就行。这样能很快感觉到技能设计是不是变笨了。这个习惯帮我避开了不少隐性退化问题尤其是在技能数量增加之后效果更加明显。