1. 项目概述:先盘点一下我为什么盯上这个方向
这两年做AI Agent相关项目的朋友应该都有同感:模型能力卷到头之后,真正决定一个Agent好不好用的,已经不是"脑子"而是"手脚"了。我见过太多团队卡在同一个地方——模型选得够强,提示词也精雕细琢,但做出来的Agent一到真实业务场景里就露怯,要么工具调用链条经常断,要么技能复用性极差,换个场景就得推倒重来。agent-skills这个标题,说白了就是在解决这套"手脚"的问题。
这个概念最近在Agent圈子里热度很高,核心要义特别朴素:把一个Agent要执行的复杂任务,拆解成一个个独立、可组合、可复用的技能单元。听起来有点像函数库或者插件体系,但在AI Agent的语境下,它跟传统的函数封装完全是两码事。传统程序里的函数是"输入-处理-输出"的刚性逻辑,而Agent Skill必须兼顾模型的非确定性推理,得设计成让模型"看得懂、调得对、用得好"的样子。
这篇文章我打算从一个实际做过的Agent项目切入,聊清楚三件事:第一,Agent Skill到底是什么,跟Function Calling、Workflow有啥本质区别;第二,从零手写一个能落地的Skill,需要做哪些关键设计;第三,实际操作里最容易踩的坑和排查思路。目标是让看完这篇文章的人,回去就能把自己手里的Agent重构出一套像样的技能体系。
先说适合谁看。如果你正在用LangChain、Claude SDK或者OpenAI Assistants API做Agent开发,但总觉得工具调用不顺手,编排复杂场景时逻辑混乱,那这篇文章就是按这个痛点写的。如果你只是听说过Agent但还没上手,这篇文章里的设计思路也可以当作入门地图。我不会写太多晦涩的理论,重点放在能直接抄作业的实操细节上。
2. Skill的定位:它凭什么不是第二个Function Calling
2.1 从一次失败的项目重构说起
先讲个真实的翻车经历。之前我做一个自动化运营助手,用OpenAI的Function Calling做了一堆工具函数,比如"查库存""发优惠券""算折扣",每个函数对应一个接口。初版跑起来很顺,模型能正确调用,准确率看着也不错。但业务一扩展就崩了:运营同事提了个需求,说"帮我把滞销商品挑出来,自动生成清仓文案,再通过企业微信发给店主"。
这个需求要串三个函数:查库存、生成文案、发消息。但Function Calling本身不关心顺序和组合逻辑,模型得自己在一次对话里连续调用好几次,而且中间任何一步出错,整个链路就断了。更要命的是,每个函数都是扁平的,没有上下文概念——"滞销商品"需要先定义筛选条件,"发给店主"需要先确定收件人,这些业务规则全部散落在提示词里,越堆越乱,最后提示词到了八千多token,改一处就崩另一处。
后来我把这个需求拆成了三个Skill:库存筛选Skill负责找滞销品,文案生成Skill负责写清仓文案,消息推送Skill负责发企业微信。每个Skill都有自己完整的输入输出Schema、失败处理逻辑和独立的提示词上下文。调用关系变成了这样:库存筛选Skill跑完,输出一批商品ID列表,作为文案生成Skill的输入上下文;文案生成Skill产出文本,再作为消息推送Skill的输入。三个Skill各自独立测试、单独换版本,互不干扰。
这个案例就是Agent Skill和Function Calling最本质的区别:Function Calling是"给模型一把工具,让它自己想怎么用",Skill是"把工具和它的使用说明书、边界条件、失败处理打包成一个整体,告诉模型在什么场景下用、怎么用、用完怎么衔接"。前者是函数,后者是完整的工作能力。
2.2 Skill、Tool与Workflow的边界到底怎么划
术语混乱是目前Agent领域最大的认知障碍。很多刚接触的人一上来就问:Skill和Tool是不是一回事?Workflow跟Skill比哪个更高级?我把这三者的边界按自己的经验理一下。
Tool是最底层的能力原子。它就是一个函数、一个API接口,完成一个单一动作,比如"查天气""发短信""算总价"。Tool不关心业务状态,不持有上下文,每次调用都是独立的。
Skill是Tool的封装加上使用上下文。一个Skill可以只包含一个Tool,也可以包含多个Tool的组合逻辑,但它与Tool的本质区别在于,Skill自带"什么时候用、怎么用、怎么处理异常、输出什么结构"这套元信息。模型在选择Skill时看到的不是"一个函数",而是"一整套能力说明"。
Workflow是更上一层的东西,它编排多个Skill之间的流转关系。主干逻辑是确定的——先做A,再做B,A失败就做C,但每个节点内部可以用Skill的灵活性。理想情况下,Workflow负责"走哪条路",Skill负责"每一段路怎么走好"。
这个分层我现在在实际项目中基本是这么用的:项目小、交互简单,直接上Tool函数就够;要处理复杂业务且希望模型有自主决策空间,Skill是性价比最高的粒度;流程刚性、路径固定且对稳定性要求极高的场景,才考虑上Workflow。三者不是替代关系,是不同抽象层级。
3. 核心设计思路:写Skill之前先想明白这几件事
3.1 输入输出的Schema设计是成败关键
我见过太多Skill翻车,不是逻辑有问题,而是输入输出Schema设计得有歧义。模型是个语义理解器,不是严格的类型系统,它靠自然语言描述来理解参数含义。如果你的输入字段写的是"query: string",模型能猜出来大概要传什么,但猜得可能跟你的预期有偏差。
一个好的Skill输入输出Schema,每个字段都要做到三件事:字段名自解释、描述给足上下文、约束写清边界。拿我做的"网页正文提取Skill"举例,输入字段我一开始只写了一个url:"url: string"。实测下来模型在调用时会把各种各样的东西塞进来,有人传的是搜索关键词,有人传了一整段带HTML标签的链接。
后来我把Schema改成这样:url字段描述改为"需要提取正文的网页完整地址,必须以http://或https://开头,包含域名和路径;如果用户提供的是搜索语句或关键词,请先使用搜索工具找到具体页面后再调用本技能",同时增加了一个参数extract_type,枚举值限定为article、product、list三种类型,用于告诉技能按不同结构解析页面。整个调用的准确率从七成左右直接提升到九成五以上。
输出Schema同样重要。我建议所有Skill的输出都遵循一个统一信封结构:status表示成功失败、data是核心数据、message是给人看的说明、meta放调试信息。这样做最大的好处是,多个Skill串联时,下一个Skill能无脑解析上一个Skill的输出,不用为每个Skill单独写一套解析逻辑。
3.2 元信息配置:注册表里的每个字段都有用途
Skill的元信息配置,很多人理解为就是给模型看的描述文本,实际上它是整个技能体系运转的"路由表"。我习惯用一个YAML文件把元信息结构化管理,每个字段都有明确用途,缺一个都会在实际运行中出问题。
name是整个Skill的唯一标识,模型靠它精确命中要调用的技能,所以必须做到全局唯一,而且最好语义清晰,比如"webpage_extractor""inventory_checker",别用"tool1""util2"这种似是而非的名字。
description是最核心的字段,直接决定模型在什么场景下会选择这个Skill。这个字段有两层要求:一是说明能力边界——"这个技能做什么、不做什么";二是给出触发条件——"当用户想了解某个网页的具体内容时,必须调用此技能"。我见过有人把description写了一百多字,但全是能力描述,没有触发条件,模型当然不知道该什么时候用。
parameters和returns就是配合Schema使用的,author和version用于团队协同和版本管理,tags用于分类检索。metadata字段我喜欢放一些模型不感知的系统级配置,比如超时时长、幂等策略、最大重试次数。
3.3 失败处理绝不能只靠提示词兜底
这块是我最想强调的。很多人把Skill的容错完全寄托在模型能力上,觉得"我提示词里写了出错就重试,出了问题让模型自我修正",这在实际生产环境里是不现实的。模型的重试逻辑经常会导致同一个错误反复触发,白白消耗token,还会把错误状态越搞越乱。
我现在的做法是,每个Skill内部都要有一套确定性的失败处理机制,不依赖模型临场发挥。具体分三层:第一层,前置校验,在调用任何工具之前先检查参数是否合法、依赖状态是否就绪,比如查库存Skill先确认商品ID格式和数据库连接状态,不合格直接返回错误信封,不进入工具调用环节;第二层,异常捕获,工具调用过程中如果报错,把原始错误信息完整记录进meta字段,同时给模型一个简化的错误类别判断,让上层调度能快速决定是重试还是换路;第三层,幂等保护,凡是发消息、创建订单这类有副作用的操作,必须支持幂等键机制,防止模型因为超时而重复执行。
这三层机制写进Skill模板后,Agent的整体稳定性的提升是立竿见影的。之前那种"明明已经扣了库存,但因为响应超时,模型又发起了一次操作请求"的经典事故,基本杜绝了。
4. 实操过程:从零开发一个可复用的邮件摘要Skill
4.1 场景选定与边界定义
理论说再多,不如上手走一遍完整流程。我用最近做的一个"邮件智能摘要与待办提取Skill"作为完整案例,把从设计到上线的全过程拆开讲。选这个场景是因为它包含了Agent类项目的典型复杂度:需要解析非结构化文本、需要调用外部工具(邮件系统)、需要输出对人友好且可机读的结构化结果、还要处理异常情况(邮件格式乱、权限不足等)。
第一步是边界定义。这个Skill只负责"基于给定的邮件内容做摘要和待办提取",它不负责接收邮件,也不负责发送回复,更不负责根据待办去执行动作。边界划清楚之后,Skill的输入输出就很好设计了:输入是邮件原始内容,输出是摘要JSON加待办列表。
关于权限和隐私:在实际接入企业邮箱系统时,这个Skill会读取邮件文本,但不会存储原始邮件,也不会将邮件内容用于任何训练,同时在日志中做脱敏处理。这些设计在做真实业务时一定要提前考虑,不要在项目上线后补防护措施。
4.2 Skill的完整代码实现框架
一个基础版本的Skill包含四个文件:skill.yaml放元信息配置,validate.py做输入校验,run.py是核心逻辑,errors.py统一定义异常。代码结构如下:
# skill.yaml 核心配置示例 name: email_summarizer version: "1.2.0" description: > 将一封或多封邮件的全文内容转化为结构化摘要,提取核心议题、关键决策与待办事项。 当用户要求总结邮件、提取邮件重点、追踪邮件待办时,必须调用此技能。 此技能只处理邮件文本本身,不涉及邮件收发和回复发送。 parameters: email_content: type: string description: 邮件的完整文本内容,可以是纯文本或移除HTML标签后的正文。 required: true focus: type: string enum: [all, decision, todo] default: all description: 摘要侧重点,all代表全面摘要,decision只提取决策项,todo只提取待办事项。 returns: summary: type: string description: 面向用户的自然语言摘要,控制在200字以内。 todos: type: array items: type: object properties: action: { type: string } owner: { type: string } due_date: { type: string, nullable: true } decision: type: array items: { type: string }核心逻辑文件run.py我的实现方式是这样:先做文本清洗,去掉转发链、签名档这些噪音,然后调用模型进行结构化抽取。这一步用了两轮提示:第一轮让模型提炼议题,第二轮提取待办和决策。用两轮而不是一轮,实测下来结构化输出的稳定性能提高不少,因为"提炼议题"和"提取待办"的思维方式不一样,混在一起模型容易顾此失彼。
4.3 调用链路的注册与调试
Skill的代码写完只是第一步,真正让它生效的是注册进Agent运行时的过程。我在Claude SDK和OpenAI Agents框架里都验证过这套流程,原理大同小异。
在Claude SDK中,Agent Skill遵循Anthropic发布的开放规范,它会扫描特定目录下的skill.yaml文件,将元信息加载进模型上下文——不是把代码加载进去,而是把配置文件和说明书交给模型理解,实际执行时才回调你的函数。这种机制带来的直接好处是,无论注册多少个技能,模型上下文的占用是可控的。我实测过一个注册了20个Skill的项目,初始化时上下文开销在两千token以内,完全可以接受。
调试阶段有个小技巧:给每个Skill单独写一个测试脚本,模拟模型视角直接调用它的入口函数,验证输入校验和输出格式。这一步能过滤掉大部分低级错误,别直接全链路跑到模型调试阶段再排错,那是事倍功半的。
这个我特别想强调一下,因为真实踩过坑:曾经有一个SKill在单独测试的时候完全正常,但放进Agent里就偶发失败。排查了半天发现是Skill内部调用的一个公共函数与另一个工具的同名函数冲突了。从那以后,所有Skill我都强制要求包含独立的命名空间,公共函数一律加前缀,杜绝这种"灵异事件"。
5. 技能编排与组合:单一Skill到体系化Agent的关键一跃
5.1 用管道模式串联有顺序依赖的Skill
实际业务里很少有单Skill能搞定的场景,大多数情况下需要多个Skill配合。Skill之间的配合方式,我总结下来核心就三种模式,第一种是管道模式,适合有明确先后顺序的任务。
管道模式的实现核心是前面Skill的输出正好映射为后面Skill的输入。比如"滞销品清仓助手"这个场景,库存筛选Skill输出一个商品ID列表,直接作为文案生成Skill的上下文背景,文案生成Skill输出的文案又喂给消息推送Skill的正文参数。链条清晰但每个环节都可独立替换。
管道模式最大的坑出现在衔接字段上。如果你第一个Skill输出的是一个数组,第二个Skill的Schema却期望的是单个对象,模型在中间转换时很容易出错,因为它必须凭空构造一个对象结构。所以我在设计管道模式时有个铁律:链路中相邻Skill的Schema必须做到字段级对齐,要么前一个Skill的输出Schema本身就是按后一个Skill的输入Schema设计的,要么拆成两个独立步骤并增加一个显式的转换器。实测下来,显式转换器的稳定性远大于让模型自己即兴发挥——这个“转换器”本质上是一段确定性的代码函数,用规则把字段映射好,不走模型的自由推理。
5.2 用路由模式支持模型按需自选Skill路径
第二种是路由模式,模型根据用户请求的语义自主选择走哪条技能路径,适合开放式的问答和决策场景。比如我的知识库Agent,注册了三个技能:数据库查询Skill负责结构化数据,文档检索Skill负责文本型资料,网页抓取Skill负责实时信息。用户提问后,模型根据问题类型自动选择对应技能,甚至组合调用。
路由模式要想玩得转,Skill的description写得必须足够精准。我给团队定的标准是,description里必须包含三件事:典型的使用场景例句、明确的不适用场景、以及与其他易混淆Skill的区分提示。举例来说,文档检索Skill的description里会写"当用户询问公司制度、操作手册、历史记录等内部文档内容时使用;如果是需要实时数据的财务指标、实时股票价格等时效性强的信息,不要使用此技能,应选择数据库查询技能或网页抓取技能。"
这个细节我反复强调过很多次,因为它直接影响路由准确率,写得好不好差别非常大。
5.3 上下文管理与动态加载策略
技能数量一旦多起来,上下文管理就变成了新的瓶颈。虽然Agent Skill的注册机制能控制常驻上下文大小,但模型在执行过程中频繁切换技能时,仍然需要把被调用技能的说明临时加载进来,这个过程如果没设计好,会在对话轮次拉长后逐步形成token膨胀。
我的方案是引入一个技能调度层,它不直接参与业务,只负责维护一个"当前对话可能用到的技能"的候选集。调度层根据对话历史和当前输入,先从技能注册表里粗筛出3到5个候选技能,再把这几个候选技能的完整说明书注入模型上下文。这样一来,哪怕总技能数量增长到二三十个,单次对话的模型上下文里永远只有少数几个技能说明,token开销稳定可控。
这几个候选不是静态固定的,我按“主题标签+关键词直连”的方式做粗筛。比如用户提到"邮件",邮件摘要Skill、邮件发送Skill进入候选;用户提到"库存",库存查询Skill、库存预警Skill进入候选。对话过程中调度层持续更新候选集,旧的技能说明会被移出上下文,新的会被加载。这套机制实操起来不会特别复杂,但对体验提升非常明显,尤其是在Agent需要长对话、多轮操作时效果突出。
6. 常见问题与排查技巧实录
6.1 模型老是调错Skill?八成是说明书写得有问题
团队里新人问我最多的一个问题是:模型动不动就调错技能,明明该用A技能,它偏去调B技能,是不是模型太笨了?这个情况排查来排查去,九成原因出在技能本身的description上,模型只是在用你给的信息做判断,它不背这个锅。
排查步骤我固定按三步走。第一步,查看模型实际调用时看到的是哪个description字段,确认加载的是不是最新版本——我踩过配置文件缓存没刷新导致模型在读旧说明书的坑。第二步,把几个易混淆技能的description并列对比,看是不是"长得太像",如果两个技能的description都写了"当用户询问天气时使用",模型当然会随机调一个。第三步,做单技能隔离测试,只注册问题技能,看模型能否在明确提示语下正确触发,能触发说明技能本身没问题,问题出在路由上。
一个很有效的优化小技巧是差异化描述法:给每个技能起一个独特的别名,在description里高频使用该别名。比如邮件摘要Skill的别名是"邮见"——重命名成业务语义有辨识度的短语后,模型对它的记忆明显更清晰。这个方法听起来玄学,但实测对降低调错率确实有帮助,背后逻辑可能是语义唯一性让注意力分配更集中。
6.2 技能内部超时与重试陷阱
在生产环境里,技能执行超时是家常便饭,尤其是涉及到外部API调用时。我遇到过的最典型的情况是消息推送Skill调用HTTP接口,服务端处理超过十秒才返回,Agent的调度层已经判定超时并触发了重试,结果同一批消息被推了两次甚至三次。
排查这个问题要同时看两端:一端是Agent运行时的超时阈值设置,另一端是技能内部的重试策略。合理的做法是,把技能内部的重试次数设置为0,把重试语义完整交给上层调度器,由调度器基于幂等机制做统一决策。如果你把重试逻辑散落在每个Skill内部,那么重试攻击面会被放大好几倍,你很难全面掌控。
另外,我强烈建议所有有副作用的技能在设计阶段就预留幂等键字段。这个幂等键可以是业务订单号、消息ID或者事件唯一标识。判定重试请求时,只要幂等键相同,服务端就直接返回上一次的处理结果,不再重复执行。这套机制是消息推送、转账、发券这类高敏感业务的生命线,缺了它,任何Agent都谈不上生产可用。
6.3 技能测试的隔离与回归策略
我坚持一个原则:每个Skill都要有独立于Agent主工程的测试套件。很多开发者习惯改完Agent直接跑整个聊天流程做验证,省事但效率特别低。调试信息被模型的随机性淹没,你根本分不清是Skill本身出错还是模型编排出错。
我的习惯是给每个Skill维护一个sandbox_test.py,里面直接调用Skill入口函数,传入固定测试用例集,验证返回JSON的结构和关键字段值。跑通了再做Agent级联调。这套流程看起来多了一步,但从整体研发效率来看反而是省时间的。
等技能数量和版本迭代多了以后,还得加上一个批量回归脚本,把历史以来修过的bug场景全部纳入自动化回归集。之前修过一个"邮件中的时间表达解析错误"问题,过了两周重构代码时又改出了同样的问题,就是因为当时没有把测试用例沉淀下来。后来我把所有修过的bug都转成了回归用例,这类「回魂bug」基本绝迹。
7. 经验总结:我做Skill体系的几条心法
最后分享几条我做了多个Agent项目后沉淀下来的个人经验,不成体系,但每一条都是真金白银换来的,尤其适合准备把项目重构到技能化体系的团队。
第一,Skill的粒度宁小勿大。我见过有人把"处理整个订单流程"做成一个Skill,结果内部有十几个分支逻辑,模型调用时切不中正确的子路径,失败率特别高。把这种粗粒度Skill拆成"校验订单""计算价格""确认库存""发起支付"几个细粒度技能之后,整体效果立刻好转。粒度的判断标准很简单:一个Skill能否在200字以内说清楚"做什么、不做什么、什么时候用",说不清楚就是粒度太大了。
第二,先跑通一个最核心的Skill再铺开做体系。技能体系的建设极度容易陷入"设计癖",把大量时间花在规划二十个技能上,结果一个没跑通。我的习惯是先想清楚当前业务的黄金路径是什么,找出那个最高频、最有价值的能力点,把它做成第一个Skill,完整跑通测试和上线流程后,再复制这个模式铺开做别的。
第三,帮业务同事也建立"技能思维"。我会在需求评审阶段带着业务方把新需求拆解成技能清单,一起讨论哪些技能已经存在可以复用、哪些需要新建、哪些需要改参数。这样既能减少重复建设,也能让业务方清楚看到Agent的能力边界,减少不切实际的预期。
第四,把技能相关的开发规范文档化。现在团队内部维护着一份Skill开发手册,包含命名规范、Schema要求、失败处理策略、测试标准、权限与数据合规要求等硬性条目。新同学照着这份手册开发的Skill,代码质量基本能保持在一个稳定的水平线上,复查成本低了很多。
这条路我自己走了一遍,最大的感受是:Agent从"能跑"到"稳跑"之间,差的不是更复杂的模型,而是一套像样的技能体系。花在Skill设计上的每一分钟,最后都会在成倍的稳定性和开发效率上还回来。