☰
Agent技能体系设计与落地:从稳定性到可复用的工程化方案
2026/10/8 11:17:48 网站建设 项目流程

最近一直在折腾 agent-skills 这个方向。坦白说,把大模型从"聊天窗口"挪到"干活场景"里,最难的从来不是选哪个模型,而是怎么让 Agent 每次干活都稳得住、能复用、可排查。我的做法是把 Agent 的能力显示地拆成一套技能体系,把它当工程资产来管,而不是靠每次临场发挥。这篇文章不聊概念,直接讲我落地 agent-skills 时怎么设计技能 Schema、怎么调度、怎么处理失败、怎么做评测,以及踩过的那些坑。

如果你正在做 Agent 应用,或者被"Demo 能跑、上线就乱"折磨过,这篇文章应该能给你一套可以直接抄作业的思路。

1. 为什么 Agent 需要一套技能体系,而不是单纯堆 Prompt

1.1 从"临场发挥"到"标准化作业"

很多人上手 Agent 的第一反应是写 Prompt:把角色背景、任务目标、可用工具一股脑塞进系统提示词里。Demo 阶段确实跑得通,但一旦任务复杂起来,问题就来了——模型每次都在重新"思考"怎么做,同样的操作有时候做有时候不做,参数经常填错,报错了也不知道该不该重试。

这就像让一个新人每次干活都现场摸索,而不是给他一本 SOP 手册。Agent 技能体系干的就是这件事:把高频、稳定的操作路径固化成一段可复用的"技能",让模型在需要时直接调用,而不是每次现场推导。

我理解 agent-skills 的本质,就是给 LLM 配一套"能力抽屉"——每个抽屉标签清晰、内容完整、开合有标准。模型要做什么事情,先看抽屉标签,抽出来就能用。这比让模型自己去翻工具箱高效得多,也稳定得多。

1.2 技能体系要解决的三类核心问题

做了一段时间之后,我总结出技能体系真正解决的是三类问题,这三类问题是纯 Prompt 工程很难系统性解决的:

稳定性问题。模型直接操作时,同样的输入,输出可能差很多。技能化之后,操作路径是预定义的,模型只在关键决策点做选择,随机性被大幅压缩。实测下来,同一任务的成功率能从 70% 提到 95% 左右,代价就是前期要把技能定义做扎实。

可复用问题。比如"查询订单状态"这个能力,可能在售后、客服、后台管理好几个场景都会用到。做成一个技能之后,所有 Agent 都能调用,不用每个场景各写一套 Prompt。改一处,所有地方同步生效。

可观测问题。纯 Prompt 驱动下,你很难判断"模型为什么这么操作"——它只是生成了一段文本。技能化之后,每一次调用都有明确的技能名称、入参、出参、执行结果,全部结构化落日志,排查问题时不用靠猜。

1.3 技术选型:纯 Prompt、外部框架、自研技能层的取舍

在动手写技能系统之前,我对比过三条路线:

第一条是纯 Prompt 工程,把技能说明写在系统提示词里。优点是启动快,缺点是没有结构化约束,模型理解偏差大,技能一多提示词就爆炸。

第二条是直接用外部编排框架。当时市面上已经有不少 Agent 框架自带工具调用和流程编排,确实省事。但我需要更灵活的权限控制、更细粒度的技能间依赖管理,外部框架往往封装得太重,定制起来费劲。

第三条就是自研一套轻量技能层,这也是我最终选的方案。核心就三个组件:一个技能描述文件(YAML)、一个注册中心、一个调度器。加起来代码量不大,但完全可控。这个思路和很多公司做内部工具平台的逻辑一样:先把标准定死,再谈扩展。

2. 技能定义:把一份"会做的活"变成可复用的工程资产

2.1 技能 Schema 设计:每个字段都有它的使命

技能定义是整个体系的基石。我早期踩过一个坑:字段设计得太少,结果技能多了之后互相描述不清,模型经常调错。后来参考工具调用的标准做法,把 Schema 定成了这样:

