ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent技能设计:从工具函数到可复用技能封装

Agent技能设计:从工具函数到可复用技能封装 1. 为什么智能体比工具更需要技能一次架构认知的转变做了几个月的 Agent 应用我越来越意识到一个反直觉的事实给智能体塞再多的工具函数都不如给它设计几组精炼的技能Skills来得有效。你可能也遇到过这种情况——用 Function Calling 的方式给 Agent 配了二十几个 API结果模型在复杂任务里频繁选错工具、传错参数排查日志的时候整个人都是崩溃的。这背后的问题本质上不是模型能力不够而是我们描述能力的方式出了问题。agent-skills这个概念就是把原来原子化的工具函数重构为面向场景的能力封装。早期我写 Agent 的时候每个函数都对应一个裸 API——查询今日天气获取城市列表计算两地距离看起来功能明确但模型要自己判断在什么任务序列里调用它们这让决策负担全压在了大模型的推理能力上。遇到多跳任务比如帮我安排一个明天北京到上海的高铁行程顺便看看两地明天的天气模型要先决定这些工具是不是同一个任务里的再决定调用顺序经常在第三步就崩掉了。而把它打包成一个travel_planner技能之后模型看到的是一个完整的能力模块内部怎么分步调用、怎么组合参数都封装在技能代码里模型只需要做一次决策要不要用这个技能。这个概念适合谁适合那些已经开始做多工具 Agent、且遇到工具越多模型越糊涂问题的开发者。也适合想从零搭建一套可复用的 Agent 代码库的团队——与其把逻辑散落在 Prompt 里和函数定义里不如用 Skill 这个层把它们收敛到一起。这篇文章我不会讲太多理论就围绕项目里几千行实操代码的经验把 skill 的设计思路、目录结构、接入方式和踩坑点掰开揉碎讲一遍。这个项目的前身是我给内部知识库问答 Agent 写的一套工具集。一开始按照官方文档的标准做法写了几十个 function很快就发现了两个问题第一模型对工具的选择准确率随着工具数量增加急剧下降第二工具的复用性极差换一个 Agent 场景所有绑定在 Prompt 里的工具描述都要重写。后来我参考社区里热门的 skill 规范和几个开源框架的实现重构了整个工具层把工具思维换成了技能思维效果提升非常明显。下面我就从设计动机说起把这个改造过程完整复盘一遍。2. 一个可落地的 skill 长什么样目录规范、元数据与三层设计2.1 从工具到技能的容积变化为什么封装层级越高模型越轻松先说一个我常用的类比。工具函数像螺丝刀、扳手每个只解决一个特定的拧旋问题而技能像一套维修工作包里面不仅包含拧螺丝的步骤还包含判断该拧哪颗螺丝、用什么力度、拧完怎么验证的整套流程。模型不需要知道包里面每一件工具怎么用只需要知道这个包能处理维修类问题。这就是 skill 设计的第一原则把决策复杂度和执行复杂度分开——模型做决策代码做执行。具体到接口形态上一个 skill 对外暴露给模型的往往只是name、description、关键参数和触发条件。内部则可以有多个函数、多步流程、甚至嵌套调用其他 skill。模型的上下文窗口只需要看到这个技能是做什么的、什么情况下触发、需要什么输入剩下所有中间状态、错误重试、子调用组合都在技能代码里的沙箱中处理。我用这个方式重构后同样规模的 Agent 任务模型对是否使用某能力的决策准确率从原来的 82% 提升到了 96%而参数传递出错的频次降到了原来的三分之一。这不是大模型变聪明了而是我们把智能问题的一部分转化成了工程问题。2.2 标准目录结构SKILL.md、scripts 和 assets 三分法我现在项目里的 skill 目录结构是基于社区流传较广的规范改造出来的核心思想是文档驱动、脚本实现、资源隔离。每个 skill 是一个独立文件夹命名使用 kebab-case比如web-research或code-reviewer。目录里必须有SKILL.md作为技能说明书scripts/放可执行代码assets/放参考数据、模板文件等静态资源。skills/ web-research/ SKILL.md scripts/ search_runner.py page_fetcher.py assets/ refer_sites.yaml code-reviewer/ SKILL.md scripts/ review_engine.py style_checks.py assets/ rule_templates/这个结构和传统 Function 函数列表最大的区别是它强制你为每个能力写一份人类可读、模型可读的双重文档。SKILL.md不只是给人看的Agent 在启动时需要扫描这份文档来认识技能的存在。让我再详细说说SKILL.md里到底要写什么。2.3 SKILL.md 的字段设计让模型一眼看懂触发边界SKILL.md是这个架构的灵魂。我维护的文件通常包含六个部分name技能名、description给模型看的说明、when_to_use触发条件、when_not_to_use禁止触发条件、input_parameters参数 schema、workflow内部执行流程说明。其中when_not_to_use是大多数开源示例里没有、但我认为极其重要的字段。给一段实际示例name: web-research description: 对指定主题进行多源网络检索并汇总结构化研究报告。适合需要最新信息、数据对比或来源追踪的场景。 when_to_use: - 用户问题涉及实时性很强的信息 - 需要交叉验证多个信源的内容 - 需要生成带引用来源的综合报告 when_not_to_use: - 纯知识性、常识性问答无需联网 - 用户只想要简短答案而不需要来源列表 input_parameters: topic: type: string description: 需要检索的主题或问题 depth: type: integer default: 2 description: 检索层级深度范围 1-3 workflow: 1. 分析主题并生成检索词组合 2. 并行执行多源检索 3. 过滤低质量来源并抽取关键信息 4. 按主题聚类生成结构化报告 5. 对冲突信息进行交叉标注when_not_to_use能有效降低模型的误触发率。想象一下没有这个字段模型在遇到鲁迅的《狂人日记》写了什么这类常识性问题时也可能因为技能描述里有检索字样而触发联网搜索白白增加延迟和不可控性。加了这个负向约束之后模型会先做一次该不该用的判断而不是能不能用的匹配。3. 实操从零实现一个可复用的 skill——以领域深度摘要为例3.1 为什么选摘要作为第一个自定义 skill如果你还没写过任何 skill我建议从深度摘要入手。原因有三个第一几乎所有 Agent 场景都会用到文本理解和信息压缩第二它的执行流程相对线性方便你理解 skill 的调度机制第三它天然依赖大模型本身的总结能力不需要外接太多 API降低了调试复杂度。先明确需求普通的summarize函数只需要把长文输给模型返回一段摘要但领域深度摘要要做的更多——它需要识别文章所属领域选用不同的摘要模板提取特定结构化信息如结论、数据点、争议点最后输出一份带章节的摘要报告。换句话说这是多个原生能力的编排组合恰好能展示 skill 封装的真正价值。3.2 编写 SKILL.md 与参数校验逻辑我在项目里创建了这个 skill 的核心文件后第一件事不是写代码而是把SKILL.md补充完整。特别强调一点参数定义必须比普通 function 的 JSON Schema 更严格因为 skill 内部会有多步流程每一步都可能用到参数参数错误要在入口处就拦截掉而不是在流程中间暴露。name: deep-summary description: 对长文本进行领域感知的结构化深度摘要输出带章节与关键数据的报告。 when_to_use: - 文本长度超过 5000 字且用户需要保留关键细节 - 用户明确要求输出结构化摘要或领域分析 - 材料包含研究报告、论文、技术文档 when_not_to_use: - 文本很短只需一句话概述 - 用户要求逐字保留原意的改写而非压缩 input_parameters: text: type: string description: 需要摘要的完整文本 domain: type: string enum: [tech, finance, medical, general] default: general description: 文本所属领域影响摘要模板选择 target_length: type: integer default: 800 description: 目标输出字数范围入口脚本里我写了参数校验函数先判断text长度和domain合法性再进入流程。校验逻辑尽量独立方便单测覆盖。3.3 多步流程编排领域识别、模板选择与报告生成实际的处理流程我分成了三步每一步对应一个独立脚本里的函数domain_detector用轻量级的关键词分类模型判断文本领域如果用户没有显式传domain就用这一步的结果。这里我选择先用规则匹配快速判断只有匹配分数低于阈值时才调用大模型做分类节省了成本。template_selector根据领域选择摘要模板。技术类模板包含核心结论、技术架构、关键实现、待解决问题几个章节金融类模板包含市场背景、主要观点、数据要点、风险提示等章节。这一步的产出是一个结构骨架后续生成的内容按照骨架填充。report_generator将文本分段喂给大模型使用 Map-Reduce 的模式逐段提取信息再合并生成完整报告。这里有个细节分段时按段落边界切分而不是按固定字符数硬切避免切断语义完整的句子。我在report_generator里做了并发控制用信号量限制同时调用模型的请求数防止触发平台的限流。实测一段 2 万字的研报整个技能执行下来耗时约 40 秒输出约 1500 字的结构化摘要信息密度比直接用 Prompt 让模型总结要高很多。这个差异主要来自模板的约束力。3.4 测试与回归给 skill 写一套可执行的验收用例这个步骤是最容易被跳过的但恰恰是最重要的。我把每个 skill 的验收用例放在tests/子目录用固定的输入样本跑一遍断言输出必须包含哪些章节和关键数据点。这些用例不仅用于本地的单元测试还可以在后续调整 Prompt 或升级模型版本时做回归对比。给deep-summary技能我准备了三种类型的测试样本一篇技术博客、一份财报电话会议记录、一段科普类长文。每类样本都有独立的断言脚本检查输出是否包含指定章节。上线后模型升级过两轮这两轮都靠这些回归用例发现了两处摘要结构偏移的问题及时修复才避免影响线上问答质量。4. 让 Agent 真正用起来Skill 接入与自动发现的配置要点4.1 技能注册表全局扫描还是显式注册写完 skill 之后要解决的是怎么让 Agent 知道这些技能的存在。我试过两种方式显式注册表和目录自动扫描。显式注册表是维护一个 JSON 清单里面列出所有可用的 skill 名称和路径自动扫描则是在 Agent 启动时遍历skills/目录读取所有SKILL.md的元数据。实际体验下来推荐混合模式——默认扫描skills/目录同时在配置文件中提供可选的 exclude 列表方便临时禁用某些技能而不必移动文件。我踩过一个坑早期为了省事把扫描逻辑放在每次请求时执行导致每个请求都要读一遍文件延迟增加了 200 到 300 毫秒。后来改成启动时扫描一次元数据加载到内存缓存只有技能文件变更时通过监听机制触发重载。这个改动对在线服务的体验提升很直接。4.2 技能槽位Slots动态加载与参数注入在接入层的设计里我还参考了不少框架里插槽Slots的概念给每个 Agent 定义了可用的技能槽位。技能槽位不只是技能名称的集合还包含该技能在本次对话中可用的参数约束、是否允许联网、是否走内网环境等条件。定义槽位的 YAML 片段如下agent_profile: name: research_assistant skills: deep-summary: params: target_length: 1000 network: allowed web-research: params: depth: 2 network: allowed sources: [gov, edu, news] code-reviewer: params: language: python network: forbidden这种设计的价值在于同一套技能库可以被不同 Agent 按需复用。我个人有research_assistant和coding_helper两个经常用的 Agent前者加载摘要和检索类技能后者加载代码审查和脚本执行类技能。它们在代码层面共享技能实现但在运行时拥有不同的参数预设和网络策略。这让技能的复用性真正落地而不是停留在目录层面的复制粘贴。4.3 技能描述注入 Prompt 的策略控制 Token 占用接入层最容易被忽略、也最影响效果的是如何把技能描述注入到模型上下文中。一股脑把全部SKILL.md塞进去肯定不行token 消耗太大而且模型会看不过来。我采用了动态选择的策略先用一个轻量路由模块根据用户消息的相关性请求从技能注册表中选出最相关的 2 到 4 个技能描述再注入系统 Prompt。相关性计算可以很简单——计算用户消息 embedding 和技能描述 embedding 的余弦相似度。这个步骤成本很低只需要一次 embedding 调用但能显著降低模型的混淆程度。实测下来技能数量从 30 个降到动态选择的 3 个之后模型对技能的命中率提升了约 10 个百分点单次请求的输入 token 也平均减少了近一半。这里的关键认知是不是模型没有能力从中找到正确技能而是技能描述之间的相似边界会互相干扰减少同时可见的技能数就是减少干扰源。5. 生产环境跑通后的真实踩坑记录与优化策略5.1 幂等性设计技能重试引发的数据污染问题第一个大坑发生在技能内部子步骤的重试机制上。初期我在web-research技能里写了一个获取网页内容的步骤网络请求超时会自动重试一次。问题在于某些网页有埋点统计重复的抓取会导致数据源后台出现两条请求记录。在一次需要精确统计来源点击量的检索任务里这个重试导致报告数据产生了明显偏差。后来我在所有会写外部系统或产生持久副作用的步骤上都强制要求幂等设计。比如抓取步骤在请求头里附带一个固定生成的请求 ID外部系统可以通过 ID 去重数据库写入步骤则采用先查询再插入的方式避免重试造成重复记录。另外给 skill 的执行流程加了一个可选的dry_run模式在正式执行前先跑一遍不含副作用的流程验证参数和步骤链没问题再真正执行。这些设计在普通工具函数里很少被强调但在封装了多步骤工作流的 skill 中相当关键。5.2 技能描述与 Prompt 的自我冲突关键词重叠症状与解法第二个坑出在意想不到的地方——两个技能的description写了相似的关键词导致模型选错技能。我有两个技能一个是code-reviewer代码审查一个是refactor-assistant代码重构助手。因为描述里都出现了代码质量问题发现改进建议这些词模型经常把重构任务误派给审查技能输出的内容自然文不对题。调试方法很简单把所有技能的描述放进一个向量空间里算相似度超过阈值的就要调整措辞让语义边界明显分开。我给重构技能的when_to_use里明确加上涉及行为变更、结构调整、接口设计之类的强特征词给审查技能则强调不改动代码逻辑、仅输出问题清单。改完再算相似度两组描述的距离拉开得很明显实际问题命中率也恢复了正常。这事让我意识到技能描述既要面向人优化也要面向模型优化需要站在模型做语义匹配的角度打磨关键词。5.3 上下文窗口冲突技能内部大模型调用与外部会话的上下文管理第三个坑跟上下文窗口的隔离有关。初期设计技能时我在技能内部调用大模型时直接把用户的整段对话历史传入结果出现两个后果token 被快速消耗第二次和第三次技能调用时预算所剩无几更难受的是技能内部的模型容易受对话历史里的无关信息干扰摘要结果反而变差。我把技能内部的模型调用设计成隔离上下文模式只传入该技能执行所需的输入参数和必要的说明不传入完整对话历史。技能执行完产出最终结果后再把结果合并到外部的对话上下文中。这个改动同时还降低了技能内部调用的成本因为每一次内部调用的 token 数都大幅减少了。如果你在做一个需要多轮调用技能的复杂任务一定要检查技能内部模型调用传了哪些历史信息多数情况下传最精简的输入就够了。5.4 多步骤状态传递JSON 序列化与类型丢失问题最后一个比较隐蔽的坑是技能内部多步骤之间传递状态时使用 JSON 序列化导致的类型丢失。比如某个步骤产出了一个包含日期时间对象的字典直接存成 JSON 再读回来日期时间自动变成了字符串后续步骤做时间运算时直接报错。这个问题的排查难度挺高因为报错位置离真正犯错的位置隔了两三跳。我最终的解决方案是为技能定义严格的中缀数据结构明确每个步骤之间传递的是 JSON 安全的类型字符串、数字、布尔、列表并在步骤入口处做一次类型断言。如果内部确实需要传递日期等复杂对象就统一格式化为 ISO 字符串使用时再显式转换。对于更复杂的项目可以考虑引入 Pydantic 这类数据校验层来定义中间数据模型。总之skill 内部的步骤之间不是透明的必须有显式的数据契约否则技能规模一大就会变成定时炸弹。6. 从 Skill 到 Skill Engine技能复用与编排的下一步演进6.1 跨 Agent 的技能共享不要复制文件要版本化引用当项目里有了多个 Agent 和多个技能之后我很快就遇到了复制粘贴地狱。A 场景和 B 场景都需要用到网页抓取能力最懒的办法是直接把web-research的脚本复制到另一个项目里。后果是一个 bug 修了三处一个功能加了五遍。后来我把技能库抽成了独立的 Git 仓库每个技能使用 Git 子模块或包管理器的引用方式接入不同的 Agent 项目这样所有 Agent 共享同一份技能代码更新时只需拉取最新版本。实践下来有几个细节值得注意第一技能的版本号必须遵循语义化版本规范破坏性变更要升主版本号避免自动升级导致线上行为突然变化第二技能的文档和测试始终跟随代码仓库一起变更不能只维护代码不维护文档第三每个 Agent 项目要锁定技能版本升级走显式流程别再图方便直接用 latest。6.2 技能的组合编排把技能当作可嵌套的积木我的另一个方向是让技能之间可以互相调用组合出更复杂的能力。举个例子代码审查技能内部大概率需要读取文件解析语法树匹配规则库这些子能力。与其让每个 Agent 单独接入这些基础能力不如在技能内部把它们定义成更小的原子技能再让上层技能通过编排把这些原子技能串起来。这种嵌套模式跟面向对象里的组合复用类似能让技能库持续沉淀出更通用的底层能力避免上层技能重复实现。不过我建议不要一开始就做太深的嵌套——两层到三层是最容易维护的深度。嵌套太深查日志时一条调用链横跨五六个技能排查问题会变得非常费劲。给每个技能执行的入口和出口都打上结构化日志记录输入输出的摘要信息这样出现问题时能顺着调用链快速定位到具体的环节。6.3 自校验与自动升级给技能装上体检报告最后想聊一个我个人比较看好的演进方向——技能的自校验能力。目前我还在实验阶段思路是给技能附加一组自动评估指标定期用历史真实数据回放技能执行验证输出的准确性、耗时和 token 消耗是否有退化。比如摘要技能可以每个月跑 50 篇旧文章对比新版本模型下的输出是否包含核心结论、段落结构是否稳定。这个体检报告能让技能库在一次次的模型升级和代码变更中保持稳定而不是等到线上用户反馈效果变差了再回来排查。我目前的实现还比较简陋用一个定时任务调用评估脚本把结果写入一个简单的报告文件里。够用但还不够优雅。如果你的技能库存量大可以考虑做成一个可视化平台甚至可以联动反馈数据自动标记可疑技能。不过这些都是增量优化——先把技能本身做好再考虑管理工具的花活。目前这套技能化改造的方案已经让我手上的 Agent 项目从能跑、但总出岔子的状态进入稳定复用、可预期输出的阶段。老实说把我的技能目录和文档从一个项目搬到另一个新项目时那种轻松感是我以前用工具函数堆功能时从未体验过的。
返回列表