skills去年被a社提出来时,最初跟mcp一样,并没有引起什么业界的重视。但随着claude模型能力的逐步提升,以及龙虾等agent的爆火(实质是模型的执行能力变的越来越强),skills跟随着爆火起来。
随着skills的爆火,社交媒体上也出现了很多介绍skills技术的文章。不过大多数skills的介绍文章,都在关注抽象概念,既不涉及实现原理,也不讨论优缺点和场景适用性。
大多数的介绍停留在诸如:不是提示词模板,是可复用能力包之类。话是没错,但能提供的价值没多少。
不关注技术细节是一件好事—在ai时代尤其如此。但我认为这并不适合agent的技术本身。除非,你只想使用它,并不想了解和自己实现它。而且我认为只有真实的了解实现的技术原理,才能对其祛魅和更好的使用其能力。
目前来说,我观察到一个现象:滥用。至少从周边我观察到这样的现象:某大厂在实现自己的业务系统中的agent业务时,将业务场景抽象为skills,在一个基本的手搓的skills运行环境上来执行它;某些人认为,应该将所有的业务场景抽象为skills以复用,以避免业务场景的臃肿。 这当然不是全部的情况,但至少代表了一种现象:在不考虑业务和技术特征的情况的纯秀技使用。
skills的实现机理
我们从skills的组成来分析实现的机理。
skills主要包含两个部分:
- 一个md文件,作为skills的主体。实际还可以拆开,包含名称、一段简短的场景说明和详细的内容
- 一些文件夹,里面可以放领域知识,相关的可执行脚本文件
skills的生效其实是利用llm的两大底层技术:提示词和工具调用(以claude code的实现为例)。
skills的执行原理过程大至是这样的:agent系统中预置了skills工具并装载了简短的skills说明列表,这会导致模型在接收到用户请求的时候识别用户意图并匹配工具调用;skills工具发起工具调用,加载完整的skills提示;工具调用的结果在下一轮模型调用中随着user请求一起发送到模型。
大致是这个样子:
skills的提示词并不是完整的被插入系统提示词中。这里采用了一种很巧妙的方法:渐进式加载。skills在装载以后,仅加将skills.md中的第一段,也就是场景使用提示。这会告诉llm,在某种场景下,或者用户提出某种请求时,你需要去使用特定的skills。
接下来,当用户请求与这段提示词一起发送到llm时,llm会感知到这个 场景,于是发起一个工具调用:我将调用工具来加载这个skills。
整个这个过程,特定的领域prompt并没有发送给llm。但随后,agent的工具系统(或者别的实现)会发起一个调用,将完整的skill.md提示词加载,并随着工具调用结果一起发送给llm。至此,完成了skill所携带携带的那个提示词的注入过程。以上就是渐进式加载。
本质上,这是应用了模型本身的 意图识别能力,加上多一次模型调用,来实现领域知识的注入。
在后续发送给模型的消息中,便有了skills相关的提示词,同时llm就了解了对应的流程、可执行的文件等等。 以下是messages列表的演变过程:
skills执行细节
我们来从claude code的源码中,看看他具体是如何实现的。
skills的规则设定
在初始的用户消息中有一段system-reminder,显示了所有被系统加载的skills的简短列表:
The following skills are available for use with the Skill tool:
- lark-mail: 飞书邮箱 — draft, compose, send, reply, forward…
最重要的是tools部分,有一个单独的Skill tool。其提示词中有一段:
- Available skills are listed in system-reminder messages in the conversation
和
- When a skill matches the user's request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task
这两段在我看来是最核心的部分,一个表示skills在哪里,另一个说明什么时候用:matches user’s request.
skills本身的设计
skill在agent中的体现是一个目录。目录的名子就是skill的名子。这样设计有什么好处呢?简单,便于复制。这个skill可以直接被复制到其他地方使用。
其结构大约是这样:
其核心是skill.md。 scripts等目录并不是协议的一部分,但skill本身可以通过目录引用这些资源,换句话说,它们是skill的专有知识和工具库。
skill.md大概长这样:
name: My Skill
description: Short description shown in skill listings
when_to_use: Use when the user asks to analyze PDF files or extract tables
version: 1.0.0
还有其他一些字段我就不贴了,感兴趣的同学可自行查阅文档。其他的字段,都是skills的执行配置。skills被加载时,会有一个hook可以影响到整体agent的执行状态,可更改模型、允许的工具等。
skill.md中,最重要的便是description和when_to_use。这两段会拼接后,注入到系统提示词的skills列表里面。拼接形式是:- <skill.name>: - <when_to_use>
另外,claude code对于token预算有一整套非常精细的管理。在skills这里,单条skills拼接后,紧多250字符,并且,如果所有skills加起来超过了总预算,还会进一步压缩每一条。因此,skills也并不是加载的越多越好,截断明显会对skills的识别效果产生影响。
skills的作用和缺点
skills的作用是什么呢?
通俗点理解,所谓的skills实际是在agent的执行过程中注入预置好的业务流程和领域知识相关的提示词,从而让llm能产生能力偏向。这其实跟我们在模型中,手动去输入一段特定领域的系统提示词,从而得到一个专家agent没有什么特别大的差别。比如如常见的用法:“你是一个excel分析专家,你可以….” 。那么skills在这个基础上有什么进化?为什么这么火?
在claude code中(或者其他的agent系统中),我们可以通过自定义agent的方式来实现类似的功能。其本质实现方式是固定系统提示词。但这样的agent实现、传播都比较麻烦。而且使用较复杂:你需要指定agent来使用它(或者注入提示词来实现意图识别激活agent,但显示成本 高的多)。
相对来说,skills会有这样一些特点:
- 实现、传播简单。编辑skill.md即可,并且整个文件夹直接打包就可以复制
- 扩展性强。 可以将能力、知识、流程打包到一起
- 使用简单。由于本身agent系统已经处理了意图识别模块部分,因此skills的实际使用中不需要再管触发条件
那skills有没有缺点?有的。在我看来,skills的实现方式是基于这样的特点:单循环agent,user消息处注入,append only式消息。因此这就不可避免的会造成,上下文过长和漂移问题。
- 也许只是在一个长任务中执行一个子任务,但不可避免的这次任务要携带之前的所有无关上下文去调用模型
- 模型在执行完skills以后,可能会忘了本来该以及能干什么。
skills的应用思考
至此我认为skills的设计已经基本分析完了。当然实际的代码中,还有很多实现的细节,例如权限、skills的来源等等。但我认为这些属于是技术实现上很细节的部分,在自己实现它的时候,完全可以自行取舍或者重新设计。只要核心原理保持一致即可。
那么,我们是否应该在自己的业务中,为了复用、场景的好管理,去使用skills呢?
我认为应该慎重考虑。
首先, 我们要看skills本身的特点与限制。skills实现了场景能力的按需加载,模块化。 但它有几个依赖条件与限制:
- 他依赖于agent中,skills子系统的的稳定性。换句话说,在你实现的这个agent中,负责skills的部分,必须稳定,否则基础设备就会形成bug的来源。调试复杂度也会直线上升
- skills的触发除了依赖skill.md的编写,还依赖模型本身的意图识别能力,并不稳定。这在to C的垂类应用场景上,有时候就是不可接受的。
那么,在特定业务场景下去使用skills是否合适呢?我认为是否定的。
举个例子,我们原本可能在一个针对投资/投研领域上做一个垂直类的agent应用。这时我们业务场景其实是有限的:估值、研究、基本面分析等等。此时agent已经直面业务时,稳定性、响应速度也会成为一些限制因素。同时,skills子系统本身也会成复杂性的来源-bug可能来源于skills系统本身,而不是业务场景。
那么claude code这类agent,为什么适用?我认为有以下几点:
- 这是通用agent,搭建skills子系统的收益要高的多,其业务场景是无限的
- 这类直接给用户使用的agent,其本身是有一定容错性的:即便skills没触发,用户仍然可以去手动触发,因为这本身就是用户自身决定好的场景。