name: query_order_status description: 根据订单号查询订单的当前状态、物流信息和预计送达时间。当用户询问"我的订单到哪了"、"发货没有"时使用。 version: 1.2.0 tags: - order - query parameters: type: object properties: order_id: type: string description: 用户提供的订单号,通常是纯数字,长度18位 required: true customer_email: type: string description: 下单时预留的邮箱,用于二次校验身份 required: false rules: - 如果用户只提供了短单号,自动补全为完整订单号后再查询 - 订单号格式不正确时,不要直接查询,先向用户确认 run: type: sequence steps: - call: validate_order_id target: skill.validator - call: fetch_order_detail target: api.order_service - call: format_result target: skill.formatter - call: notify_user target: channel.chat fallback: - condition: order_not_found action: ask_user_verify_order_id - condition: api_timeout action: retry_with_backoff

每个字段我都想强调一下作用:

name是技能的唯一 ID,全局不能重复。命名规范建议用"动词_对象"结构,比如query_order_status、cancel_subscription,这能让模型更容易理解技能边界。

description是整个定义里最重要的字段,没有之一。模型选择技能时主要就看这段描述。好的描述有两个特点:一是说清楚"什么时候用",二是给出典型场景的触发词。比如"当用户询问我的订单到哪了、发货没有时使用",这种描述能把误调用率压到很低。

parameters定义了技能需要的入参。每个参数要标明类型、是否必填、格式说明。注意rules这一块是很多定义里没有的,我加它是因为模型常常会拿一个格式不对的参数就去调用工具,导致不必要的失败。把校验规则写进定义里,调度器可以提前拦截。

run是技能内部的执行流程。用sequence表示顺序执行,也可以定义条件分支或并行任务。早期版本我全用代码写死,后来改成 YAML 编排之后,产品同事也能参与维护技能逻辑了,协作效率明显提高。

fallback是失败处理策略。每个失败场景配一个兜底动作,这个在线上跑起来太重要了。没有这一层,技能一报错整个对话就僵住,用户体验非常差。

2.2 技能描述怎么写,模型才不会"误调用"

这一节单独拿出来说,是因为它太容易被低估了。我见过很多人把技能描述写成"查询订单状态",然后模型在用户问"退款到哪了"的时候也去调这个技能,结果查出来的数据对不上。这就是描述没写清楚。

我的经验是,一个高质量的description要满足三个条件:

第一,说清楚触发场景。不要只写"查询订单状态",而要写"当用户询问订单当前进度、物流信息、预计送达时间时使用;当用户询问退款进度时不要使用本技能,使用 query_refund_status"。

第二,主动列举反例。描述里明确"不适用"的场景,能极大减少误调用。这和训练分类模型时给负样本是一个道理。

第三,控制长度。描述太长会把注意力稀释,一般两三行内说清楚触发条件和边界即可。我和团队定的规范是 50 到 100 字之间,超过就得精简。

2.3 技能分类与目录组织:原子技能、复合技能、领域技能

技能数量一多,怎么组织就成问题了。我目前的分类是三档:

  • 原子技能(atomic skill):不可再拆分的最小操作,比如"验证订单号格式"、"调用支付接口"、"格式化金额显示"。这类技能只做一件事,参数最少,最容易复用。
  • 复合技能(composite skill):把多个原子技能串起来的操作流程,比如"查询订单状态"就是"验证订单号 → 调用订单服务 → 格式化结果 → 通知用户"。复合技能可以包含条件分支。
  • 领域技能(domain skill):面向特定业务场景的完整能力包,通常聚合多个复合技能和原子技能,比如"售后服务"领域技能可能包含query_order_status、process_return、apply_refund三个复合技能。

