我先说句实在话:这两年聊AI Agent的人很多,但真正能把手上的模型变成一个"指哪打哪"的角色,核心瓶颈往往不在模型本身,而在"技能"这一层。标题里这个"agent-skills"看起来只是两个英文词拼在一起,实际上它指向的是智能体技能工程化这件事——怎么让Agent稳定地调用工具、完成任务、编排流程。这篇文章我想从自己的实操经验出发,拆清楚技能体系应该怎么设计、怎么写、怎么调,以及最容易踩的那些坑。适合正在做Agent应用、又觉得"提示词已经救不了我"的开发者参考。
1. 技能体系设计:先搞清楚Agent到底需要什么样的"技能"
1.1 技能不是函数,而是模型与工具之间的翻译层
很多朋友一上来就把Agent的技能等同于调用外部API,觉得"我会写个Python函数,能联网搜索、能算个加减乘除,这就是技能了"。错。技能的核心不是执行逻辑,而是让模型理解"什么场景该用、输入输出长什么样、边界在哪里"。说白了,技能是模型和工具之间的翻译层。
模型本身不会知道"你要我查天气,我该去看哪个服务",它只知道"我该给某个函数传参了,但参数格式我不确定"。技能体系要做的事情,就是把一件任务的语义描述、触发条件、参数结构、执行方式,用模型能读懂的方式写清楚。我在早期的项目里吃过亏,当时把内部的订单查询接口直接丢给模型调用,接口文档写得很规范,但模型经常把参数传错——后来才发现,问题不在模型笨,而是我没有给这个接口配一份"模型友好"的技能描述。
所以,一个合格的技能描述至少包含四块:
- 技能名称:简短、无歧义,最好是动词开头的短语,比如"查询订单状态"
- 功能描述:一到三句话说明这个技能能做什么,不能做什么,适合什么场景
- 参数Schema:每个参数的名称、类型、取值范围、是否必填、默认值
- 返回结果说明:告诉模型会拿到什么样的数据,以及这些数据怎么理解
这四块信息缺一不可,尤其是"返回结果说明",很多技能定义里根本没写,导致模型拿到返回值后不知道怎么用。我见过一个很典型的例子:技能返回了JSON,里面有订单号、金额、状态,但模型不知道"status=1"到底代表什么,于是就开始瞎猜,甚至还编出个"status=2表示已发货"。后来在技能描述里加了状态码的枚举说明,准确率一下就上去了。
1.2 技能粒度怎么定:太粗容易失控,太细容易低效
技能拆到什么程度,是衡量Agent系统设计水平的分水岭。我见过两种极端:一种是把整个业务逻辑塞进一个技能里,比如"处理售后"这样一个技能,里面涵盖了退款、换货、人工介入、物流查询,模型根本搞不清楚该走哪个分支;另一种是把技能拆得极细,比如"计算字符串长度"这种也在技能列表里,导致模型每次决策都要从几十个技能里去选,推理速度明显变慢,选错的概率也在上升。
我的经验是,技能粒度应该根据任务闭环来定,而不是根据API粒度来定。什么叫任务闭环?就是一件用户从提出诉求到获得结果的事情。比如"查物流"是一个任务闭环,"根据物流状态判断是否需要自动理赔"是另一个闭环。前者应该是一个技能,后者如果已有业务规则,应该也是独立技能,而不是混在一起。
粒度定完之后,还有一个重要动作——给技能分级。核心技能(高频使用、跟主流程强相关)应该放在最前面,边缘技能(低频、辅助性)往后排。因为很多Agent系统在意图路由时会对技能描述做语义匹配,描述越靠前,被选中的概率越高。实测下来,把高频技能放到列表头部之后,路由准确率能提升不少,这个优化成本极低,性价比非常高。
1.3 技能描述怎么写,模型才真正"看得到"
写技能描述跟写API文档是完全不同的思路。API文档是给人看的,可以用大量专业术语和精确格式;技能描述是给模型看的,要追求语义清晰、边界明确、样例充分。模型在做技能选择时,本质上是拿用户的输入跟技能描述做语义匹配,所以描述写得越贴近真实用户话术,选得越准。
我在写技能描述时,会刻意用"这个技能可以处理...",不能处理..."的句式,把边界直接画出来。比如说一个天气查询技能,描述写成"根据城市名称查询当前天气和未来天气预报,支持国内主要城市,不支持查询历史天气数据"。后半句很重要,因为用户如果说"帮我看看上周三的天气",模型知道这个技能干不了,就会去走其他路径,而不是硬调。
参数Schema这块,我强烈建议给每个参数写一个"参数含义"和"示例值"。比如城市参数,示例值写"北京、上海、广州",模型就知道该传中文城市名,而不是拼音。再比如时间参数,示例值写"2025-06-01",模型就知道日期格式是"YYYY-MM-DD",不会给你传一堆乱七八糟的东西。这个细节别偷懒,我统计过,加上示例值之后,参数传错率大概能下降好几成。
2. 技能实现与运行机制:从定义到落地的关键细节
2.1 技能描述文件的结构:用JSON还是YAML,怎么设计才科学
现在主流的Agent框架里,技能描述基本都走声明式配置,常见格式是JSON或YAML。我个人偏好JSON Schema风格,因为工具链成熟,而且很多模型在微调和少样本学习时都见过JSON结构,理解成本低。一个完整的技能描述文件,我通常按下面这个模板来写:
{ "name": "query_order_status", "description": "根据订单号查询订单的当前状态,支持的状态包括:待支付、已支付、已发货、已签收。不支持查询历史订单。", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,一般为数字或字母组合", "examples": ["SO20250601001"] } }, "required": ["order_id"] }, "returns": { "type": "object", "properties": { "order_id": "string", "status": "string", "status_desc": "string" } } }注意我把"returns"单独拉出来定义了,这不是JSON Schema标准里的字段,但对模型理解输出结果帮助极大。很多框架不会额外定义返回结构,模型只能靠执行结果自己去猜,猜就容易出错。加上这块之后,Agent拿到返回值就能直接判断"任务是否完成""结果是否符合预期",尤其是在多轮对话场景下,模型需要基于返回值决定下一步动作,这个信息非常关键。
2.2 技能执行器的设计:把描述文件变成真正能跑的代码
描述文件只是"剧本",真正执行还要靠技能执行器。我推荐把每个技能实现成一个独立的Python类或者函数,统一继承一个抽象基类,这样做的好处是统一管理生命周期、统一处理异常和日志。设计执行器时,有一个常被忽略的点——超时控制。任何一个外部API调用都可能卡住,如果不给技能执行设置超时,整个Agent的响应就会被拖死。
我通常在技能执行器里做三层防护:第一层是模型调用层的超时,控制在几秒内;第二层是工具执行层的超时,外部API或数据库查询单独计时;第三层是整体任务级的超时,整个Agent一次回复的时间上限。这三层超时配合,至少能保证Agent不会因为某个技能卡壳就彻底失联。实操中我遇到过MicroService响应极慢导致Agent超时的线上故障,当时排查了很久,最后发现就是缺了工具执行层的超时设置。
另外,技能执行器里一定要留钩子(hook)用来做日志埋点。每次技能被选中、被调用、执行成功、执行失败,都要记录下来。这些日志不仅是排查问题的依据,更是后续评测和优化技能路由的直接素材。
2.3 技能注册与发现:运行时如何知道"有哪些技能可用"
技能定义好了,执行器也写好了,接下来要解决的是"模型怎么知道有哪些技能"。常规做法是在系统提示词里把技能列表塞给模型,但这有个问题——技能过多时,提示词会越来越长,模型在长上下文中做工具选择的准确率会下降。我见过有人塞了30多个技能描述进提示词,结果模型经常选错,而且每次请求的token消耗也很大。
解决思路有两个方向。一是分层路由,先做粗粒度意图分类,比如"订单相关""支付相关""售后相关",再在子类里选具体技能,这样可以显著减少模型单次决策的候选集。二是动态技能发现,根据用户输入先做一个检索召回,从技能库中选出Top5相关技能,再让模型在候选技能中做选择。第二种方式更灵活,适合技能数量几十上百的场景。
我自己的做法是"静态清单+动态召回"混合:把高频核心技能固定在提示词里,其余技能放技能库,每次请求先做向量检索召回,合并后再交给模型决策。这样做之后,技能选择准确率提升了差不多十个点,token成本也降了不少。向量检索不需要多复杂的模型,普通的Embedding模型就够用,关键是技能描述文本要写得规范,检索质量才有保障。
2.4 技能参数注入与校验:从模型输出到函数入参的最后一公里
模型输出参数经常是"差不多对但不完全对",这最后一公里处理不好,前面做的所有工作都白费。常见的坑包括:模型给出参数值类型不对(字符串传成了数字)、参数名跟Schema对不上(多了或少了前缀)、字段缺失、枚举值写错。我的做法是在技能执行器入口加一个参数校验层,用JSON Schema的校验库来做格式校验,同时对关键字段做二次转换。
比如日期参数,模型可能给你"6月1号"或"2025/6/1"这种格式,执行器里就要做归一化。再比如手机号参数,模型可能给你"1 3 8 0 0 0 0 0 0 0 0"这种带空格的东西,不清理就往API里传,基本就是错误。这些转换逻辑千万别放在技能业务代码里,要集中放在参数校验层,统一处理,否则每个技能都要重复实现一遍,维护成本直线上升。
还有一个我自己踩过的坑:模型偶尔会在参数里带上跟任务无关的信息,比如用户说"帮我查一下订单,订单号是SO123,顺便告诉我今天天气怎么样",模型可能把"今天天气怎么样"也塞进查询参数的某个字段里面。所以参数校验层除了格式校验,还要做超出范围的数据过滤,该拒绝的字段就拒绝,不要惯着。
3. 技能编排与组合调用:单技能能跑通之后,真正的挑战才开始
3.1 技能编排的本质:让模型学会"先做什么,再做什么"
单技能调通只是第一步。真实业务里,大部分任务都需要多个技能组合完成。比如用户说"我的订单超时未发货,帮我申请退款",这背后至少涉及:查询订单状态、判断是否满足退款条件、执行退款申请、通知用户,可能还要调用客服系统留工单。这里面的执行顺序是有依赖的——不查订单就没法判断,不判断就不能退款。
技能编排的核心是让模型理解"依赖关系"和"执行顺序"。我在设计技能路由时,会给每个技能标注"前置技能"和"后置技能"的关系,模型在生成执行计划时,会优先检查前置条件是否满足。比如退款技能的前置技能是"查询订单状态",模型在计划阶段发现还没查订单,就会先插入查询动作,而不是直接跳到退款执行——这能避免一大批逻辑错误。
编排的控制权不完全在模型手上,还要有硬规则兜底。我的建议是让Agent框架中的Planner负责生成执行计划,用一个轻量级的规则引擎来做计划校验,检查是否有循环依赖、是否缺少必要前置、是否调用了不存在的技能。规则校验通过后才真正下发执行。纯靠模型自动编排,在复杂任务上翻车率很高,加一层规则校验能让稳定性有质的提升。
3.2 串行与并行:什么场景并行,什么场景必须串行
技能编排中有一个非常影响体验和成本的决策——并行还是串行。有些技能之间没有依赖关系,完全可以并行执行,省将近一半时间;有些技能有严格依赖,必须串行等待结果。判断规则很简单:后一个技能的输入是否依赖前一个技能的输出。比如"查询订单状态"和"查询当前用户信息"互不依赖,可以并行;但"查询订单状态"和"根据状态判断是否退款"强依赖,必须串行。
我在实际系统中的做法是,把无依赖的技能放入一个并行组,组内技能同步发起调用,等所有结果返回后再进入下一阶段。这样做不仅省时间,还能减少模型多轮推理的次数,提升整体稳定性。但是并行也不是免费的——多个外部API同时调用,对系统资源、下游服务的并发能力都有要求,如果下游服务扛不住并发,强行并行反而会把服务打崩。所以并行之前,先确认下游服务的限流阈值。
3.3 组合技能的复用:把固定套路沉淀成独立技能
在编排过程中,我慢慢发现一个规律:某些技能组合方式是固定套路。比如"查订单->判断状态->给用户反馈"这套流程,在很多对话场景里反复出现。与其每次让模型现场编排,不如直接沉淀成一个组合技能(也叫复合技能)。组合技能的内部逻辑固定下来,对外暴露一个简洁接口,模型只需要调用一次,内部自动跑完整条链路。
这样做有三个好处:减少模型决策负担,提高执行一致性,方便统一调优。坏处是灵活性降低,如果业务规则经常变,组合技能的维护成本就上去了。我的建议是,把稳定成熟的流程做成组合技能,把易变的分支逻辑保留为单技能,让模型自己拼装。这个分寸需要在实际业务中反复拿捏,没有统一标准。
组合技能的实现也不复杂,本质上就是执行器内部调用其他技能。关键是在组合技能的描述里写清楚"这个技能已经包含了哪些步骤""调用前需要满足什么条件",避免模型在组合技能外部又重复调用内部技能,造成重复执行。
4. 技能评测与调优:拿数据说话,别靠感觉
4.1 建立评测集:这是技能优化最容易被跳过的一步
技能效果好不好,不能靠"感觉还行"。最容易被偷懒跳过的一步,就是建立评测集。我见过太多团队把Agent上线后遇到问题,第一反应是改提示词,改了几轮发现还是不行,才想到要做评测。评测集不需要一开始就很大,但覆盖面要足够:每个技能至少准备几十条典型用户输入,包含正常情况、边界情况、异常情况,以及容易被混淆的相似输入。
评测集做好之后,要跑一个自动化评测流程。每次修改技能的描述、参数Schema、编排逻辑,都要重新跑一遍评测集,对比前后效果。我踩过的坑是改了一个技能描述,结果A任务的准确率上去了,B任务开始频繁选错技能,如果不跑全量评测根本发现不了。有了评测集,回归问题才能被及时拦下来。这个过程很像传统软件工程里的"自动化测试",只是断言的粒度从代码逻辑变成了"模型选对技能并正确执行"。
评测维度上,我建议至少看四类指标:技能选择准确率、参数传递正确率、任务完成率、单任务耗时和token消耗。前三个是效果指标,第四个是成本指标。很多团队只盯任务完成率,忽视了token消耗,结果模型为了完成任务疯狂调用多个技能,链路上每一步都要跟模型交互,成本涨三五倍都不奇怪。
4.2 常见问题排查:描述冲突、上下文污染、串联失败
评测跑起来之后,最常暴露的问题是三类,我一个个说。
第一类是描述冲突。两个技能的功能描述太像,模型分不清该用哪个。排查方法是把用户的真实输入拿出来,分别跟两个技能的描述做相似度计算,如果分数很接近,说明描述写得不够有区分度。解决办法是把你期望的边界写得更清楚,或者加"更适用于..."这样的偏向性描述。
第二类是上下文污染。模型在长对话过程中,把之前轮次的内容错误地带入技能调用。典型场景是用户先问了A订单,再问B订单,模型可能把A订单号当成B订单号传给了查询技能。这个问题的根源在于Agent框架把历史对话全塞进了上下文,模型区分不了"当前意图"和"历史信息"。我的解决办法是在模型做技能调用的那一轮,只把当前用户输入和必要的会话摘要传给模型,不要全量灌入历史记录。
第三类是串联失败。组合技能内部某个环节出错,导致整个流程中断,但模型又不知道错在哪。解决思路是给组合技能的执行器加上"阶段状态返回"机制,内部哪个环节失败,就把错误信息结构化成返回值的一部分,这样外层模型就能基于错误信息做下一步决策,比如重新调用或向用户解释,而不是盲目重试。
4.3 迭代方法论:每次改动都要回答三个问题
技能体系的调优是一个持续的过程。我在实践中逐渐形成了一套固定的迭代节奏:每次改动前先想清楚三个问题——这次改动想解决什么问题、预期带来什么变化、如何评估是否成功。想不清楚这三个问题,就不要动手。
举个例子,如果你发现"查询天气"技能经常被选错成"查询空气质量"技能,你要做的不是急着改两个技能的描述,而是先搭建一个评估集,专门准备十来条涉及天气和空气质量的用户输入,跑一遍基线数据,确认选错的比例。改完描述后再跑一遍同样的评估集,对比准确率变化。整个过程可能只需要一两个小时,但比"凭感觉改提示词"靠谱得多,因为你有了可量化的依据。
另外,技能列表不是越长越好。技能数量膨胀到一定规模后,模型选择成本上升、准确率反而下降。我一般每个季度做一次技能盘点,把长期未被调用的技能下线,把重叠度高的技能合并。技能库保持精简,模型的路由压力就小,整体稳定性也会更好。
5. 写在最后的一点经验
技能体系做到后面,你会发现它跟写业务代码越来越像——不是堆Prompt技巧,而是设计接口、管理依赖、做测试回归、控制复杂度和成本。如果非要说一条最重要的实操体会,那就是"技能描述是给模型看的文档,不是给人看的文档",每个自然语言的措辞都值得反复打磨,因为它直接影响模型的选择和执行。
我最后一次踩到的大坑是上线前没做全量回归,改了一个共享技能的返回结构,结果下游三个组合技能全部间接受到牵连,线上出现了一堆"看似正常但实际上跑错了逻辑"的会话。那次之后我给自己的团队定了一个硬规矩:任何技能改动,必须跑通全量评测集才能合入,哪怕是一次措辞微调。这条规矩看起来麻烦,但长期来看省下来的返工时间远超投入成本。
希望这篇基于"agent-skills"整理的实操笔记,能帮你把Agent从"能聊"推进到"能干"。技能体系没有一劳永逸的方案,数据驱动、小步迭代,才是走得很稳的姿势。