
1. 从一次翻车说起为什么我要把 Skill 和 Tool 拆开看去年冬天我接了个私活帮一家做跨境电商的朋友搭一套自动处理售后邮件的 Agent。需求听起来不复杂读邮件、判断意图、查订单、生成回复、必要时转人工。我当时的思路很直接——给 Agent 挂一堆 Tool 就完事了。查订单一个 Tool发邮件一个 Tool查物流一个 Tool退款一个 Tool凑了十来个跑起来看着挺热闹。结果上线第三天就出事了。客户投诉说 Agent 把一封我要退货的邮件回复成了您的退款已到账。我翻日志查了半天发现是模型在某个环节把查询订单状态这个 Tool 的返回结果误当成了执行退款的确认信号。两个 Tool 的语义边界太模糊模型在长上下文里串了台。更麻烦的是我想修这个问题得改 Tool 的描述、改 prompt、改调用逻辑改完还得重新测一遍所有场景因为 Tool 之间是耦合的。那次之后我开始认真琢磨一件事Agent 的能力到底该怎么组织把所有能力都塞进 Tool 里是不是从一开始方向就偏了后来接触到 Skill 这个概念尤其是看到 Claude Code、Codex 这类工具里 Skill 的用法我才慢慢理清楚——Skill 和 Tool 根本不是一回事它们解决的是两个层面的问题。这篇就把我这大半年踩坑、试错、重构的经验完整写出来从概念差异讲到技能库的自演进和动态加载尽量把每个为什么都讲透。如果你正在做 Agent 开发或者正准备给自己的 Agent 加能力这篇应该能帮你少走不少弯路。2. Skill 与 Tool 的本质差异不是命名游戏是架构分层2.1 先给结论Tool 是手Skill 是手艺我用一个生活化的类比来解释这两者的区别。你去餐厅吃饭Tool 就像厨房里的工具——菜刀、炒锅、烤箱、打蛋器。每一件工具功能单一、边界清晰菜刀就是切东西的烤箱就是加热的。而Skill 是一道菜的完整做法——红烧肉这个技能包含了选肉、焯水、炒糖色、炖煮、收汁一整套流程过程中会用到菜刀、炒锅、灶台这些工具但技能本身不等于工具。放到 Agent 语境里Tool是原子化的外部能力接口通常对应一次函数调用或一次 API 请求。它的特征是无状态、输入输出明确、可独立测试。比如search_web(query)、read_file(path)、send_email(to, subject, body)。Skill是面向目标的能力封装它描述在什么场景下、按什么步骤、调用哪些 Tool、遵循什么约束、产出什么结果。它是有状态、有流程、有领域知识的。这个区别听起来抽象但落到代码上非常具体。我拿自己重构前后的对比给你看。重构前我把处理退款直接写成一个 Tool# 反例把业务流程塞进 Tool def process_refund(order_id: str, reason: str) - dict: order query_order(order_id) if order[status] ! paid: return {error: 订单状态不允许退款} if order[amount] 500: # 大额需要人工审核 create_ticket(order_id, refund_review) return {status: pending_review} refund call_payment_api(order_id, order[amount]) send_email(order[email], 退款成功, f您的退款 {order[amount]} 元已到账) return {status: success, refund_id: refund[id]}这段代码能跑但问题一大堆。首先业务规则500 元阈值、状态校验硬编码在 Tool 里改规则要改代码、要重新部署。其次这个 Tool 内部调了四个其他能力任何一个出错都很难定位。最关键的是模型根本不知道这个 Tool 内部发生了什么它只能看到输入和输出一旦输出不符合预期模型没有任何调整空间。重构后我把退款处理变成一个 SkillTool 只保留原子能力# 正例Tool 只做原子操作 tools [ query_order, # 查订单 check_order_status, # 查状态 call_payment_api, # 调支付 create_ticket, # 建工单 send_email, # 发邮件 ]而 Skill 用一份声明式的描述来定义流程# skill: refund_processing name: refund_processing description: 处理客户退款请求包含状态校验、金额判断、审核分流 triggers: - 客户要求退款 - 订单退款 steps: - action: query_order input: { order_id: {{context.order_id}} } - action: check_order_status condition: order.status paid on_fail: 回复客户当前订单状态不支持退款 - action: branch condition: order.amount 500 then: - action: create_ticket input: { type: refund_review } - action: send_email input: { template: refund_pending } else: - action: call_payment_api - action: send_email input: { template: refund_success } constraints: - 退款金额不得超过订单实付金额 - 同一订单 24 小时内只能发起一次退款你看同样的业务拆开之后清晰多了。Tool 负责能做什么Skill 负责该怎么做。这个分层带来的好处我在后面几节会逐个展开。2.2 为什么这个区分在长上下文里特别重要很多人会问我直接把流程写进 prompt 不就行了何必搞个 Skill 层这个问题我一开始也纠结过。答案是prompt 里的流程是软约束Skill 是硬约束。在短对话里prompt 写清楚流程确实够用。但 Agent 一旦跑长任务上下文动辄几万 token模型对 prompt 里中段内容的注意力会明显衰减——这就是业内常说的lost in the middle现象。你把退款流程写在系统 prompt 的第 3000 字位置跑到第 20 轮对话时模型很可能已经忘了那个 500 元阈值。Skill 的价值在于它把流程从常驻上下文变成了按需加载。模型不需要一直记着退款流程只有当它判断当前任务需要退款技能时才把这个 Skill 的描述加载进来。这样既省 token又保证了流程约束在需要时是新鲜的、高权重的。我实测过一个对比同样一个包含 8 个步骤的复杂任务把流程写死在 system prompt 里任务成功率大概 62%改成 Skill 动态加载后成功率提到 89%。差距主要来自长任务后半段——写死的那版模型经常漏掉中间步骤。2.3 一张表看清两者的边界维度ToolSkill粒度原子操作面向目标的流程状态无状态有状态、有上下文复用方式直接调用按场景加载变更成本改代码、重部署改描述、热更新对模型的作用扩展手扩展手艺典型例子查数据库、发请求处理退款、写周报测试方式单元测试场景回归测试这张表是我自己总结的不一定权威但基本覆盖了日常开发里最需要区分的几个点。记住一句话Tool 是能力Skill 是能力的使用说明书。3. 技能库的自演进让 Agent 自己长出新手艺3.1 什么是自演进为什么需要它技能库的自演进说白了就是Agent 在使用过程中能自己沉淀新 Skill、优化旧 Skill。这个概念听起来有点玄但逻辑很朴素你不可能提前把所有场景都想到与其等用户反馈再手动加 Skill不如让 Agent 在遇到新场景时把成功的处理路径固化下来。我举个真实例子。我那个跨境电商 Agent 上线后遇到一类我没预料到的场景客户问我的包裹为什么还没到是不是丢了。这既不是退款也不是查物流那么简单它需要先查物流、判断是否超时、如果超时则安抚并给补偿券、如果没超时则解释预计到达时间。我一开始没这个 SkillAgent 处理得很乱。后来我加了一个机制当 Agent 用一组 Tool 调用成功解决了一个之前没见过的任务类型时把这条调用链抽象成一个候选 Skill存进技能库。下次遇到类似任务直接加载这个 Skill。这就是最基础的自演进。3.2 自演进的三个层次我把自演进分成三个层次从易到难第一层技能沉淀。把成功的 Tool 调用序列抽象成 Skill。这是最基础的实现难度低收益明显。核心是判断什么算成功——通常用任务完成信号、用户满意度、无报错这三个指标综合判断。第二层技能优化。已有 Skill 在执行中如果频繁失败或效率低自动调整步骤顺序、替换 Tool、修改约束。比如某个 Skill 里先查 A 再查 B但统计发现 80% 的情况 B 的结果能直接推出 A那就把顺序反过来省一次调用。第三层技能组合。把多个小 Skill 组合成大 Skill或者把一个大 Skill 拆成可复用的子 Skill。这一层最难因为涉及技能的抽象和泛化容易过度拟合。我目前的生产环境只做到第一层加部分第二层第三层还在实验。下面重点讲前两层怎么落地。3.3 技能沉淀的具体实现技能沉淀的核心是轨迹抽象。Agent 每完成一个任务都会留下一条执行轨迹trace包含用户输入、模型思考、Tool 调用序列、每步结果、最终输出。我们要做的是从轨迹里提取出可复用的模式。def abstract_skill_from_trace(trace): # 1. 过滤只处理成功且非平凡的轨迹 if not trace.success or len(trace.tool_calls) 2: return None # 2. 提取调用序列 sequence [(call.name, call.args_schema) for call in trace.tool_calls] # 3. 检查是否与已有 Skill 重复 if is_duplicate(sequence, skill_library): return None # 4. 生成 Skill 描述 skill { name: generate_name(trace.intent), description: summarize_intent(trace.user_input), steps: sequence, constraints: extract_constraints(trace), source_trace: trace.id, confidence: compute_confidence(trace), } return skill这里有几个坑我要提醒。第一别什么轨迹都沉淀。我一开始贪多把所有成功轨迹都转成 Skill结果技能库迅速膨胀到几百个加载时检索慢、命中率低反而拖累了性能。后来我加了置信度阈值只沉淀那些多次出现且成功率高的模式。第二Skill 描述要写清楚触发条件。我早期生成的 Skill 描述太笼统比如处理客户问题结果模型一遇到客户问题就加载它但它其实只适用于物流超时场景。后来我强制要求描述里包含具体的触发关键词和排除条件。第三沉淀的 Skill 要标记来源和版本。自演进的 Skill 和人工写的 Skill 质量不一样得能区分。我一般给自动生成的 Skill 打个auto_generated: true标记并且记录它来自哪条轨迹方便回溯。3.4 技能优化的触发与执行技能优化比沉淀更微妙因为你要改的是已经在用的东西改错了影响面大。我的做法是先影子运行再灰度切换。具体流程是这样当某个 Skill 的失败率超过阈值我设的是 15%系统自动生成一个优化候选版本但不直接替换而是让它在后台影子运行——同样的输入新旧两个版本都跑一遍对比结果。跑够一定样本量我设的是 50 次如果新版本成功率明显更高才灰度切换 10% 的流量观察一周没问题再全量。def optimize_skill(skill, recent_traces): failures [t for t in recent_traces if not t.success] if len(failures) / len(recent_traces) 0.15: return None # 分析失败模式 failure_patterns cluster_failures(failures) # 针对每种模式生成优化建议 candidates [] for pattern in failure_patterns: if pattern.type wrong_order: candidates.append(reorder_steps(skill, pattern)) elif pattern.type missing_constraint: candidates.append(add_constraint(skill, pattern)) elif pattern.type tool_mismatch: candidates.append(swap_tool(skill, pattern)) return candidates这里有个经验优化建议不要一次改太多。我试过让系统一次改三个地方结果新版本反而更差因为无法归因是哪个改动导致的。后来改成每次只改一处效果稳定多了。3.5 自演进的边界什么时候该停下来自演进不是越自动越好。我踩过最大的坑是让系统无限制地自动生成 Skill结果技能库变成了一个垃圾场——大量低质量、高度重叠、甚至互相矛盾的 Skill 堆在里面检索时噪声极大。后来我定了三条硬规则技能库总量上限。超过上限时触发淘汰机制按使用频率、成功率、最近使用时间综合打分淘汰末尾的。人工审核闸门。自动生成的 Skill 先进入候选区只有被实际调用并成功 N 次后才转正进主库。冲突检测。新 Skill 如果和已有 Skill 的触发条件高度重叠但流程不同必须人工介入不能自动合并。这三条规则加上之后技能库从失控的 400 多个稳定在 80 个左右检索命中率反而提升了。4. 动态加载机制让技能库真正活起来4.1 为什么不能一次性全加载技能库有了下一个问题是怎么让 Agent 用上最笨的办法是把所有 Skill 的描述都塞进 system prompt。我试过80 个 Skill 的描述加起来大概 4 万 token还没开始干活上下文就快满了。而且模型面对 80 个选项选择准确率会明显下降——这跟人一样菜单太长反而不知道点啥。所以必须动态加载根据当前任务只把相关的几个 Skill 加载进来。这背后是一个检索 排序 注入的流程。4.2 动态加载的三段式流程我把动态加载拆成三步每一步都有讲究。第一步意图识别。从用户输入里提取任务意图。这一步可以用小模型做也可以用规则匹配。我一般用轻量级的分类模型把输入映射到技能库的标签空间。注意这一步不要追求精确匹配宁可多召回几个候选后面再筛。第二步技能检索。用意图向量去技能库里做相似度检索。这里的关键是技能描述的质量。描述写得好检索准描述写得烂检索出来的全是噪声。我要求每个 Skill 的描述必须包含适用场景、触发关键词、不适用场景、典型输入示例。这四样齐了检索准确率能到 90% 以上。第三步上下文注入。把检索到的 Skill 描述注入到当前对话上下文里。这里有个技巧注入位置很关键。我实测下来把 Skill 描述放在用户消息之后、模型回复之前效果最好。放在 system prompt 里效果反而差因为位置太靠前模型注意力不够。def dynamic_load_skills(user_input, skill_library, top_k3): # 1. 意图识别 intent classify_intent(user_input) # 2. 检索 candidates skill_library.search(intent, top_ktop_k * 2) # 3. 重排序用更精细的模型 ranked rerank(candidates, user_input) # 4. 取 top_k注入上下文 selected ranked[:top_k] context_injection format_skills_for_context(selected) return context_injection4.3 检索策略向量、关键词还是混合技能检索用什么方法我试过三种纯向量检索把 Skill 描述和用户输入都转成向量算余弦相似度。优点是语义泛化好用户换个说法也能命中。缺点是容易召回语义相近但实际不相关的 Skill。纯关键词检索用 BM25 之类的算法。优点是精确缺点是用户换个词就失效。混合检索两者结合加权打分。这是我目前用的方案实测召回率和准确率都最好。混合检索的权重怎么定我的经验是向量 0.6、关键词 0.4。因为 Skill 描述里通常有明确的关键词比如退款物流关键词能兜住精确匹配向量负责处理用户的口语化表达。这个比例不是绝对的你可以根据自己技能库的特点调。4.4 加载时机什么时候该加载什么时候该卸载动态加载不只是加载还包括卸载。一个 Skill 用完了如果一直留在上下文里会占用 token、干扰后续判断。我的做法是按任务边界卸载当一个任务完成比如退款流程走完就把这个 Skill 从上下文里移除。但这里有个坑有些 Skill 是常驻型的比如安全校验这种每个任务都要用的就不能卸载。所以我给 Skill 加了个persistent标记常驻的始终保留其他的按需加载、用完即卸。还有一个细节加载要预判。如果模型正在执行一个多步任务下一步大概率还需要某个 Skill那就提前加载避免中途加载打断流程。我一般用简单的状态机做预判——当前 Skill 的next_skills字段里列了可能的后续 Skill提前加载。4.5 一个完整的动态加载实例我把上面这些串起来给你看一个完整例子。假设用户输入是我上周买的那个耳机还没到能帮我看看吗。# 1. 意图识别 intent classify_intent(我上周买的那个耳机还没到能帮我看看吗) # 输出: {intent: logistics_inquiry, entities: {product: 耳机, time: 上周}} # 2. 混合检索 candidates hybrid_search(intent, skill_library, top_k6) # 召回: [logistics_tracking, order_query, delivery_delay_handling, # refund_processing, product_info, customer_complaint] # 3. 重排序 ranked rerank(candidates, user_input) # 排序后: [logistics_tracking, delivery_delay_handling, order_query, ...] # 4. 加载 top 3 loaded ranked[:3] # 注入上下文: logistics_tracking, delivery_delay_handling, order_query # 5. 模型执行 # 模型先调 order_query 找到订单再调 logistics_tracking 查物流 # 发现超时触发 delivery_delay_handling 的安抚流程 # 6. 任务完成后卸载 unload_skills([logistics_tracking, delivery_delay_handling, order_query])整个过程模型只看到了 3 个 Skill 的描述而不是全部 80 个。上下文干净选择准确率自然高。5. 实操中踩过的坑与排查技巧5.1 技能库检索不准怎么办这是最高频的问题。我遇到过的表现是用户明明问的是退款检索出来的却是物流 Skill。排查思路分三步先看描述质量。把检索出来的 Skill 描述和用户输入放一起看如果描述里根本没有退款相关的词那就是描述写得太烂得重写。我一般要求描述里必须包含至少 3 个该场景的高频词。再看向量模型。如果描述没问题那就是向量模型对中文语义的捕捉不够。我试过几个开源模型中文场景下差异挺大。建议用中文语料训练过的模型或者直接用大模型做 embedding。最后看检索权重。如果向量和关键词的权重配比不合适也会导致偏差。我一般会做个小实验拿 50 条真实用户输入人工标注应该命中的 Skill然后调权重看准确率找到最优配比。5.2 Skill 之间互相冲突怎么处理冲突有两种触发条件冲突和流程冲突。触发条件冲突是指两个 Skill 都声称自己适用于某个场景。我的处理方式是加优先级。每个 Skill 有个priority字段冲突时高优先级的胜出。优先级怎么定按业务重要性——涉及资金、安全的 Skill 优先级最高。流程冲突是指两个 Skill 的步骤互相矛盾比如一个说先查 A 再查 B另一个说先查 B 再查 A。这种必须人工介入不能自动解决。我的做法是建一个冲突检测机制新 Skill 入库时自动扫描发现冲突就挂起等人工裁决。5.3 动态加载导致上下文抖动这个问题比较隐蔽。表现是模型在同一个任务里一会儿加载这个 Skill一会儿加载那个上下文频繁变化导致模型精神分裂输出前后不一致。根因是加载策略太激进。我早期是每轮对话都重新检索、重新加载结果模型刚适应一个 Skill下一轮又换了。后来改成任务级加载一个任务开始时加载一次任务过程中除非明确需要新 Skill否则不重新加载。这样上下文稳定多了。5.4 常见问题速查表问题现象可能原因排查方向解决思路检索命中率低描述质量差检查描述关键词重写描述补高频词加载后模型不用注入位置不对检查注入位置放到用户消息后上下文超限加载太多 Skill统计 token 占用减少 top_k加卸载技能冲突触发条件重叠检查 priority加优先级人工裁决自演进失控无淘汰机制统计技能库规模加上限和淘汰优化后更差一次改太多对比新旧版本每次只改一处5.5 几个我踩过的独家坑坑一Skill 描述里别写具体参数值。我早期在 Skill 描述里写了退款金额超过 500 元需要审核结果业务规则改成 300 元后Skill 描述没同步模型按 500 判断出了事故。后来我把参数值抽出来放到配置里描述里只说超过阈值需要审核阈值从配置读。坑二别让模型自己决定加载哪个 Skill。我试过让模型在思考时自己说我需要加载 XX Skill结果模型经常判断错要么该加载不加载要么加载一堆没用的。后来改成系统侧检索 模型侧确认模型只负责确认这个 Skill 是否适用不负责检索。坑三Skill 的版本管理要跟上。自演进会不断产生新版本如果不做版本管理出了问题根本不知道是哪个版本导致的。我现在每个 Skill 都有版本号每次变更记录 diff出问题能快速回滚。坑四别忽视 Skill 的测试。Tool 可以单元测试Skill 得做场景回归测试。我建了一个测试集包含 200 个真实场景每次 Skill 变更都跑一遍确保没有回归。这个投入很值帮我拦下了好几次潜在事故。6. 从 Tool 到 Skill 的迁移路径给正在重构的你6.1 什么时候该迁移不是所有项目都需要 Skill 层。我的判断标准是当你的 Tool 数量超过 10 个或者出现多个 Tool 需要按固定顺序调用的场景时就该考虑引入 Skill 了。Tool 少的时候直接调用完全够用硬上 Skill 层反而增加复杂度。6.2 迁移的四个步骤第一步盘点现有 Tool。把所有 Tool 列出来标注每个 Tool 的调用频率、是否常和其他 Tool 组合使用。组合使用的那些就是 Skill 的候选。第二步识别高频流程。从日志里找出高频的 Tool 调用序列这些序列就是天然的 Skill。我一般取 top 20 的序列人工确认后转成 Skill。第三步先并行再切换。新老两套并行跑一段时间对比效果。确认新方案更好后再逐步切换。别一刀切风险太大。第四步建立技能库运营机制。Skill 不是建完就完事得有运营——监控使用情况、定期优化、淘汰低效的。这部分工作得有人负责不能放任自流。6.3 迁移后的收益与代价收益方面我自己的项目迁移后任务成功率从 62% 提到 89%上下文 token 占用降低 40%新场景的响应速度从改代码重部署变成加个 Skill 描述快了一个数量级。代价方面主要是初期投入大。建技能库、写检索逻辑、搭自演进机制前前后后花了我一个多月。而且 Skill 层的调试比 Tool 层难因为涉及模型行为不确定性更高。所以我的建议是小项目别急着上等 Tool 确实管不过来了再迁移。6.4 一个务实的起步方案如果你现在就想试试我给一个最小可行方案先挑 3 个最高频的流程手动写成 Skill别搞自演进。用最简单的关键词检索做动态加载别上向量。跑两周看效果。如果确实比纯 Tool 好再逐步加自演进、加向量检索。这个方案投入小、见效快适合验证方向。我自己就是这么起步的先跑通最小闭环再逐步加复杂度。7. 关于技能库未来的一点个人判断我最近在琢磨一件事技能库会不会成为 Agent 之间共享的公共资产就像 npm 包一样有人写好一个处理退款的 Skill别人直接引用。这个方向我觉得挺有意思但有几个问题没解决Skill 的质量怎么保证、版本冲突怎么处理、安全边界怎么划。我现在还没想清楚但感觉这是接下来一两年会热起来的方向。另一个我比较确定的方向是技能库和记忆系统的融合。现在 Skill 和 Memory 是两套东西Skill 管怎么做Memory 管做过什么。但实际用下来这两者边界挺模糊的——一个高频使用的 Skill某种程度上也是一种记忆。我最近在试把两者统一到一个经验库里用同一套检索机制效果还在观察。最后分享一个小技巧技能库的检索日志一定要留。我每次检索都记录用户输入、召回结果、实际使用、最终效果这些数据是优化技能库的金矿。我靠这些日志发现了不少问题比如某些 Skill 从来没被命中过说明描述有问题某些 Skill 命中后成功率很低说明流程有问题。没有这些日志优化就是盲人摸象。这套东西我还在持续迭代踩坑肯定还会继续。如果你也在做类似的事欢迎交流尤其是自演进那块我总觉得还有更好的做法没找到。