分类的意义在于调度的灵活性和维护的效率。模型决策时先按领域筛一遍,再在领域内选具体技能,搜索空间从"全部技能"缩小到"几个相关技能",选错概率大幅下降。维护上,底层服务接口变了,只需要改对应的原子技能,上层复合技能不用动。

3. 技能注册与调度:从"技能库"到"正确的技能被调用"

3.1 注册中心:像管理函数库一样管理技能

技能定义是一份 YAML 文件,但它只有在注册之后才能被 Agent 使用。注册中心本质上是一个技能元数据中心,我实现了四个核心能力:

  • 技能的增删改查(CRUD),带版本管理。
  • 启动时全量加载到内存,避免每次请求都去读文件。
  • 提供list_skills()接口给调度器,返回所有技能的结构化描述。
  • 技能下线时做引用检查,确保没有其他技能依赖它。

版本管理这块我之前吃过亏。有一次改了format_result的格式逻辑,结果所有依赖它的复合技能输出格式全变了,业务侧直接报警。后来我在注册中心加了一个依赖关系图,每次发布新版本前自动跑一遍依赖检查,有破坏性的改动直接阻断发布。这里建议各位也留意一下:技能文件一定要进 Git,每次改动留记录,线上出问题才能快速回滚。

3.2 技能选择策略:让模型做选择题,而不是填空题

技能选择是整个系统里最核心的决策环节。我的方案分三层:

第一层是硬性筛选。根据对话上下文和业务预设,筛掉明显不相关的技能。比如当前会话属于"售后"领域,就不会把"新品推荐"技能纳入候选池。这一步减少了模型的选择面,准确率自然就上来了。

第二层是语义匹配。把用户请求和技能描述分别做向量化,计算相似度,保留 Top K 个候选技能。这块可以不引入重模型,用 lightweight 的 embedding 模型就够用。候选数量我一般控制在 5 个以内。

第三层是模型排序。把候选技能的描述、参数列表、当前对话上下文拼成一个精简的 Prompt,让 LLM 从中选一个最合适的技能并填写参数。这一步本质上是让模型做一道选择题,出错的概率比让它从零开始想怎么做要低很多。

举个例子。用户说"我上周买的那个蓝牙耳机,现在左耳不出声了,想退货"。硬性筛选阶段,候选技能只剩售后领域的几个;语义匹配阶段,process_return和query_order_status分最高;模型排序阶段,根据"左耳不出声想退货"这个意图,最终选中process_return并自动把订单时间约束设置为"上周购买"。整个过程是渐进的,每一步都在缩小范围。

3.3 技能编排:顺序、条件、并行怎么组织

技能选定之后,还要处理技能内部和外部的编排问题。我在run字段里定义了三种基本结构:

  • sequence:按顺序执行,前一步的输出作为后一步的输入。比如"查询订单 → 判断状态 → 发送通知"。
  • condition:根据前置步骤的结果走不同分支。比如订单状态是"已发货"就走物流查询分支,是"退款中"就走退款进度分支。
  • parallel:互不依赖的步骤同时执行。比如同时查询商品详情和库存信息,可以节省响应时间。

实际项目里,我倾向于把一个完整的业务操作封装成一个复合技能,模型只负责入口和出口的感知,内部的复杂流程由编排引擎控制。这样做的好处是模型不需要理解所有的中间步骤,犯错的概率低很多。需要注意的是,并行分支不要开太多,实测下来同时超过 3 个并行任务时,收益就开始递减,还容易把下游服务打爆。

4. 技能执行过程中的稳定性和错误处理

4.1 执行前校验:把大部分错误挡在门外

技能执行的第一步不是调用工具,而是参数校验。调度器在拿到模型填写的参数后,按照技能定义里的parameters规则做一次严格校验。

比如query_order_status要求order_id是 18 位数字。模型把参数填成了12345,这个时候如果直接调接口,大概率会得到一个"订单不存在"的报错。有了前置校验,可以当场拦截,并触发一个引导流程:"你提供的订单号只有 5 位,请确认一下是否完整?"用户体验比冷冰冰的报错好很多。

