
从第一次在 Cursor 里看到skill目录、又在 Claude Code 里遇到SKILL.md再到自己维护的 Agent 项目里全是tool调用很多人和我一样第一反应都是同一个这俩是不是换了个名字的同一个东西先给结论不是。Skill 和 Tool 在 Agent 体系里属于完全不同的两个层次。Tool 解决的是“Agent 能调用什么”Skill 解决的是“Agent 怎么把一件事做好”。理解了这个区别再去看什么“技能库自演进”“动态加载机制”就顺理成章了。这篇文章不聊虚的直接把概念边界、演进逻辑、加载机制和落地时的坑都拆开讲清楚。不管是刚入门 Agent 开发还是已经在维护生产环境技能库的同学都能拿到能直接用的东西。1. Skill 与 Tool 的本质区别先厘清概念边界我见过不少团队把 skill 定义成一个结构体里面塞几个函数指针也见过有人把 tool 的 JSON Schema 里写满自然语言提示词试图让模型“按 better 的方式调用”。这两种做法都是把两个概念混在了一个层级里短时间能跑但很快会在维护阶段出问题。1.1 从代码形态看函数声明与技能包Tool 的本质是一个可执行单元。在 OpenAI Function Calling 里它就是{name, description, parameters}这样的 JSON Schema在代码里它通常是一个接收结构化参数、返回结构化结果的函数。Tool 是无状态的Agent 调用它就像你按了一下电梯按钮输入和输出都有明确边界。Skill 就不一样了。它是一个“包”里面至少包含以下内容触发条件与适用场景描述什么任务激活它什么任务不该激活它。推理路径或工作流模板完成这件事需要分几步每一步用什么 Tool、按什么顺序调用。领域知识与约束规则哪些操作禁止哪些参数必须满足什么条件错误时怎么恢复。示例Few-shot一到两组成功的输入输出片段方便模型模仿。依赖的工具脚本或子技能一个 Skill 内部可以编排若干个 Tool也可以引用另外的 Skill。所以从代码形态上看Tool 是一个“函数声明”Skill 是一个“带说明书的工具包作业流程”。一个 Skill 经常要编排多个 Tool甚至内部还会维护对话状态和中间缓存。1.2 从 Agent 视角看调用者与规划者的关系换个角度从 Agent 的决策逻辑来看这两个概念会更清楚。Tool 是 Agent 的“手”。ReAct 循环里模型根据当前观察决定调哪个 ToolTool 执行完把结果还给模型模型再继续推理。Agent 对 Tool 的理解仅限于 description 和 parameters它对“为什么要调”“调完之后下一步做什么”没有概念。Skill 是 Agent 的“脑内剧本”。加载一个 Skill相当于往上下文里注入了一段“怎么完成这类任务”的完整方法论。模型拿到的不只是一个“执行入口”而是一套决策框架先做什么、后做什么、遇到什么情况用哪个分支、什么情况下直接放弃。用生活化的类比来说Tool 是给你一把螺丝刀Skill 是给你一份“修理一盏台灯”的完整作业指导书上面写着先用万用表测通断、再判断是开关坏了还是灯座坏了、拆的时候注意哪些卡扣、装回去之前怎么验电。螺丝刀只是指导书里要调用的工具之一。1.3 Skill 优于 Tool 的四个关键差异点把差异点整理成一张表方便团队内部对齐认知维度ToolSkill抽象层级原子能力单个操作组合能力完整流程状态管理通常无状态可维护会话级上下文和中间结果内容构成仅函数 Schema说明、流程、规则、示例、工具依赖加载方式静态注册全量可见按需检索动态加载生命周期稳定极少变化可自演进新增、合并、淘汰、版本更新对上下文影响单个 Schema 较小注入后占用较大需要管理复用粒度函数级复用流程级复用容错能力自身只管执行包含兜底策略和异常恢复路径这个差异对架构的影响是决定性的。把 Tool 当 Skill 用上下文会爆炸把 Skill 当 Tool 用Agent 就永远学不会“多步协作完成任务”只会机械地一个一个函数调。理解完这个边界才能继续聊技能库。2. 技能库为什么要自演进先问一个问题如果你的 Agent 只处理三类固定任务skill 库完全手写就够了根本不需要自演进。需要自演进的场景是任务类型持续变化、用户输入覆盖不到边界、最佳实践还在快速迭代的时候。2.1 静态技能库的四大问题很多人把 skill 库做成了“一次性交付”上线之后除了加新功能基本不动。这在任务面很窄的内部工具里没问题但放到通用型 Agent 上静态库会暴露四个问题覆盖不到长尾任务。真实用户的问题永远比开发时列的清单多。手写 20 个 skill 之后第 21 个场景还是不会被覆盖。流程固化。一旦用户用新的方式完成了某类任务积累了更好的路径静态库没有办法吸收这些经验。维护成本高。每次模型升级或底层工具变化所有 skill 都要手动检查一遍工作量线性增长。知识孤岛。某个 skill 里暴露出的好方法没法自动迁移到其它相似场景。2.2 自演进的三个层次积累、修正、涌现我在项目里把 skill 库的自演进拆成三个层次分别对应三种不同的能力和风险等级。第一层是经验沉淀也是最容易落地的一层。Agent 每完成一次任务都会产生执行轨迹trace。如果某条轨迹的执行过程很顺利、结果评分很高系统可以把这条轨迹提炼成一个新的 skill 草稿。具体做法是记录“用户需求 → Agent 的推理链 → 实际调用的工具序列 → 最终输出”然后用一个总结模型把这串记录压缩成触发条件 推理步骤 工具序列 注意事项的格式存入待审区。这个层次做的是“把做过的题整理成错题本”风险低、见效快。第二层是技能修正也就是对已有 skill 做更新。触发条件有两种一是用户显式反馈“这一步不用这么麻烦”二是评估模块发现某个 skill 的失败率开始上升。修正不是直接用新内容覆盖旧内容而是生成一个候选版本和旧版本做离线对比测试——用一组固定的评测任务分别跑旧版和新版看成功率、调用深度和 token 消耗。新版不优于旧版就不上线。第三层是模式涌现这个层次最性感也最危险。当 skill 库数量超过一定规模后会自然出现“两个 skill 的组合能解决第三个问题”的情况。比如团队里有“提取通讯录”和“批量发送邮件”两个 skill组合起来就是一个“群发通知”的新 skill。系统通过分析 skill 之间的共现频率和调用序列会主动生成组合式 skill 的候选。这一层已经不是经验复制而是能力进化。2.3 技能库自演进的落地架构在工程上自演进不是一个魔法函数而是一条流水线我通常把它分成四个模块记忆模块记录每一次任务的完整状态包括输入、中间决策、工具返回、最终评级。这是 skill 生成的原料。提炼模块用大模型定期把记忆中的高价值轨迹转换成 skill 草稿。注意这里的“高价值”不能只看成功率还要看任务出现频率、涉及步骤数量、结果可复现性。评审模块对 skill 草稿做质量检查。自动检查包括步骤里有没有幻觉工具名、定义里有没有冲突触发词、示例是不是和描述一致。手动检查由人来完成最终审批。发布模块把评审通过的 skill 写入索引库更新向量索引标记版本号和生效时间并触发动态加载机制的缓存刷新。这里要给一个非常重要的提醒不要一上来就做全自动演进。LLM 生成的 skill 草稿出现幻觉步骤的概率并不低比如编造一个不存在的外部 API、把应该并行调用的工具写成串行、把错误恢复分支写得过于激进。生产环境里我建议至少保留一层人工审批哪怕只是看标题和概览也能挡住绝大多数低级错误。3. Skill 动态加载机制的设计与实现如果说自演进解决的是“技能库怎么长”动态加载解决的就是“技能怎么被想起来”。很多 Agent 框架早期不做动态加载所有 skill 一股脑塞进系统提示词里效果一定会随着 skill 数量增加急剧下降。3.1 动态加载要解决的三个具体问题第一个问题是上下文长度。一个 skill 的完整定义通常包含几百到一两千 token如果有一百个 skill全量加载就要十几万 token模型既不理解也容易丢失注意力。动态加载只注入当前任务需要的 skill把上下文预算留给真正关键的推理过程。第二个问题是语义干扰。多个 skill 的描述之间可能存在重叠全量注入会导致模型选错触发条件。动态加载通过检索机制把最相关的 skill 筛出来减少混淆。第三个问题是启动与更新成本。skill 库是持续演进的每次新增一个 skill 都要重启服务是不现实的。动态加载意味着 skill 定义可以存储在外部索引或文件系统中更新技能库不需要重启 Agent 进程下一次请求命中时自然用到新版本。3.2 动态加载的两级结构索引层与加载层我习惯把动态加载机制拆成两个相对独立的模块。索引层负责“发现”。每新增一个 skill会先把它切分为描述文本、触发条件、依赖工具列表等几个部分分别建立索引。检索时把用户当前的消息和历史上下文拼接成一个查询向量用混合检索embedding 相似度 BM25 关键词命中找出 Top-K 个候选 skill。注意索引的是“skill 的描述元数据”不是完整的 skill 内容这样检索速度才能达到毫秒级。加载层负责“注入”。拿到候选 skill 之后不是直接把完整内容全部塞进 prompt而是做一次过滤和压缩。过滤规则包括排除与当前任务冲突的 skill、排除未通过权限校验的 skill、排除依赖环境不可用的 skill。压缩策略包括精简 Few-shot 示例为一条、把过长的规章制度改成摘要、把工具脚本留在外部待命而不是加载进模型上下文。这两级结构缺一不可。只有索引层没有加载层你会把所有候选内容都灌进上下文只有加载层没有索引层你就得全量遍历技能库性能撑不住。3.3 基于指令触发的 Skill 定义与动态加载示例在具体实现上一个 skill 的文件定义通常长这样以 JSON 格式为例{ name: meeting_minutes_generator, version: 2.3.1, trigger_keywords: [会议纪要, minutes, 记录会议, action items], description: 从会议转写文本中提取决定、待办、风险并按指定格式输出纪要, workflow: [ { step: summarize, tool: llm_summarize, max_tokens: 1500 }, { step: extract_todos, tool: entity_extractor, requires: [summarize] }, { step: format_markdown, tool: template_renderer, params: {template: minutes_v2} } ], rules: [ 待办事项必须标注负责人拼音缩写, 未明确指出的时间点标记为 TBD, 不要在纪要里添加转写文本中不存在的结论 ], examples: [ [用户: 帮我记录今天的产品评审会, 输出: 一段包含决定/待办/风险的markdown纪要] ], dependencies: [llm_summarize, entity_extractor, template_renderer] }加载管理器在运行时做这件事核心逻辑可以写成下面这样class SkillLoader: def __init__(self, index_store, storage_backend): self.index index_store self.storage storage_backend self.cache {} def load_for_task(self, task_text, top_k3): # 1. 从索引中召回候选 skill 元数据 candidates self.index.search(task_text, top_ktop_k) # 2. 过滤跳过冲突、无权限、依赖缺失的 skill valid_ids [c.id for c in candidates if self._is_loadable(c)] # 3. 从存储中读取完整 skill 定义并做上下文压缩 loaded [] for skill_id in valid_ids: if skill_id in self.cache: payload self.cache[skill_id] else: payload self.storage.fetch(skill_id) self.cache[skill_id] payload loaded.append(self._compress(payload, budget_tokens800)) # 4. 注入 system prompt 并返回 return self._compose_system_prompt(loaded)这里有一个经验点缓存策略不能只看 skill 是否被加载过还要关注版本号。自演进系统更新了 skill 后缓存里的旧版本必须失效。我会在缓存 key 里加入skill_id version避免脏读。3.4 技能健康检查与失败回退动态加载不是一个“加载完就不管了”的动作。skill 被命中并执行之后需要记录执行结果包括是否成功、卡在哪一步、是否有异常退出。这些数据一方面进入自演进流水线作为修正素材另一方面要触发实时的健康检查。我在生产环境里会设置一个简单的规则单个 skill 连续失败 3 次自动从动态加载候选集里剔除失败恢复期间Agent 降级为无 skill 的通用推理模式如果降级后任务完成系统标记该 skill 为“表现不稳定”在下次检索时降低其排序权重。这套回退机制非常重要。一个质量差的 skill 如果被执行 100 次会产生 100 条坏轨迹自演进模块还可能把这些坏轨迹当成“经验”沉淀成新 skill。有了健康检查和回退等于给演进路径加了一道过滤器。4. 从理论到实践常见问题与经验技巧概念讲清楚了机制也拆完了。但真正把这些东西落地到项目里的时候还会遇到一些文档里不会写的问题。这里把我踩过的坑和常用对策整理出来。4.1 技能冲突与优先级两个 Skill 都命中怎么办最典型的问题是用户说“帮我分析这份销售数据”结果“销售周报生成”和“异常指标归因”两个 skill 同时命中了。如果两个都加载模型很容易走错方向如果随机选结果不稳定。我的做法是给每个 skill 增加一个priority字段并定义两条规则一是明确包含高优先级触发词的 skill 优先二是如果优先级相同选择“历史执行成功率更高”的那个。另外在检索阶段可以把 Top-K 里的 skill 做一次两两相似度去重如果两个候选的语义相似度超过阈值只保留描述更精确的一个。4.2 自演进的安全边界哪些内容不允许自动生成大模型生成的 skill 最大的风险不是效果差而是“看起来像是真的”。模型可能生成一个关于删除生产数据库的操作流程写得一本正经。所以技能库自演进必须有硬性边界我在项目里设置的禁止规则包括包含系统级命令执行、文件删除、权限提升等危险操作的 skill一律不允许自动发布涉及资金支付、隐私数据导出的 skill必须经过人工审批且操作路径使用白名单 API技能中引用的工具必须是已注册 tool出现未知工具名的 skill 草稿直接淘汰所有自动生成的 skill 在发布前会跑一遍“危险动作扫描”用规则引擎检查工作流里是否存在禁用操作序列。安全这块宁严勿松。一个全自动演进但缺少安全护栏的技能库比没有技能库还危险。4.3 加载性能与上下文预算的平衡技巧动态加载最常见的性能瓶颈不是检索而是“加载时生成 prompt 的耗时”。如果每个候选 skill 都重新做压缩、示例改写、指令拼接延迟会叠加得很明显。我的优化思路是把 skill 的压缩结果做成预计算缓存每个版本发布时生成一次压缩后的注入模板运行时不重复压缩。这样加载一次 skill 的时间能压到几毫秒。另外在上下文预算分配上可以按 3:1:1 的比例切分主要 skill 占 60%次要 skill 占 20%通用系统指令和当前任务信息占 20%。如果本轮任务明确需要多个 skill 协作则把预算集中到协作路径上不强求“尽可能多加载”。4.4 团队协作中的 Skill 命名与目录规范自演进技能库还有一个很容易被忽略的问题多人协作时skill 的命名和目录结构会迅速腐烂。同一个任务A 写的 skill 叫weekly_reportB 写的内部叫report_generator_weekly检索时两个都命中还会互相干扰。我们团队试过一段时间后确定了两个约定体验很好技能命名统一使用场景_动作的形式比如meeting_minutes_extract、customer_complaint_triage每个 skill 在发布时必须有唯一 ownerowner 负责处理该 skill 的修正申请和废弃提议索引更新时间固定为每天凌晨阶段性合并相似度高的 skill避免技能库无限膨胀。这些规范看起来琐碎但它们是技能库能长期自演进的前提。没有规范的技能库检索召回质量会越来越差最后所有人都会绕过 skill 直接写死逻辑回到原始状态。写在最后的实践经验我个人在实际项目里最深的体会是Skill 和 Tool 的分层不仅仅是代码组织问题它决定了 Agent 系统的成长上限。Tool 层负责稳定Skill 层负责进化动态加载是连接两者的调度中枢。建议先从一个静态维护、全量加载的小技能库起步跑通流程之后再引入检索和动态加载最后才做自演进。不要第一步就追求全自动先让团队习惯“技能是持续生长的资产”这个理念再逐步把成长过程交给系统。等到技能库能自己吸收经验、修正偏差、淘汰冗余的时候Agent 才真正开始像一个有经验积累的团队而不是一台每次从零开始的无状态机器。