1. 从一个被问烂了的问题说起Skill 和 Tool 到底差在哪如果你最近半年在折腾 Agent 相关的东西大概率会在某个群里看到有人问“Skill 和 Tool 到底有什么区别不都是给模型调用的能力吗”这个问题看起来像概念辨析实际上它决定了你整个 Agent 架构的走向。我自己在这个问题上踩过坑早期做的一个客服 Agent把所有能力都写成 Tool 注册进去结果跑到第三周就崩了——不是代码崩了是维护崩了。工具列表膨胀到四十多个模型选工具的准确率断崖式下跌改一个工具的描述还可能影响另外三个工具的选择。后来我把一部分能力重构成 Skill情况才好转。所以这篇东西不是概念科普是我自己从 Tool 堆砌到 Skill 分层这个过程中的完整复盘。核心想讲清楚三件事Skill 和 Tool 在机制上的本质差异、技能库为什么需要自演进、动态加载到底怎么落地。适合已经写过至少一个能跑的 Agent、正在被工具膨胀和上下文爆炸折磨的开发者。如果你还在“Hello World”阶段建议先跑通一个最小 Agent 再回来看会更有感觉。先给一个我自己的定义不一定标准但足够实用Tool 是原子能力Skill 是带上下文的能力编排。Tool 回答“能做什么”Skill 回答“在什么情况下、按什么顺序、用什么参数做什么”。这个差别听起来很虚但落到代码结构上差别巨大。2. Tool 的本质一个被过度简化的抽象2.1 Tool 的经典形态与它的隐含假设绝大多数 Agent 框架里的 Tool形态都差不多一个函数一段描述一份参数 schema。模型看到的是描述和 schema调用时输出参数框架负责执行。这个设计非常优雅优雅到让人误以为它能解决所有问题。它背后有几个隐含假设平时不会有人告诉你但一旦规模上去就会暴露假设一工具之间相互独立。你注册search和summarize框架默认它们互不干扰。但真实业务里搜索的结果格式直接决定摘要工具能不能用。假设二工具描述足够让模型选对。工具少的时候成立超过二十个之后描述之间的语义重叠会让选择准确率明显下降。假设三工具是无状态的。每次调用都是全新开始但很多任务需要跨调用保持中间状态比如“先查订单再根据订单状态决定是否退款”。这三个假设在小规模 demo 里都不成问题一旦进入真实场景它们就是三颗定时炸弹。2.2 工具膨胀为什么必然发生我观察过自己经手的几个项目工具数量的增长曲线几乎都是一样的第一周 5 个第一个月 15 个第三个月 40 个以上。原因很简单业务需求是加法驱动的每来一个新场景最直觉的做法就是加一个 Tool。问题在于Tool 的边际成本不是线性的。我做过一个粗略统计在一个 40 工具的系统里工具数量工具选择准确率我的实测平均上下文占用5约 95%低15约 88%中30约 76%高40约 65%很高这个数据不是学术结论是我在几个项目里用同一批测试用例跑出来的趋势具体数值会因模型和任务而异但趋势很稳定工具越多选得越不准上下文越贵。这就是工具膨胀的代价。2.3 一个具体的翻车现场说个具体的。之前做一个内部知识助手工具列表里有search_docs、search_wiki、search_tickets三个搜索类工具。模型经常在该用search_docs的时候选了search_wiki因为两者的描述都写了“搜索内部知识”。我改描述改了三次每次好一阵子又退化。后来我意识到这不是描述的问题是抽象层级的问题。这三个工具在业务上其实是一个能力——“搜索内部知识”只是数据源不同。把它们拆成三个 Tool等于强迫模型在错误的层级上做决策。正确的做法是把它做成一个 Skill内部根据查询意图路由到不同数据源模型只需要决定“要不要搜索知识”不需要决定“搜哪个库”。这个认知转变是我从 Tool 思维转向 Skill 思维的起点。3. Skill 的本质带上下文的能力编排单元3.1 Skill 的构成要素一个完整的 Skill我通常会包含这几个部分触发条件什么情况下这个 Skill 应该被激活。可以是语义描述也可以是前置状态判断。编排逻辑内部调用哪些 Tool、按什么顺序、如何处理中间结果。上下文契约需要哪些输入上下文产出哪些输出上下文供后续 Skill 使用。失败处理某一步失败时是重试、降级还是终止。元数据版本、依赖、适用场景标签这些是自演进的基础。对比一下 ToolTool 只有“函数 描述 schema”Skill 多了编排、上下文和元数据。多出来的这些正是解决工具膨胀的关键。3.2 为什么 Skill 能缓解工具膨胀核心在于抽象层级的提升。Tool 是扁平的所有能力都在同一层模型要在一堆平级选项里挑。Skill 是分层的模型先决定用哪个 Skill数量可控Skill 内部再决定用哪个 Tool由代码逻辑决定不消耗模型的决策预算。打个比方Tool 像是把家里所有工具摊在桌上让模型挑Skill 像是把工具收进几个抽屉抽屉上贴标签模型先选抽屉打开抽屉后里面的工具怎么用是固定的。桌子清爽了选择也准了。我实测下来把 40 个 Tool 重构为 8 个 Skill每个 Skill 内部平均编排 5 个 Tool之后顶层选择准确率从 65% 回升到 90% 以上而且上下文占用降了将近一半因为模型不需要同时看到所有 Tool 的完整 schema。3.3 Skill 不是银弹它引入的新问题得说清楚Skill 不是免费的。它引入了几个新问题编排逻辑的维护成本Skill 内部的流程是代码改流程要改代码不像加 Tool 那么随意。粒度的拿捏Skill 太粗内部逻辑复杂难维护太细又退化成 Tool。调试难度上升一个 Skill 出错可能是编排问题也可能是某个 Tool 问题排查链路变长。所以 Skill 和 Tool 不是替代关系是分层协作关系。我的经验是原子操作做 Tool业务语义做 Skill。判断标准很简单——如果一段能力需要“先做 A 再做 B 根据 B 的结果决定做 C 还是 D”它就是 Skill如果它就是“输入 X 输出 Y”它就是 Tool。4. 技能库的自演进让 Skill 自己长出来4.1 为什么技能库需要演进静态的技能库有个致命问题它假设设计者能预判所有场景。但真实业务里用户的需求是长尾的你今天想不到的场景明天就会出现。如果每次都要人工加 Skill那技能库永远滞后于需求。自演进要解决的就是这个滞后。它的目标是让系统在运行过程中从实际使用数据里发现“缺了什么 Skill”“哪个 Skill 该拆”“哪个 Skill 该合”并给出可执行的调整建议甚至自动完成调整。4.2 演进的两个方向增量和结构我把自演进分成两类增量演进发现缺失的 Skill。比如系统频繁出现“用户问 A现有 Skill 都无法很好处理最后靠模型硬答”的情况这就是一个信号——需要一个新 Skill 覆盖 A 场景。结构演进调整现有 Skill 的边界。比如两个 Skill 的触发条件高度重叠说明它们该合并或者一个 Skill 内部逻辑分支过多说明它该拆分。增量演进相对好做结构演进难得多因为它涉及对现有 Skill 的修改风险更高。4.3 演进信号的采集自演进的前提是有信号。我通常采集这几类未命中日志用户请求没有匹配到任何 Skill或者匹配到的 Skill 执行失败。降级日志Skill 执行中某步失败走了降级路径说明这个 Skill 的编排不够健壮。人工接管日志模型处理不了转人工的 case这是最强烈的信号。执行耗时与成功率某个 Skill 成功率持续偏低可能是编排有问题。这些信号采集起来不难难的是从信号到决策。我的做法是定期比如每周跑一次分析把信号聚类人工review后决定是否调整。全自动的演进我试过风险太大一个错误的自动合并可能让整个技能库退化。4.4 一个增量演进的实例说个真实的。有个做文档处理的 Agent运行两周后日志显示大量请求是“把这份 PDF 转成结构化数据并校验”。当时技能库里只有parse_pdf和validate_data两个独立 Skill模型经常只调一个就结束导致结果不完整。分析后我加了一个pdf_to_structured的 Skill内部编排解析和校验两步并强制校验不通过就重试解析。上线后这类请求的成功率从 60% 出头涨到 90% 以上。这个 Skill 不是我拍脑袋想的是日志告诉我“用户实际在做的是一件事而系统把它拆成了两件”。这就是自演进的价值它让技能库跟着真实使用走而不是跟着设计者的想象走。5. 动态加载让技能库按需生长5.1 为什么不能一次性全加载技能库大了之后全量加载有两个问题上下文爆炸和启动变慢。假设你有 50 个 Skill每个 Skill 的描述加元数据平均 200 token全量注入就是 10000 token还没开始干活就烧掉一大截预算。而且大部分请求只会用到其中两三个 Skill剩下的纯属浪费。动态加载的思路是只加载当前请求可能用到的 Skill。这需要一套检索机制根据请求内容召回相关 Skill再注入上下文。5.2 动态加载的三种实现路径我试过三种各有适用场景路径一向量召回。把所有 Skill 的描述向量化请求来了先做相似度检索召回 top-k 个 Skill 注入。优点是实现简单缺点是召回质量依赖描述质量且对“需要组合多个 Skill”的场景不友好。路径二分层路由。先用一个轻量分类器判断请求属于哪个大类再加载该大类下的 Skill。优点是可控缺点是需要维护分类体系新场景要更新分类器。路径三渐进式加载。先只加载最核心的几个 Skill模型发现不够用时主动请求“加载更多”系统再按需注入。这个最灵活但对模型的元认知能力要求高实现也最复杂。我目前的主力方案是路径一 路径三的混合默认向量召回 top-5同时给模型一个load_skill的元工具允许它主动加载。实测下来召回覆盖了大部分场景主动加载兜住了长尾。5.3 动态加载的工程细节这块坑不少说几个关键的Skill 描述的写法直接影响召回质量。描述不能只写“这个 Skill 做什么”还要写“什么情况下用”。我通常要求描述包含能力概述、适用场景、不适用场景、典型输入示例。这样向量召回才能区分开相似 Skill。召回数量要控制。召回太多等于没召回召回太少容易漏。我的经验值是 top-5 到 top-8具体看 Skill 的平均粒度和请求的复杂度。要有兜底机制。召回失败或者召回的 Skill 都不适用时要有降级路径比如回退到全量加载或者直接让模型硬答。不能因为召回失败就让整个请求挂掉。缓存召回结果。相似请求的召回结果往往一致缓存能省不少向量检索的开销。我用的是请求指纹做 key命中率大概能到 40%。5.4 动态加载与自演进的配合这两个机制是互相成就的。动态加载让技能库可以变大而不拖累性能自演进让技能库可以变大而不失控。没有动态加载自演进加进来的 Skill 会拖垮上下文没有自演进动态加载只是在管理一个僵化的库。我的系统里自演进产出的新 Skill 会先进入“候选池”动态加载默认不召回候选池的 Skill只有人工确认后才转正。这样既保证了演进速度又控制了风险。6. 实操从零搭一个带自演进的技能库6.1 整体架构我用的架构分四层接入层接收请求做初步的意图识别。召回层根据请求召回相关 Skill动态注入上下文。执行层运行 Skill 的编排逻辑调用底层 Tool。演进层采集执行日志定期分析产出 Skill 调整建议。这四层里召回层和演进层是 Skill 体系区别于纯 Tool 体系的关键也是我花时间最多的地方。6.2 Skill 的数据结构我用的是类似这样的结构伪代码具体语言随意skill { id: pdf_to_structured, version: 1.2.0, description: 将 PDF 文档解析为结构化数据并校验完整性, when_to_use: 用户提供 PDF 并要求提取结构化信息时, when_not_to_use: 用户只是要 PDF 的文本内容不需要结构化, inputs: [pdf_url], outputs: [structured_data, validation_report], orchestration: [ {tool: parse_pdf, retry: 2}, {tool: validate_data, on_fail: retry_parse}, ], tags: [document, extraction], status: active, # active / candidate / deprecated }几个设计要点when_to_use和when_not_to_use是给召回用的必须写清楚orchestration是编排逻辑用声明式而不是命令式方便演进层分析和修改status字段支撑候选池机制。6.3 召回层的实现召回层我做了两路并行向量召回和标签过滤。向量召回负责语义相似标签过滤负责硬性约束比如某些 Skill 只在特定租户下可用。两路结果合并去重后取 top-k。向量化用的是通用的 embedding 模型Skill 描述拼接description when_to_use作为向量文本。这里有个细节when_not_to_use不参与向量化但会在召回后做二次过滤把明确不适用的 Skill 剔除。6.4 演进层的实现演进层是个离线任务每周跑一次。流程是拉取过去一周的执行日志。聚类未命中和降级 case。对每个聚类判断是“缺 Skill”还是“现有 Skill 有问题”。缺 Skill 的生成候选 Skill 草稿有问题的生成调整建议。人工 review确认后写入技能库。第 4 步的草稿生成我用了模型辅助让它根据聚类里的请求样本草拟 Skill 的描述和编排。但草稿必须人工确认我试过全自动写入出过一次事故——模型把一个本该拆分的 Skill 合并了导致后续一周的相关请求全部走错路径。6.5 上线后的效果这套东西在一个文档处理 Agent 上跑了三个月几个关键指标指标上线前上线后顶层选择准确率约 70%约 91%平均上下文 token约 8000约 3500未命中率约 18%约 6%技能库 Skill 数12静态23含演进新增Skill 数涨了将近一倍但上下文反而降了这就是动态加载的功劳。未命中率降下来是自演进的功劳。7. 踩过的坑与排查技巧7.1 常见问题速查现象可能原因排查方向召回总是漏掉某个 Skill描述里缺少场景词检查 when_to_use 是否覆盖真实请求的表达召回结果高度重复Skill 粒度过细考虑合并语义重叠的 Skill模型不按 Skill 编排走编排逻辑对模型不透明检查是否需要把编排暴露给模型自演进产出大量低质候选信号聚类粒度过粗调整聚类参数增加人工过滤动态加载后响应变慢向量检索成为瓶颈加缓存或改用更轻量的召回方式7.2 几个独家避坑经验坑一Skill 描述写得太“官方”。我一开始按写文档的习惯写 Skill 描述结果召回效果很差。后来发现描述要贴近用户的真实表达甚至要把用户可能说的口语化说法写进去。比如“帮我看看这个 PDF”这种比“解析 PDF 文档”召回效果好得多。坑二编排逻辑过度依赖模型。早期我让模型在 Skill 内部也做决策结果 Skill 的执行路径变得不可预测。后来改成编排逻辑由代码固定模型只在 Skill 之间做选择稳定性大幅提升。Skill 内部要确定性Skill 之间可以灵活。坑三自演进频率太高。我试过每天跑一次演进结果技能库天天变模型的行为跟着飘用户反馈“这个助手怎么一天一个样”。后来改成每周一次稳定多了。演进不是越快越好要给系统留出稳定的窗口。坑四忽略 Skill 的版本管理。Skill 改了之后历史请求的复现会出问题。后来我给每个 Skill 加了版本号日志里记录用的是哪个版本排查问题时能精确复现。坑五候选池没有过期机制。候选 Skill 如果一直没人确认会越积越多。我加了个 30 天过期过期自动归档需要时再捞出来。7.3 一个排查实例有段时间发现某个 Skill 的成功率突然从 90% 掉到 70%。查日志发现是它内部调用的一个 Tool 的返回格式变了上游服务升级导致编排里的解析逻辑失败。这个问题的教训是Skill 的编排逻辑要对底层 Tool 的变化有感知。后来我在编排里加了输出 schema 校验格式不对就报警而不是静默失败。8. 我对 Skill 体系的一些个人判断折腾了这么久有几个判断越来越清晰。第一Skill 和 Tool 会长期共存不是替代关系。Tool 负责原子能力Skill 负责业务编排这个分层是稳定的。试图用 Skill 替代所有 Tool会让 Skill 变得臃肿试图用 Tool 解决所有问题会陷入工具膨胀。第二自演进的重点是“发现”而不是“自动修改”。让系统自动发现“缺了什么”价值很大让系统自动修改技能库风险很大。我现在把自演进的产出定位为“建议”人工确认这一步短期内不会去掉。第三动态加载的瓶颈在召回质量不在加载机制。加载本身不难难的是召回得准。我在这上面花的时间八成在优化 Skill 描述和召回策略两成在写加载逻辑。第四技能库的规模不是越大越好。我见过有人追求“技能库上千个 Skill”但实际用到的可能就几十个。技能库的价值在于覆盖真实场景不在于数量。定期清理 deprecated 的 Skill比不断新增更重要。最后分享一个我最近在试的方向把 Skill 的编排逻辑用声明式的 DSL 描述而不是直接写代码。这样演进层可以直接分析和修改编排而不需要人去读代码。目前还在早期效果不好说但方向我觉得是对的——让技能库的结构本身可被程序理解和操作是自演进走向更深层的前提。如果你也在做类似的东西欢迎交流。这个领域变化太快一个人踩坑太慢互相抄作业才是正道。