这个校验动作放在调度器里做,可以让步骤时序变成"校验 → 执行 → 后处理"三段式,任何一个环节出错都有清晰的响应路径。参数校验规则建议不要纯靠代码硬编码,而是写进技能定义里,让业务人员也能维护。

4.2 超时、重试与降级:线上出问题时的保命策略

线上环境里,外部接口总会出问题。我的经验是,技能系统一定要内置三层保护:

超时控制。每个外部调用必须有明确的超时时间。我一般把单个工具调用的超时控制在 3 到 5 秒,如果超过就认为失败,进入重试或降级流程。超时时间设太短容易误判,设太长会卡住整个对话,需要根据实际接口响应时间调参。

重试策略。对于瞬时故障,比如网络抖动、接口 5xx,采用指数退避重试。第一次失败等 1 秒,第二次 2 秒,第三次 4 秒,最多重试 3 次。如果还是失败,就不再重试了,直接走降级路径。

降级方案。降级是很多刚做 Agent 的人容易忽略的。一个技能必须设计"如果核心链路挂了,我该怎么办"的方案。比如查询订单状态的接口挂了,降级方案可以是先返回缓存中的订单快照,同时告诉用户"系统暂时无法获取最新进度,稍后再试"。这样用户不会完全得不到反馈,至少体验不会中断。

fallback字段在技能定义里专门承载这些策略,运行时会自动匹配异常条件触发对应的兜底动作。我自己测试下来,加了完整降级策略之后,用户可感知的失败率能下降一半以上。

4.3 可观测性:让每次技能调用都有据可查

技能系统上线之后,最怕的就是"模型执行了 A 技能,但不知道为什么执行了 A 技能"。要解决这个,必须做全链路结构化日志。

我在每次技能调用时记录这样一组信息:请求 ID、当前场景标识、用户输入的原始内容、技能选择时的候选列表和排序分、最终选中的技能名和置信度、每个步骤的入参出参、每一步耗时、以及异常时的错误码和重试次数。

这些日志全部打到同一个链路追踪体系里,配合请求 ID 可以一步到位地把一次对话从头追到尾。排查问题时就不用再"对着屏幕猜"了。另外,我会定期统计每个技能的平均耗时、成功率、误调用率,做一张"技能健康度报表",哪项指标出现异常,立刻定位到技能定义或依赖接口的问题。

5. 技能评估与迭代:怎么知道一个技能真的"好用"

5.1 构建技能评测集:用黄金样例说话

技能写得好不好,不能靠感觉,得靠评测。我的做法是为每个技能准备一组黄金测试样例,每一条样例包含三部分:

  • 输入:一段模拟用户请求。
  • 期望技能:正确应该调用的技能名。
  • 期望参数:调用技能时应填写的参数键值。

比如对于query_order_status,黄金样例可能是这样一条:用户说"帮我看看刚买的那本书物流到哪了",期望技能是query_order_status,期望参数包含order_id=待从上下文提取的商品订单号。

评测时,把全部样例跑一遍,统计技能选择准确率(选对技能的比例)和参数提取正确率(选对技能且参数填对的比例)。这两个指标是我衡量技能"可用性"的核心指标。每次修改技能定义之后,全量回归一遍,分数掉了就说明改坏了。

5.2 回归测试机制:改一个技能会不会弄坏另一个

技能之间不是孤立的。改了一个原子技能的入参格式,依赖它的复合技能可能全崩。所以我在 CI 里加了一道"技能回归"工序:任何技能定义变更都要触发全量评测集跑一遍。

第一次跑回归就救过我一次。当时我把format_result的金额格式从"12.00 元"改成了"¥12.00",原本只影响查询类技能,结果发现apply_refund技能文档里引用的格式化结果样例也变了,下游链路直接断言失败。幸好回归测试拦住了,不然上线又是一次事故。

