Agent技能封装:用模块化技能包替代堆Prompt实现稳定复用

发布时间:2026/9/16 6:47:53
Agent技能封装:用模块化技能包替代堆Prompt实现稳定复用 做Agent落地的项目做久了会越来越明显感受到一个事实模型本身的智力水平已经不是核心瓶颈真正卡住项目进度的是怎么把一项能力稳定地交到Agent手里。你可以在System Prompt里塞上几千字操作规范也可以给Agent挂上几十个API工具但等到业务场景一变、工具一升级、或者对话轮次一长这套手搓式组合就会暴露出各种各样的意外行为。这就是我要做agent-skills项目的原因。简单说它是一套面向AI Agent的模块化技能体系把Agent要完成的某项完整任务比如整理会议纪要并分派待办、从合同里抽取关键条款并比对标准模板封装成目录结构固定、元信息可解析、执行环境隔离、结果标准化的独立技能包。Agent不再靠堆Prompt去理解该怎么做而是像人一样在工具箱里挑一件趁手的工具打开说明书就能用。这篇文章从实际构建角度切入适合两类人看一类是正在设计自有Agent应用、对能力封装方式摇摆不定的开发者另一类是已经在用LangChain、CrewAI这类框架但觉得Agent行为不稳定、想引入更可控执行单元的技术负责人。全文不贴几千行源码重点讲清楚设计取舍和落地时容易踩的坑。1. 从一堆Prompt到一门手艺Skills体系要解决的真实痛点先说个让我下决心重做这件事的经历。当时我维护了七八个业务Agent每个Agent背后都挂着一套System Prompt、几组Few-shot样例、再加上一长串工具函数列表。表面上看各自独立但改一个需求往往要同时动三层Prompt里的操作规范得改工具函数里的判断逻辑得改Agent的启动参数配置也得改。最头疼的一次线上Agent在处理订单确认时连续漏了两步校验查了半天发现根因就是两套Prompt里对确认这个词的定义不一致一套认为是仅告知客户另一套认为是告知并核验库存。这类问题不是靠写更长的Prompt能解决的。Prompt越长模型越容易在关键步骤上迷失注意力而且你没法对Prompt做自动化测试更没法在不同Agent之间复用同一段能力。1.1 Agent碎片化的三个典型症状我把当时遇到的问题归纳成三类。第一类是能力边界模糊。工具函数只定义了能做什么没有定义在什么条件下用、按什么步骤做、做完了要产出什么。模型拿到一个工具列表时它对使用边界的理解完全依赖上下文里的零散描述换个场景就容易用错。第二类是上下文不断膨胀。为了确保模型按规范执行我不得不把大量操作说明、业务规则、历史案例都塞进System Prompt。对话稍长一点Token开销剧增模型反而抓不住重点。第三类是能力不可复用。在一个Agent里调通的流程换个Agent又要重新写一遍Prompt、重新配一遍工具。本质上是因为能力被写在了和具体业务Prompt高度耦合的文本里而不是被包装成一个独立可挂载的模块。1.2 工具Tool和技能Skill到底差在哪这可能是理解agent-skills最关键的认知切换。工具箱里的螺丝刀是一个工具它只定义旋转、拧紧这个动作但把松动的柜门铰链修好是一门技能它包含了对场景的判断、正确的操作顺序、需要哪些螺丝刀、拧到什么程度算合格、拧坏了怎么补救——这些内容单靠一个工具表达不了。对应到Agent上就是工具函数只要定义输入参数、执行逻辑、返回值就够了技能则是一整个操作流程的封装至少应该包含四件事——适用场景描述、执行步骤指令、所需工具列表、结果校验与异常处理规则。这也是我在agent-skills里把技能作为顶层抽象单元的原因。工具交给Agent只能让模型去猜怎么组装技能交给Agent则是把一套已经被验证过的解题流程直接端到它面前。2. 技能库的骨架目录约定、元信息与依赖隔离我设计agent-skills时给自己定了个原则一个技能必须能独立看懂、独立测试、独立拷贝到另一个项目里用。为了做到这一点每个技能被设计成一个自包含的文件夹而不是散落在代码里的几个类和配置文件。2.1 一个技能就是一个文件夹目录结构沿用了我在实践中验证过的组织方式skills/ └── meeting_minutes/ ├── SKILL.md # 技能说明场景、步骤、边界 ├── skill.yaml # 机器可读元信息名称、版本、参数、依赖 ├── actions/ │ ├── process.py # 核心执行逻辑 │ └── helpers.py # 私有辅助函数 ├── prompts/ │ ├── main.md # 给模型看的步骤指令 │ └── refine.md # 结果加工指令 └── tests/ ├── fixtures/ # 测试样本 └── test_skill.py # 技能级测试这个结构的核心思想是把对人的说明和对机器的描述分开。SKILL.md读起来像一份说明书让开发者能快速理解技能的作用skill.yaml则是给Agent运行框架和调度器解析用的字段完整且语义明确。我会在SKILL.md里写清楚这个技能的擅长场景和边界。比如会议纪要技能我会写明本技能适合处理1小时以内的中文语音转写文本如果会议超过3小时或者有多个语种混用建议拆分后再调用。边界写清楚能省掉大量Agent在错误场景里硬套技能导致的低级错误。2.2 机器可读的元信息让框架能替你看门skill.yaml是整个技能体系大脑里最容易被忽视但又最有价值的部分。我的设计里包含这些关键字段name: meeting_minutes version: 2.1.0 description: 从会议转写文本中提炼议题、结论与待办事项 author: team-core tags: [meeting, minutes, todo] input_schema: transcript: { type: string, description: 会议转写全文, required: true } language: { type: string, enum: [zh, en], default: zh } with_priority: { type: boolean, default: true } output_schema: format: { type: string, enum: [markdown, json] } summary: { type: boolean } dependencies: python: 3.10 packages: [openai, dateutil] isolation: venv为什么元信息必须机器可读因为这样才能在Agent运行前就完成三层看门校验第一参数校验——Agent传入的参数是否符合input_schema定义参数缺失或类型不对我可以在调用前直接拦截而不是让它执行到一半才报错第二索引构建——框架启动时扫描技能库用yaml里的description和tags自动生成技能检索索引第三依赖检查——启动时检查Python版本和依赖包不满足就直接跳过该技能避免运行时才发现环境缺依赖。配合数据结构化校验还能规避Prompt里反复强调格式却依然输出不规范的场景。有了output_schema执行结果出来以后框架先自动做一次格式校验不合法就直接走重试或报错流程。2.3 依赖隔离别让一个技能的环境问题炸掉整个应用有段时间我在技能里引入了Pandas做数据处理另一个技能又偏偏和Pandas版本冲突两个技能装在同一套环境里把Agent搞到频繁重启。这个教训直接推动我在agent-skills里加入依赖隔离策略。现在我做两档隔离默认档用Python虚拟环境venv隔离适合绝大多数纯Python逻辑的技能重档用容器方案隔离比如技能需要特定系统库、或者执行的是不可信的外部脚本我会把它扔进一次性容器里运行执行完销毁。隔离的本质是让技能A的土壤不影响技能B的庄稼这一步省下来的排查时间非常可观。3. 执行链路的关键设计上下文窗口、产物回传与错误恢复技能包搭好只是第一步更麻烦的是运行时设计——Agent到底怎么调用一个技能、技能跑完的结果怎么回到主对话里、以及技能执行失败时整个链路怎么兜底。这三件事没做好技能库再规范也发挥不出作用。3.1 别把整个对话历史塞给技能我在早期版本里犯过一个最典型的错误调用技能时把主Agent的完整对话历史作为上下文一起传给技能内部的大模型调用。结果对话超过十轮后Token消耗猛增技能产出质量反而下降——大量无关历史干扰了模型对当前任务的理解。后来我把设计改成了技能输入规约模式技能执行时只接收跟我定义的input_schema匹配的关键信息以及一段限定长度的业务背景摘要。背景摘要不是直接把对话历史倒进去而是由一个轻量级的上下文提取器先完成压缩只保留当前任务相关的实体、日期、约束条件。这个模式上线后单次技能调用的Token开销下降了约60%结果稳定性明显提升。3.2 产物设计的双通道机器能解析人能看懂技能的产出如果只是一段自然语言文本下游任务就无从解析。我在agent-skills里定义了标准产物结构执行结果统一返回两个通道。{ status: success, data: { action_items: [ {task: 确认Q3预算, owner: 张三, due: 2025-11-30} ], follow_ups: [] }, summary: 共提取3项待办其中2项已明确负责人, artifacts: [/tmp/minutes_20251120.md] }data字段是结构化数据主Agent可以基于它做后续分析、写入外部系统、或者触发另一个技能。summary字段是给人看的自然语言摘要直接用于对话回复。这样设计的好处是主Agent既不需要从一大段文本里重新解析JSON也不用为了展示对用户友好而反复加工结构化数据——两个通道各司其职。3.3 技能失败时重试、降级、还是换一条路技能执行失败是常态我实测下来的失败率在5%到15%之间取决于输入数据的规整程度。所以执行链路里必须有分级的错误恢复策略。第一级是本技能内重试。比如调用外部API超时可以自动重试两次但每次重试要带上更积极的参数比如温度调低、让模型更保守地截断输出避免用同一套参数反复撞同一堵墙。第二级是降级输出。如果技能的核心逻辑失败但又有部分结果产出就返回一个降级结构把已产出的部分按照output_schema回传同时在status字段标记为partial并附带failure_reason。主Agent拿到这个标记后可以决定是否换个技能或者让用户确认。第三级是技能路由切换。我在技能注册表里为部分技能声明了fallback_skills——当主技能失败两次以上调度器会尝试调用备用技能。这一步的收益被低估了业务Agent的可用性在真实环境里不是靠单个技能质量撑起来的而是靠兜底链路撑起来的。4. 让Agent知道该用哪个技能选择与动态编排的两种实现思路技能库里的技能数量超过20个之后一个新问题出现了Agent怎么知道自己该用哪个技能传一个几百字的中文用户请求怎么匹配到正确的技能包这个问题的解决方案基本分成两派我自己先后试过两派的做法最后落了一套混合策略。4.1 方案一向量检索召回技能这个思路朴素直接把每个技能的description、tags、示例场景预先用Embedding模型转成向量收到用户请求后把请求也转成向量然后做相似度检索取Top-K个技能作为候选。优点是实现成本低、延迟低一次向量查询通常几十毫秒而且不需要额外的大模型调用。缺点是Expr精确理解能力有限——用户请求里如果没有出现和技能描述足够相似的关键词召回结果就会跑偏。比如技能里写的是整理会议纪要用户只是说帮我总结一下刚才聊的内容向量相似度可能不够高导致该用的技能没被召回。4.2 方案二让模型自己看菜单点菜另一种做法是把技能列表当作一份菜单塞进上下文让主模型自己去判断该选哪个技能。我做过一轮对比实验在小模型比如7B到14B级别上效果不算稳定但在当前主流的强模型上这个方法的效果明显好于纯向量召回。模型对意图的理解能力强很多总结刚才聊的内容它也能关联到meeting_minutes这个技能。但纯走这条路有两个实际问题。第一是Token开销技能数量多时把每个技能的完整描述都塞进上下文会挤占对话空间我试过挂80个技能以后光技能清单就占了将近3000Token的开销。第二是幻觉式选择模型偶尔会在多个相似技能之间做出误差较大的选择在没有参考信息时尤其明显。4.3 我的混合策略先粗筛、再精排最终我采用的是先粗筛、再精排的两阶段路由先向量检索召回Top-8候选技能再把召回的技能描述作为候选菜单交给主模型精排。这样既能利用模型的语义理解降低召回错误又能把上下文开销控制在可接受范围。精排阶段我会额外补一个信息每个候选技能的历史调用成功率。这个数据是从运行日志里统计的出现调用失败次数过多的技能会在精排阶段被模型优先排除。我把这个字段称为技能健康度它的作用远比我预想的大——模型会自动倾向那些在真实运行中表现稳定的技能而不是只靠描述选。两个阶段配合最终准确率在我自己的一批业务技能集上做到了96%以上。如果用户请求明显不属于任何技能相似度阈值低于某个值调度器不会强制选择而是回落到普通对话模式防止错误路由产生不可预测的行为。5. 实测中踩过的四个坑上下文污染、技能僵化、冲突与回退最后这一章是真正的实践总结。问题都不在官方文档里是我在把agent-skills这套体系接入实际业务后踩过并完全解决的四个坑。5.1 上下文污染技能内部变量泄漏进主对话技能执行过程中为了做调试或者让模型更好地完成中间步骤会产生大量内部中间变量。早期版本里这些中间变量会被一起写进执行记录然后主Agent在总结时偶尔会把内部调试信息当成业务信息输出给用户——有一次它甚至把技能里用的API Key末尾几位一起报了出来这个事故直接把我吓出一身汗。后续我专门加了一套上下文隔离标识机制技能内部变量统一加internal_前缀在主Agent聚合上下文时强制过滤掉所有带此前缀的变量。机制显式化后再没有出现过内部信息泄漏的问题。这事的教训是技能的边界不仅要体现在目录结构上更要体现在运行时变量隔离上。5.2 技能僵化写死的规则一换场景就失效我的第一个技能版本里塞了大量硬编码比如日期格式写死为YYYY年MM月DD日金额判断写死为超过5000元需审批。换个项目用同套技能这些硬写到代码里的规则就变成了定时炸弹——不出错则已一出错就是看起来很自然的业务判断错误。解决方式是规则外置化凡是容易随着业务变化的规则全部抽到skill.yaml里配置并设计成可被外部覆盖。技能代码只负责执行规则不负责决定规则。这个改动让技能从一个死板的脚本变成了可配置的服务跨项目复用率明显上升。5.3 技能冲突多个技能组合时状态互相覆盖有两个独立技能——一个负责生成周报一个负责更新项目管理表格。两个技能各自测都没问题但组合使用时周报技能生成的报告会把项目管理表格技能写入的临时缓存给覆盖掉导致表格更新到一半丢失了数据。排查深入后发现是两套技能都用了同一个本地缓存目录变量名还恰好一致。我把技能的数据存储路由改成基于命名空间隔离每个技能写文件、缓存、临时数据都限定在各自独立的目录里跨技能数据共享必须显式声明并通过框架提供的数据总线传递。从此这类隐形互相踩踏的问题基本绝迹。5.4 技能升级导致旧对话无法复盘我更新了某技能内部Prompt后发现以前跑过的历史对话记录里凡是使用过旧版技能的记录都没法精确复盘——因为老对话里只存了最终结果没存技能版本和输入参数。后来我在每次技能调用时把所有关键调用信息技能名称、版本号、输入参数、输出结果、执行耗时附加到对话的事件流里。这些信息不参与模型生成但保存为独立事件记录。有了这套历史可追溯机制后任何一次业务异常都能定位到具体是哪个版本的技能、用了什么参数、执行哪一步出了问题。对一个生产环境里的Agent体系来说可追溯性的价值甚至高于技能本身的精度。构建agent-skills这套体系前后花了三个多月回看最值得高兴的不是Agent表现变好了多少而是我终于有一套可以持续演进、可以测试、可以追溯的技术框架来管理Agent能力。技能库现在已经积累了几十个技能包新业务进来时不用再从零写Prompt而是先看技能库能不能攒出一个组合方案。如果你也在搭自己的Agent应用我建议别急着堆功能先把技能包这个抽象层想清楚——它决定你未来是持续积累资产还是永远在维护一地鸡毛。