
我们做 Agent 开发的时间一长几乎都会被同一个问题卡住Skill 和 Tool 到底有什么区别很多刚入门的同学把两者混为一谈甚至直接在 Agent 里把几百个 API 封装成“技能”塞进去结果上下文爆炸、指令选择困难、维护成本直线上升。我最近在重构一个内部自动化 Agent 的时候把技能体系彻底换了一套设计逻辑才真正想明白这两者的本质差异以及一个关键结论Tool 是 Agent 的手脚Skill 是 Agent 的肌肉记忆。理解不了这个区别Agent 永远只是个“一次性的玩具”而不是“越用越顺手的老员工”。这篇文章不聊概念空谈我会直接从我实际做的 Agent 技术改造出发拆解 Skill 与 Tool 的底层差异、技能库为什么要“自演进”、以及动态加载机制如何落地。文章会涉及代码、架构、还有不少踩坑实录适合正在做 Agent 开发、或者准备在团队里搭建 Agent 技能体系的朋友。看完之后你应该能直接抄一套可运行的方案回去。1. Skill 与 Tool 的本质差异你缺的不是工具是手艺先说一个最容易被忽略的事实Tool 是函数Skill 是方法论。这个差异如果不掰开揉碎后面所有的架构设计都会走偏。1.1 从调用方式看一次调用 vs 一套流程Tool 的本质是一个可被调用的函数它有明确的输入输出结构。比如你给 Agent 注册一个“查询天气”的 Tool输入是城市名输出是天气 JSONAgent 在需要的时候发起一次函数调用拿到结果后继续对话。整个过程是“一次性、原子化”的。但 Skill 不一样。Skill 往往代表着一套完成目标的流程它内部可能包含多个步骤、多次 Tool 调用、甚至嵌套的子决策。举个例子一个“撰写竞品分析报告”的 Skill它内部可能是这样的流程步骤一调用“网页搜索” Tool 检索竞品公开信息步骤二调用“网页抓取” Tool 提取指定页面核心内容步骤三调用“结构化输出”能力将信息整理成 SWOT 框架步骤四根据用户偏好调整语气和篇幅输出最终报告。你会发现如果只是一个“竞品分析”的 Tool那 Agent 只能做一次“搜索关键词”或者“截图网页”这种单点动作但 Skill 封装的是遇到“竞品分析”这个任务时应该按什么步骤来、每一步用什么工具、中途出错了怎么兜底。所以 Skill 的粒度远大于 Tool它更像一个“带决策逻辑的流程包”。1.2 从能力边界看工具是死的技能会生长Tool 的能力边界在注册的那一刻就定死了——函数签名是什么样它就只能做什么事。但 Skill 是一种“模式复用”它可以针对新场景不断长出变体。我打个比方Tool 是一把固定的螺丝刀只能拧对应规格的螺丝Skill 是一个木工师傅脑子里的“开榫卯”方法你给他不同的木料、不同的榫头形状他都能调整手法完成任务。所以当你问“Skill 为什么不同于 Tool”时最核心的答案其实是Tool 解决的是“Agent 能调用什么”Skill 解决的是“Agent 知道该怎么调用、什么时候调用、调用了之后怎么组织结果”。前者是原子能力后者是组合智能。1.3 用生活类比彻底区分开我在团队分享时经常用一个类比。把 Agent 想象成一个餐厅后厨Tool是厨房里的设备烤箱、灶台、刀具。每一台设备都有固定功能烤箱就是烤箱你指望它切菜是不现实的。Skill是厨师脑子里的菜谱烤鸭的菜谱包含选鸭、腌制、风干、烤制温度、上色手法等一整套经验。菜谱里会用到烤箱、刀具、甚至计时器多个 Tool但菜谱本身不是设备。如果你只给后厨买设备不培训厨师的菜谱那做出来的菜全凭厨师临场发挥但如果你把菜谱沉淀下来每一位新来的厨师都能直接按流程做出合格水平的菜——这就是Agent 技能库的雏形。Agent 开发里很多项目失败就是因为把大量精力花在“买设备”接 Tool上却从来没有“沉淀菜谱”写 Skill。2. 技能库为什么需要“自演进”靠人肉更新是走不远的好如果 Skill 只是“预先写好的菜谱”那它就和一个复杂的 Prompt 没什么太大区别。真正让 Skill 发挥杠杆效应的地方在于技能库能在真实使用中不断自我进化。这里就涉及到“自演进”机制。2.1 手工维护技能库的困局我最早做技能库的时候思路非常简单把高频任务写成固定的 Skill 模板每个 Skill 对应的场景和步骤都是人肉梳理的。用了一段时间之后问题开始暴露线上 Agent 在真实场景里经常会遇到“ Skill 覆盖不到”的长尾情况每次都要回头改模板业务流程一变所有相关 Skill 要手动同步更新改一个流程要连带检查十几个技能老 Skill 之间还有“隐性耦合”比如 A 技能升级了数据获取方式B 技能还在用旧版接口完全靠人去追。本质上业务是动态的而静态技能库是跟不上的。这就像用 Excel 管理一套不断变化的库存清单——库存永远在你改完表格之后才变。要打破这个局面就得让技能库有一部分“自我修正”的能力。2.2 自演进的核心回路失败 → 沉淀 → 生成 → 验证我现在采用的方案核心是一条四段式的演进回路失败捕获Agent 在执行任务的过程中一旦出现连续重试失败、用户明确反馈“这不是我要的”、或者结果质量评分过低系统会把这段“任务上下文 执行轨迹 失败原因”记录下来。注意不是简单的日志而是结构化的事件。沉淀候选把这些失败案例汇聚到“技能待提炼区”。经过一定的相似度聚合比如按任务类型、涉及 Tool、失败模式聚类形成一批候选场景。生成变体或新技能针对候选场景可以人工编写也可以由大模型生成一份新 Skill 草稿。我这里说的是草稿因为它不会直接进入生产环境。验证入库新 Skill 先在隔离环境里用历史案例回放验证通过率达标后再进入技能库。这个过程可以半自动也可以保留一个人工审批的开关。这个回路跑起来之后技能库就不再是一堆静态文件了而是一个越用越贴合实际业务的生命体。需要特别注意自演进不等于完全无人值守尤其是“生成”这一步一定要有验证环节兜底。我见过不少团队让大模型自动生成技能后直接入库结果技能库一周之内就塞满了各种看似合理、实际不能用的垃圾技能。2.3 演进过程中的安全与质量边界自演进听起来很美好如果没有约束机制很容易失控。我在实际落地中设置了三条底线版本回滚机制每一个 Skill 都有版本号演进生成的变体是“新增版本”而不是“覆盖旧版”。一旦新版本在真实任务中表现不佳可以直接切回旧版本保证线上稳定。技能来源分级人工专家编写的核心技能优先级最高自动生成的技能被标记为“候选”必须经过足够次数的验证才能转正用户自定义的技能如果开放了入口权限级最低不能覆盖系统技能。使用配额控制对自动生成的 Skill 设置一个最大数量上限。技能库不是越多越好数量失控会让动态加载的检索准确率急剧下降。这个我后面会讲。有了这三条边界自演进机制才敢放开了跑。否则“进化”就会变成“退化”。3. 动态加载机制不是把所有技能塞进上下文而是按需取用既然技能库会越长大就不可能再把所有 Skill 一股脑塞进 Agent 上下文中。这里要聊的是动态加载机制——它是整个技能体系能否在真实生产环境落地的关键。3.1 为什么不能全量加载资源消耗与决策熵增我先算一笔账。假设一个中等规模的 Agent 技能库里已经有 50 个 Skill每个 Skill 的描述步骤文档平均 1500 token 左右那么全量加载光技能说明就要消耗 75000 token。再加上系统 Prompt、对话历史、工具定义一个请求的上下文轻松突破 10 万 token。这会带来两个直接后果成本失控大模型按 token 计费每次请求都要为这 10 万 token 的“技能百科全书”买单但实际任务可能只用到了其中 2 个技能。决策质量下降几十个 Skill 的描述同时出现在上下文里模型面临极大的“选择噪声”。你在实际操作中会发现技能越多模型越容易选错技能或者被不相关的技能描述干扰这就是“决策熵增”。所以动态加载不是“优化项”而是“必选项”。3.2 动态加载的分层架构元数据常驻技能体按需载入我采用的方案核心思想是把技能的描述信息变薄把技能的完整内容变厚让 Agent 平时只接触“薄层”信息真正要执行时才把“厚层”内容加载进来。这套架构分四层技能注册表Registry每个 Skill 在注册表里只有一条轻量记录包含技能 ID、名称、标签、一句话描述、适用场景关键词。这一层是常驻上下文的核心控制每个技能占用不超过 80 token。技能描述索引Index相当于搜索用的倒排索引用于做召回。它包含更丰富的关键词、触发样例、数据格式特征但这部分不需要直接喂给模型而是供检索模块查询。技能完整体Body真正可执行的技能文件包含完整的步骤指令、工具编排逻辑、代码脚本、异常处理规则。这部分只在技能被选中执行时才加载进上下文。技能附属资源Assets例如模板文件、示例文档、数据字典等按需读取不进上下文直接作为执行时的外部资源。你可以在自己的项目里复现这套结构。我给的参考实现是这样的skills/ ├── registry.json # 技能注册表轻量元数据 ├── index/ # 技能检索索引 │ ├── keyword_index.jsonl │ └── embedding_index.npy ├── skill_competitive_analysis/ │ ├── skill.json # 技能描述元数据 │ ├── SKILL.md # 完整技能体Markdown 指令 │ ├── scripts/ # 可执行脚本 │ └── assets/ # 模板、数据字典等附属资源 ├── skill_data_report/ │ ├── skill.json │ ├── SKILL.md │ └── assets/ └── ...动态加载的价值在于Agent 在“选择技能”阶段只阅读注册表里的 80 token 摘要而不是阅读每个技能的完整 1500 token 文档。通过注册表做第一轮粗筛锁定 2~3 个候选技能后再把这些技能完整体加载进上下文。这个“粗筛 精读”的路径很像搜索引擎的“召回 精排”。3.3 检索与加载的执行流程我直接把一套可以照抄的加载流程写出来你可以在代码里实现第一步技能预筛选基于规则或向量检索。把当前用户请求进行关键词抽取再利用 embedding 向量在技能索引里做相似度检索召回 Top 5~10 个候选技能。这一阶段的目的不是精确判断而是“千万别漏掉正确技能”。如果规则检索和向量检索的结果有交集优先保留交集中的技能。第二步技能精排让大模型二选一或多选一。把 Top 5~10 个候选技能的注册表描述轻量交给大模型让它根据当前对话上下文选出最终要用的 1~3 个技能。注意这里给模型的依然是摘要而非完整文档否则精排阶段就失去了意义。第三步技能体加载。选中技能后把对应技能的完整 SKILL.md 和必要的脚本文件注入系统 Prompt。这一步才真正增加 token 消耗但因为只加载 1~3 个技能整体开销可控。第四步执行与回收。任务执行完后将技能体从上下文清理或者通过上下文截断机制自然淘汰避免一个任务结束后还残留上一个任务的技能指令造成污染。3.4 一套可参考的代码骨架下面是核心调度器的 Python 伪代码注释部分直接对应上面的执行流程import json from typing import List class SkillManager: def __init__(self, registry_path: str, skills_dir: str): self.registry json.load(open(registry_path)) # 轻量注册表 self.skills_dir skills_dir def retrieve_candidates(self, user_request: str, top_k: int 8) - List[str]: 第一步召回候选技能按关键词与向量相似度粗筛 keywords self.extract_keywords(user_request) candidates [] for skill in self.registry[skills]: score self.score_by_keywords(skill, keywords) candidates.append((skill[id], score)) # 这里省略向量检索可以接入 embedding candidates.sort(keylambda x: x[1], reverseTrue) return [c[0] for c in candidates[:top_k]]def select_skills(self, candidates: List[str], dialog_context: str) - List[str]: 第二步把候选技能的轻量描述交给大模型精排返回最终选中的技能ID descriptions [self.get_summary(s) for s in candidates] prompt self.build_selection_prompt(descriptions, dialog_context) response self.llm_chat(prompt) return self.parse_selected_skills(response) def load_skill(self, skill_id: str) - str: 第三步加载完整技能体返回注入上下文的内容 meta self.registry[skill_map][skill_id] with open(f{self.skills_dir}/{skill_id}/SKILL.md) as f: skill_body f.read() # 也可以在这里把脚本路径、所需工具清单一并打包 return f[技能名称]{meta[name]}\n[技能说明]{skill_body}提示精排环节的 Prompt 设计非常关键。不要问大模型“请选出合适的技能”而要给出明确的判断标准当前任务的目标、候选技能的适用场景、哪个技能最贴合。你可以在实践里逐步补充候选技能的负面排除条件。3.5 动态加载与上下文管理的配合动态加载还有一个容易被忽略的工程细节加载技能体的顺序会直接影响模型理解。我建议把技能体放在“历史对话”和“当前用户请求”之间形成一个“背景知识”区段。如果技能体放在最后模型执行时容易忽略如果放在对话历史之前又可能干扰对上下文的延续性理解。另外我强烈推荐在技能描述里写明“该技能不适用于哪些场景”。负例的作用在精排阶段比正例还要大它能让模型快速排除明显不适合的技能大大减少错误选择。比如一个“数据分析报告生成”的技能明确写上“本技能不做实时数据抓取只处理已提供的数据表格”就能避免模型拿它去当爬虫工具。4. 实操中的核心实现与避坑经验讲完架构和代码骨架这一节重点说我实际操作中发现的问题和对应解法这些都是文档里不会写的。4.1 技能体的组织方式把“指令”和“代码”分离我在第一批写 Skill 的时候犯过一个错误把大段的指令、代码模板、提示词全写在一个 SKILL.md 文件里结果技能一长模型识别和执行都开始出错。后来我把技能体拆成了三个部分指令区Instruction用 Markdown 写清楚这个技能的目标、步骤、输出格式、坑点。脚本区Scripts可执行的实际代码Python、Shell、SQL 等存放在同目录下的 scripts 文件夹中。知识区Context可选的数据字典、参考示例、规则文档按需注入不作为常驻内容。这样拆的好处是指令区是给模型看的“思维过程”脚本区是给模型调用的“做事证据”知识区是给模型参考的“资料库”。三者分开之后技能的执行稳定性和可调试性都有明显提升。4.2 技能定义里的元数据 Schema别小看这几个字段技能注册表里的元数据看起来只是“描述文字”但它的 Schema 设计会直接决定动态加载的效果。我的建议是最简 Schema 如下{ id: skill_competitive_analysis, name: 竞品分析洞察生成, version: 3.1.0, tags: [市场调研, SWOT, 竞品], apply_scenes: [需要对比多个同类产品时, 用户要求输出竞品优劣势], not_apply_scenes: [没有明确竞品名称时, 需要实时抓取竞品价格时], token_cost: 800, authorized_tools: [web_search, web_fetch, structured_output] }其中not_apply_scenes字段在精排阶段作用极大。没有这个字段模型会把“要查竞品价格”的任务匹配给竞品分析技能执行到一半才发现技能不抓取价格数据。有了负例信息之后错误匹配率几乎可以下降一半。注意token_cost 字段是我额外加进去的“预算标注”。当多个候选技能评分接近时模型可以优先选择消耗更小的技能。这个字段虽然没有直接优化效果但在长期运行中能帮你控制成本和延迟。4.3 动态加载的索引更新写入与删除的时机技能库自演进之后注册表和索引的更新就不能再做“全量重建”了。我在线上使用的是“双写双删”策略技能入库时先写新文件再更新注册表如果注册表更新失败则回滚文件写入。技能下架时先标记禁用再延迟删除索引保留一段时间以便回滚。索引更新使用异步任务保证 Agent 在线服务不会被重建索引阻塞。我见过很多团队把索引重建放到请求链路里结果每次技能更新都会出现几秒钟的查询超时非常影响体验。4.4 常见问题速查与排查实录这里整理一下我实际遇到的几个典型问题附上排查思路问题表现根因分析解决方案Agent 总是选错技能注册表摘要过长模型被次要信息干扰压缩每个技能摘要到 100 token 内强化关键词技能加载后执行仍失败技能体里步骤描述过于模糊把“步骤描述”改写为“决策条件操作动作”结构自演进产生重复技能沉淀阶段缺少相似度合并在生成新技能前与已有技能做 embedding 相似度阈值判断技能库越来越大检索越来越慢缺少淘汰机制定期清理低使用率技能归档到冷存储上下文容纳不了完整技能体单个技能体超过模型的上下文预算拆分技能为多阶段执行每个阶段只加载部分内容我在一个长期运行的 Agent 项目里通过这套排查方法把技能体的平均执行成功率从 62% 拉到了 88% 以上。最关键的突破点其实不是“模型变聪明”而是技能文档变得结构化、检索变得精准、加载变得克制。4.5 自演进里的人工确认逻辑过犹不及最后聊一个非常容易执行偏的问题。自演进机制设计得好确实能大幅降低维护成本但你千万别把它变成“全自动流水线”。我在项目里保留了“候选技能 → 项目经理确认 → 灰度上线 → 全量发布”的四段式确认环节尤其是自动生成的技能必须跑 3 条以上历史案例回放验证每两周人工抽查一次技能库的“技能血缘图”看有没有出现进化链过深、维护成本过高的技能家族一旦发现某条技能演化路径的失败率持续偏高立即冻结该链路回退到上一个稳定版本。我在实际使用中的体会是Skill 与 Tool 的界限理清之后很多架构决策会突然变得简单。它不只是概念辨析而是直接影响你该把优化精力放在哪里——Tool 层面你要关注的是接口稳定性Skill 层面你要关注的是过程可复现性和上下文效率。动态加载和自演进则是一体两面没有自演进动态加载只是在维护一堆静态模板没有动态加载自演进只会让技能库变成一锅粥。把这两个机制配合好你的 Agent 才能真正从“能用”走向“越用越强”。这套方案里的细节不少如果你正在做自己的 Agent 技能库建议先找一个高频任务从最小闭环跑起来再逐步叠加演进回路。