这套机制的运行成本不算高,几百条样例跑一遍也就几分钟,但换来的安全感非常值得。建议做 Agent 项目的朋友一定要把这步纳入开发流程,不要手测。

5.3 成本与效果权衡:每增加一个技能都是有代价的

技能多了也不是好事。每注册一个技能,调度时就要多一次筛选和模型排序,延迟和 token 开销都会涨。而且技能越越多,描述越相似,模型选错的概率也越大。

我自己定的原则是:一个技能必须在一周内被调用超过 50 次,才值得长期保留。如果某个技能上线一个月调用量极低,就考虑合并或者下线。另外,技能描述如果和其他技能的重合度太高,优先改描述而不是新增技能。

成本数据也要可观测。我每个月统计一次每千次调用的 token 花费和平均延迟,设置预警线,超了就回去检查是不是技能描述太冗长,或者调度链路里有不必要的模型调用。

6. 技能系统落地的常见问题与排查手册

6.1 典型问题速查表

技能系统在落地过程中,有四个问题出现频率最高,我整理成一张速查表:

问题现象可能原因处理建议
模型频繁选错技能技能描述触发场景不清晰,或技能间语义重合度过高检查候选技能描述,补充"不适用"场景;调整调度时硬性筛选条件
入参经常填错或缺失技能定义中参数说明不完整给每个参数补充格式示例和默认值;在描述里写明参数提取的线索来源
技能执行到一半卡住外部接口超时且无重试策略检查技能fallback配置,补充超时和重试流程
技能改了导致别的流程异常技能间存在隐性依赖跑一遍技能回归测试,使用依赖关系图定位影响范围

6.2 排查步骤与调试技巧

遇到技能行为异常,我有一套固定排查路径:

第一步,看链路日志。用请求 ID 定位一次完整的技能调用记录,先搞清楚在哪一步出错——是技能选择错了,参数解析错了,还是外部接口返回了异常数据。这一步能省掉 80% 的瞎猜时间。

第二步,复现并缩小范围。找到出错的用户输入,直接在调试环境里跑一次,观察技能选择阶段的候选列表和排序分。如果候选列表里压根没有预期技能,说明筛选条件太狠了;如果列表里有但排序分不高,说明描述语义没匹配对。

第三步,检查技能定义本身。看描述的表达方式是不是和用户表达习惯差异太大。有一次我们把技能描述写得很书面,结果用户口语"我要退钱"匹配不上,后来在描述里加了一句"当用户表达退款、退钱、不想买了时使用",问题立刻解决。

第四步,看外部服务是否稳定。如果技能逻辑没问题但结果依然异常,检查依赖的下游接口。把错误码和响应时间拉出来看,是超时还是业务校验失败,再决定是改技能还是改接口。

6.3 几条实践经验

最后分享几个我没在别处见过太多讨论的个人经验。

第一,技能描述要"站在用户的话说"。描述时尽量用目标用户的实际表达,而不是文档语言。把"查询订单状态"改成"当用户说查订单、订单到哪了、发货了没、物流信息时使用",路由准确率立竿见影。

第二,给技能做"版本发布"而不是"原地修改"。早期我直接改技能文件,出问题没法回退。现在每次修改都是新版本,线上审核确认后再切换,出问题一键回滚。这个习惯在团队协作时尤其重要。

第三,技能选择阶段少用大模型。能用 embedding 匹配解决的,就别让大模型参与排序,成本和延迟都能降下来。大模型只做最终的选择和参数提取,这样整体链路是最经济的。

第四,给技能系统设计一个"拒答"通道。当调度器觉得所有技能都不匹配时,不要强行让模型随便选一个来答,而是让 Agent 明确表达"我暂时无法处理这件事,已经帮你转给人工"。这个看似"消极"的设计,实际上能明显提升整体的可靠性——因为强行执行一个不合适的技能,比直接说不会做,造成的后果严重得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询