最近 AI Agent 圈子里,“Skill”几乎成了和“Tool”一样高频的词。你看社区里到处是“Cursor 有哪些 Skill 推荐”“Claude 怎么写一个完善的 Skill”“Codex 的绘图 Skill 怎么配”,但你要是追问一句“Skill 和 Tool 到底有什么区别”,大部分人是答不上来的。我自己从年初开始给 Agent 项目引入技能库,中间把这两个概念来回揉了好几遍,踩了不少坑,才真正理清楚。今天这篇就聊聊这个看似基础、实际卡住很多人的问题:Skill 为什么不同于 Tool?以及在此基础上,Agent 技能库的自演进和动态加载机制到底该怎么设计。
先说结论:Tool 是一个函数,Skill 是一套方法论。很多人把 Skill 当成“更复杂的 Tool”,或者干脆当成“Tool 的另一个名字”,这个误解会在你做技能库的时候带来连锁问题——技能设计得不像技能,加载策略也用不上,最后系统变成一堆工具的堆砌,谈不上任何演进能力。
1. 先搞清楚:Skill 和 Tool 到底差在哪
1.1 从一次失败的功能调用说起
我先描述一个很典型的场景。你写 Agent 时最早接触的概念大概率是 Tool,OpenAI Function Calling 出来之后,大家都习惯了给模型注册几个 JSON Schema,让它自己去决定调用。你注册了search_on_web、write_file、run_code,模型看着用户的问题,选一个执行。这套逻辑理解成本极低,用起来也不容易出大错,但它的上限非常明显:你的 Agent 永远只会做这三件事,不会多,也不会少。
Skill 的思路不一样。Claude Code 引入 skill、Cursor 现在也有类似的目录结构,它们核心逻辑是一致的:把“一件完整的事应该怎么做”描述清楚,而不只是提供一个孤立动作。一个 Tool 解决“怎么打开网页”,一个 Skill 解决“怎么完成一次靠谱的竞品调研”——后者包含了检索词的构造方法、信息来源的选择、交叉验证的步骤、报告输出的结构,甚至常见坑的规避方式。
我见过不少项目把 Skill 写成“把五个 Tool 塞进一个 JSON”,然后告诉模型“这几个函数你看着用”。这不叫 Skill,这叫函数分组。真正的 Skill 必须有过程性知识,而 Tool 只有原子能力。
1.2 粒度不同:Tool 是螺丝刀,Skill 是拆机流程
我常用一个生活类比来解释粒度差异。Tool 是工具箱里的一把螺丝刀,它只负责“拧”这一个动作,输入是螺丝型号,输出是扭矩。Skill 则是你用螺丝刀拆一块显卡的整套流程:先断电、判断哪几颗螺丝、按什么顺序拧、用多大力道、拧下来的螺丝放哪个格子、遇到滑丝怎么处理。
这个类比能引出 Skill 和 Tool 最核心的三个分界线:
| 维度 | Tool | Skill |
|---|---|---|
| 粒度 | 原子操作,单一动作 | 复合能力包,包含多步流程和决策规则 |
| 状态 | 无状态,每次调用相互独立 | 可以有状态,记录进度、中间结论、已排除的错误分支 |
| 描述方式 | JSON Schema 结构化参数 | 自然语言工作流 + 脚本 + 参考资料 |
| 能力边界 | 固定,写死就是写死 | 可演进,可从失败中增补步骤 |
| 触发方式 | 模型按需直接调用 | 先检索、再加载、再执行 |
这幅表格里的最后两行,其实就是标题里“自演进”和“动态加载”的伏笔。Tool 是固定的,所以它不需要演进,也没法演进;Tool 数量有限,所以它也不需要动态加载,一次性全注册给模型就行。但 Skill 不一样,它的数量和复杂度会增长,而且它需要在实践中不断调整自己。
1.3 本质分水岭:Skill 是有状态、可演进的
我再展开说说状态性。一个 Tool 调用web_search(query),发出去就结束了,不需要记住“我刚才搜过什么”。但一个 Skill“分析用户反馈”执行到一半,它必须要知道:已经读完了哪三个文件、情感分类做到哪一步、目前发现的主题有哪些、下一步该调用哪个聚类脚本。这些状态如果每次都要模型从零开始推理,token 消耗大,还容易错。
所以我设计 Skill 的时候,会把“工作记忆”作为一等公民。Skill 的元信息里带一个current_progress字段,每当执行完一个阶段就更新一次。这样 Agent 哪怕中途被打断、或者上下文被压缩,恢复之后还能接着干。这个设计 Tool 是做不到的,因为 Tool 没有“阶段”的概念。
可演进就更关键了。Tool 的 bug 修一次就完了,逻辑是内存里写死的;Skill 则是一个可以持续修改的文档加脚本组合。你今天发现这个 Skill 的流程漏了一个“数据去重”步骤,第二天就可以把这一步补进 SKILL.md,之后所有加载这个 Skill 的 Agent 行为都会跟着更新。这种“行为会变”的特性,就是后面讲自演进机制的地基。
2. 代码层面的差异:从 JSON Schema 到技能描述
2.1 Tool 的接口长什么样
先看一段最常见的 Tool 定义,这是 Function Calling 里一个标准 Web 搜索工具的写法:
tools = [ { "type": "function", "function": { "name": "web_search", "description": "执行一次 Web 搜索,返回前10条结果", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "max_results": {"type": "integer", "default": 10} }, "required": ["query"] } } } ]这套结构的本质是给模型一份“可调用函数清单”。模型读到的信息只有:函数名、一句话描述、参数约束。它不知道这个函数内部怎么工作,也不知道什么时候该优先用它,更不知道用了之后结果该怎么解读。这些高层决策目前还是靠模型的通用推理能力硬扛的。
当你的项目只有三五个 Tool 时,这种做法完全够用。但当你开始积累几十个 Tool,问题马上来了:模型的选择准确率开始往下掉,经常在参数上传错、或者调了 A 工具其实应该调 B 工具。这时候你就需要一个中间层,把“选哪个工具”的决策,变成“用哪套流程”的决策。
2.2 Skill 的目录结构长什么样
我现在项目里的 Skill 组织方式,参考了 Claude Code 的 skill 写法,又加了点自己的约束。一个典型的 Skill 长这样:
user-feedback-analysis/ ├── SKILL.md ├── scripts/ │ ├── sentiment.py │ └── cluster_topics.py └── references/ └── report_template.mdSKILL.md 是核心,它用自然语言描述完整工作流。我一般要求它必须包含以下几个字段:
--- name: user_feedback_analysis description: 对用户反馈文本执行结构化分析,输出情感分布、主题聚类和改进建议 triggers: - 分析用户反馈 - analyze user comments - 用户评价聚类 --- ## When to use 当用户要求分析评论、反馈、工单,或需要从非结构化文本中提取洞察时。 ## Workflow 1. 读取原始反馈数据(使用 read_file 或 fetch_data) 2. 调用 scripts/sentiment.py 对每条反馈做情感分类 3. 调用 scripts/cluster_topics.py 做主题聚类 4. 汇总成表格报告,输出到 write_file ## Error handling - 数据文件不存在:先搜索本地目录,不要直接报错 - 情感分类置信度低于 0.6:标记为 uncertain,归入待复核列表你可以对比一下:Tool 的 JSON Schema 里只有“输入输出”,而 SKILL.md 里有触发条件、执行顺序、错误处理、产出格式。模型加载这个 Skill 之后,它不只是多了一个函数,而是知道了“做这件事的标准流程”。
这种描述驱动的好处,在于把不可见的推理过程变成了可见的流程文档。你可以直接审视这个 Skill 写得对不对,可以在失败之后精准地修改某一处步骤,而不用翻代码。
2.3 为什么是“SKILL.md + scripts + references”三段式
我解释一下这个结构为什么值得参考。SKILL.md 承担叙事层,负责让模型理解整体流程和决策逻辑;scripts 承担确定层,比如聚类算法、情感打分这类稳定的逻辑,不应该让模型每次临时自由发挥;references 承担知识层,提供特定领域的背景资料或产出模板。
模型是语言模型,不是代码解释器,所以叙事层的质量直接决定它执行得有没有章法;脚本可以把容易出错的数值计算固定下来,避免模型幻觉;参考资料则可以控制知识注入的时机——不是一开始就全塞进去,而是流程走到某一步需要时再读。这三个层次互相配合,Skill 才有灵魂。
3. Agent 技能库为什么要“自演进”
3.1 静态技能库的三个痛点
先说自演进之前,静态技能库到底痛在哪里。第一个痛点是长尾能力永远覆盖不全。你不可能在上线前把所有任务场景枚举完,真实世界里用户的问题总会在某个角落超出你预设的范围。第二个痛点是冷启动阶段技能是抄来的——社区里一堆人做的“推荐 Skill 包”本质上都是别人方法论,适合别人场景不代表适合你,必须跑起来后针对自己领域修正。第三个痛点最实际:上下文窗口和 token 成本不允许你塞下满满的技能文档。当技能库超过二三十个,你就已经被迫开始做取舍了,而这个取舍不该靠手工完成。
这几个痛点指向同一个答案:技能库必须能在运行中自己“长出”新技能、修订旧技能、淘汰过时技能。这就是自演进的价值。
3.2 自演进的基本循环:观察、反思、生成、验证、入库
我给自己的 Agent 设计了一套五个阶段的演进循环,跑下来小半年,效果还不错:
- 观察(Observation):每个任务执行完,把完整的执行日志留存下来,包括任务目标、实际动作序列、每步的结果、最终是否成功、失败时的错误信息。
- 反思(Reflection):用反思模型判断失败根因。是“缺少某个步骤”“步骤顺序不对”“领域知识不足”,还是“模型本身幻觉”。这个分类很关键,因为只有根因是前三类时,才适合通过修改 Skill 解决。
- 生成(Generation):根据根因生成补丁。缺步骤就往 Workflow 里加一步;顺序不对就调整 Workflow 的步骤编号;知识不足就补 references 里的资料。
- 验证(Verification):拿历史成功任务当回归集,跑一遍修改后的 Skill,确认没有破坏原本能跑通的场景。
- 入库(Registration):通过验证后,更新 SKILL.md 或 references,同时更新技能库索引。
这里最关键的一条经验是:自演进的关键不是“生成”,而是“评估”。大模型生成一个新 Skill 太容易了,难的是判断这个 Skill 到底该不该存在。我看有的朋友做的 Agent 跑一天就多出两三百个 Skill,每个描述都写着“处理各种问题”,最后检索时全是噪音,根本没法用。我后来给自己的库定了几条硬门槛:
提示:新 Skill 必须让某类任务的成功率相对提升10%以上才允许入库;连续10次执行无失败记录的旧 Skill,可以降低它在前置检索中的权重;连续20次未被加载的 Skill,自动进入待清理名单。
这套规则让技能库保持克制,进化出来的都是真正经历过实战检验的能力,而不是反思模型的胡思乱想。
3.3 一次失败记录长什么样
自演进依赖结构化日志,我记录失败事件的格式大致如下:
{ "task": "分析某产品的用户反馈", "action_sequence": ["read_file", "fetch_data", "write_file"], "failure": "产出报告未覆盖负面情绪条目", "root_cause": "缺少情感分类步骤", "patch": "在 SKILL.md Workflow 第2步后插入 sentiment.py 调用" }有了这种记录,演进就变成了一个可追溯的过程。你能清楚知道哪个 Skill 在哪次任务上跌倒过、补了什么内容、补完之后效果如何。这套日志也是后面动态加载优化的重要数据来源。
4. 动态加载机制:怎么让几百个技能不撑爆上下文
4.1 为什么必须动态加载
技能库一旦超过三五十个,把所有技能的描述一次性塞进系统提示词就不现实。一个中等规模的 SKILL.md 约 2000 token,50 个就是 10 万 token,这还不算脚本和参考资料。每轮对话都支付这么大的成本,速度慢、费用高,而且模型注意力被大量不相关内容稀释,选择准确率反而下降。
所以动态加载不是“高级功能”,而是技能库规模化的必然选择。核心思路就一句话:每次只往上下文里注入最可能解决当前任务的 Skill,任务结束或不再相关时再撤下。
4.2 一个可落地的五段式加载流程
我自己的实现是这样的:
- 索引层(Index):每个 Skill 维护一份 profile,包含标识、功能描述、触发关键词、依赖的工具列表、embedding 向量。加载时只读 profile,不读 SKILL.md 全文,这样索引层可以做到非常轻量。
- 意图解析(Intent Parsing):从用户请求和当前 Agent 状态抽取任务描述,生成检索 query。
- 召回(Recall):组合三种策略——向量检索算相似度拿 top-N、关键词来自 profile 里的触发词精确命中、规则匹配看当前阶段适合哪些技能。
- 精排(Ranking):对候选技能打分,综合向量相似度、历史成功次数、被加载后的任务完成率,最终选 top-K 注入,我一般设 K 为 3 到 5。
- 执行与回收(Execute & Recycle):加载后交给模型执行,任务完成后把完整技能描述撤出上下文,只保留必要的记忆摘要。
这个流程的一句话版本是:把技能库当知识库用,不是把全部文档发给模型,而是先检索出最相关的几份,再让模型精读。这和 RAG 的哲学完全一致,区别在注入的内容不是纯知识文本,而是一整套带工作流的方法论。
4.3 依赖关系和常用技能的缓存优化
动态加载里最容易忽视的是 Skill 之间的依赖。有些 Skill 执行到中间需要调用某个公共工具,如果这个工具没有同时加载,流程就会卡住。我的做法是每个 Skill 的 profile 里显式声明dependencies字段,加载时先递归把依赖一起加载,确保整套流程能跑通。
另外一个优化策略是把高置信度常用技能做成常驻缓存。比如“需求分析”和“报告生成”这类几乎每个任务都会用到的 Skill,不需要每次动态检索,直接压在系统提示词里。剩下的长尾技能才走动态加载,检索压力会小很多。这个“常驻 + 动态”的混合模式,是我实测下来性价比最高的组合。
4.4 动态加载的动态回溯
动态加载不只是静态的“检索一次、加载一次”。我后来加了一层基于反馈的自我修正:某个 Skill 被加载后,如果任务成功,给它一个正向权重加成;如果失败,权重降低。这个权重直接影响下次检索排序。时间一长,技能库本身就会形成一种“好用的技能靠前、不好用的技能靠后”的状态,这算是一种微观层面的自演进。
用一段 Python 伪代码可以概括完整流程:
def dynamic_load(user_input, library_index, k=5): candidates = semantic_search(user_input, library_index, top_k=k*3) keyword_hits = keyword_trigger(user_input, library_index) merged = merge_score(candidates, keyword_hits) selected = top_n(merged, max_num=k, weight_boost=True) injected = [lib[skill_id].compile_for_prompt() for skill_id in selected] return injected这个 70 分的基础实现已经能解决 80% 的问题,剩下的 20% 都是边界情况和工程细节,下面专门讲。
5. 从设计到落地:我踩过的四个典型坑
5.1 工具注入太多,触发 tool-call flooding
有一个坑我印象特别深:技能库上线第二周,系统突然频繁报“tool call cancelled because tool-call flooding was detected”。排查了半天才发现,某个 Skill 的依赖声明写得太宽泛,一次加载带了 15 个工具进来。模型一下子看到太多可选工具,一次请求里发了几十个 tool calls,直接把框架的保护机制打爆了。
解决办法有两层。第一层是把同时注入的工具数量限制在 5 到 8 个,宁可让 Skill 内部多步串行调用同一个工具,也不要一次性开放所有权限。第二层是要求 Skill 内部步骤设计成顺序依赖——第 1 步完成并写入状态之后,第 2 步才可见对应的工具,从机制上逼着模型按流程走。
5.2 Skill 描述与用户问法对不上,检索召回率低
第二坑是“写了 SKILL.md 但检索不到”。有一次我写了个“数学建模 Skill”,profile 里用的全是数学术语,结果用户在 Cursor 里问“帮我建个模型预测销量”,向量检索出来的 top-5 根本不含这个 Skill。原因很纯粹:profile 的关键词和 embedding 描述没有覆盖用户可能用的日常表达。
解法是在 profile 里维护一个synonyms字段,把领域术语、口语说法、中英文表达都写进去。现在我的规则是:写 profile 时至少列 5 个同义触发表达,并且用真实用户问题做过一轮测试,确保能召回。这一步看着不起眼,实际上对动态加载的命中率影响最大。
5.3 自演进失控,技能库从 8 个膨胀到 150 个
这个坑前面提到过,我再细化一下。我的一个实验 Agent 跑了 48 小时,技能库从 8 个涨到 150 个,中间大量技能功能重叠,甚至有 20 多个技能描述都是“处理各类任务”。复盘后发现反思模型太“积极”,遇到一点小失败就生成新 Skill,而不是先尝试修订现有 Skill。
我后来加了三条硬约束才止住膨胀:一,候选 Skill 在生成时必须和现有库做相似度对比,阈值过高直接拒绝入库;二,设置单库上限,比如 60 个,超出的必须淘汰库里已有 Skill 才能放新的进来;三,每个新 Skill 入库前用 5 个历史真实任务做回归测试,跑不过就重写。这三条加上之后,技能库的规模就平稳下来了,质量和有效加载率反而上去了。
5.4 “需要立即结果”的调用,被动态加载的延迟拖垮
还有一个很隐蔽的坑,群体热词里有个常见报错叫“messages tool calls need immediate results”,大意是模型发出了某个工具调用,但框架要求它立即返回结果,而技能库动态加载本身有延迟,导致调用被判定为超时或取消。这类问题本质上是"加载时机"设计失误。
我自己的修正方案是:把高置信度常用技能做成常驻缓存,避免每次从磁盘读取和 embedding 检索;同时在意图解析阶段就预加载候选技能,而不是等模型生成 tool call 时再临时加载。也就是说,把动态加载从“模型决策后”提前到”意图明确后“,把延迟从关键路径上挪走。这个调整之后,类似的“调用无法立即满足”的问题基本绝迹。
6. 最后的一点经验沉淀
Skill 和 Tool 的区别,本质上是“能力”和“方法论”的区别。Tool 在解决“能做什么”,Skill 在解决“该怎么做得靠谱”。一个只有 Tool 的 Agent 像一个什么都会一点但没条理的新手;一个有成熟 Skill 库的 Agent 像一个有标准操作程序的老工程师。
如果你现在正在做 Agent,我的建议是:先别急着收藏一堆网上推荐的 Skill 包,先把手头能稳定执行的几类任务沉淀成 Skill,跑一周看看哪些步骤容易出错、哪些决策需要人工介入,再动手优化。技能库的自演进和动态加载,不是一开始就要做得很复杂的东西——它是你的库长到一定规模之后自然产生的需求。真到了那一步,你会发现前面聊的这些机制,每一条都是从实际问题里长出来的,一个都不会白做。