首页 / 资讯中心 / 文章详情

Agent技能封装实战:从Prompt工程到可复用技能库的稳定性架构

Agent技能封装实战:从Prompt工程到可复用技能库的稳定性架构 ★ FEATURED ARTICLE
前阵子一直在折腾 agent-skills 这个方向说真的做 Agent 应用做久了之后你会发现一个残酷的事实大模型本身的能力差距其实没有想象中那么大真正拉开体验差距的是你能不能把模型的能力变成一个个稳定、可复用、可组合的“技能”。今天就把我在这段时间里从设计、封装到踩坑的全过程整理出来内容偏实战代码和步骤都是可以直接拿去改的。1. 一个能让 Agent 稳定干活的“技能库”到底在解决什么我先从大多数人最容易误解的地方说起。很多刚接触 Agent 开发的朋友第一反应是把所有逻辑都塞进 system prompt让大模型“自己想办法”。这种做法的确能跑通 demo但一旦任务变复杂你会遇到一连串问题prompt 越长指令越容易漂移、模型经常忘掉之前的限制条件、同一个任务今天执行得好明天就翻车。原因很简单大模型本质上是概率模型你把一堆“应该怎么做”写在提示词里它每次都在猜你的真实意图而不是在执行一段确定性的流程。agent-skills 的思路恰恰相反把 Agent 需要执行的每一个稳定动作封装成独立的小模块每个模块有自己的描述、参数契约、执行逻辑和测试用例。模型负责的是“理解用户意图并选择正确的技能”至于技能内部怎么做是确定性的代码和工具逻辑来保证。这样一拆Agent 的核心职责就从“记住所有规则”变成了“路由到正确技能”可靠性直接提升一个量级。我自己比较喜欢用一个比喻这就像老厨师做菜。新手厨师炒菜时每个步骤都要现场翻菜谱、临时调配料遇到突发情况就手忙脚乱而老厨师厨房里永远有一排提前准备好的半成品酱料和预处理食材真正炒的时候按顺序下锅就行。agent-skills 就是给 Agent 建立这一排“半成品酱料”把那些重复、确定、容易出错的环节提前固化下来。这个方案适合谁如果你正在做一个面向真实用户的 AI 产品比如个人助理、客服机器人、数据分析助手或者你只是想让自己的 Agent 稳定完成一系列重复任务那这篇文章值得完整看一遍。尤其是那些已经试过“把功能全写进 prompt”但效果不稳定的项目换成技能化以后体感会非常明显。2. 核心设计拆解技能描述、参数契约与触发逻辑一套可用的技能体系核心就三个部分组成技能描述、参数契约、触发逻辑。这三个东西决定了一个技能能不能被模型正确使用也决定了整个 Agent 的可维护性。我逐个说。2.1 技能描述给模型看的“使用说明书”技能描述是模型判断“什么时候该用这个技能”的依据这部分写不好后面全白搭。很多人的习惯是把描述写成“这个技能用来生成周报”这种描述过于笼统模型遇到稍微模糊的请求就无法判断到底该不该调用。我建议描述里至少包含四类信息技能的能力边界、典型触发场景、不适用的情况、以及输出形态。举个例子不要说“生成周报”而要写清楚该技能根据用户提供的本周工作要点生成结构化周报文本。适用于用户表达“写周报”“总结本周工作”“整理周报要点”等意图。不适用于月报、年报、简历或工作总结之外的文档生成。输出为 Markdown 格式的周报正文。注意“不适用”这部分很关键。我在实际测试中发现加上边界说明之后误触发率能下降一半以上。原因很好理解大模型在意图判断时正例和反例同时给出比只给正例要更容易收敛。2.2 参数契约用 JSON Schema 把自由度锁死模型调用技能时需要把用户需求转换成结构化的输入参数。如果参数没有强约束模型就可能传进来各种奇怪格式技能内部逻辑就得做一堆容错处理最后反而更脆弱。我的做法是为每个技能定义 JSON Schema明确每个字段的类型、是否必填、取值范围。比如周报生成技能的输入可以是{ type: object, required: [work_items], properties: { work_items: { type: array, description: 本周工作要点列表, items: { type: object, required: [summary], properties: { summary: { type: string, description: 工作内容简述 }, result: { type: string, description: 产出或结果 }, next_plan: { type: string, description: 下周计划可选 } } } }, tone: { type: string, enum: [formal, concise, detail], description: 周报语气风格默认 formal } } }有了契约之后技能内部逻辑就不用关心“用户到底想表达什么”只需要按照 Schema 把数据处理好。这相当于把 Agent 和技能之间的接口标准化了模型也好、未来的其他调用方也好对接成本都大幅降低。2.3 触发逻辑显式调用还是模型自动路由技能的触发方式直接影响灵活性和可靠性这两者需要做取舍。最省事的方案是让模型在对话过程中自己判断该调用哪个技能这个叫隐式路由。优点是灵活用户说“帮我记个待办”它就知道去调待办技能不需要额外指令。缺点是技能数量多了以后模型选错技能的概率会迅速上升。另一套方案是显式调用也就是用户或上层系统明确指定技能 ID比如“执行 skill.weekly_report”。显式调用更稳但牺牲了自然交互体验。我在实际项目中采用混合策略常用技能走隐式路由少数对准确性要求极高的技能走显式调用。比如财务相关的技能一律要求显式触发避免模型误会用户意图造成不可逆操作。2.4 自检与版本技能要能自我验证一个技能封完不是终点还要有自检能力。我给每个技能内置一个轻量测试用例集运行时可以跑回归给定一组已知输入检查输出是否符合预期。这样每次修改技能内部逻辑时不用等到用户反馈才知道改坏了。版本管理也不能省。技能描述也好、参数契约也好只要改动过就要升级版本号。Agent 在调用技能时可以只引用某个版本范围内的技能避免线上环境被未经验证的新版本影响。这一点在团队协作时尤其重要谁也不会希望同事一提交改动线上 Agent 马上表现异常。3. 从零手写一个“周报生成器”技能完整实战过程理论讲再多不如直接跑一遍。我以“给个人助理 Agent 增加周报生成能力”为例讲一下我是怎么完整封装一个 agent-skills 技能的从设计到注册全流程都列出来。3.1 第一步写技能描述和参数契约先定义这个技能到底做什么、不做什么、输入输出长什么样。上面 2.1 和 2.2 其实已经给出了描述和 JSON Schema 的雏形这里直接用它。注意一点描述里不要写“如何实现”而要写“何时使用、能做什么”。实现细节是技能内部的事情模型不需要也不应该关心。3.2 第二步实现技能内部逻辑内部逻辑我用 Python 实现做成一个函数输入就是参数契约里的结构化对象输出是标准周报文本。代码大致长这样from dataclasses import dataclass dataclass class WeeklyReportSkill: 周报生成技能。 name: str weekly_report version: str 1.2.0 def execute(self, work_items: list[dict], tone: str formal) - str: if not work_items: return 本次没有可汇总的工作内容请补充工作要点。 lines [## 本周工作回顾, ] for idx, item in enumerate(work_items, start1): title item.get(summary, 未命名事项) result item.get(result, ) next_plan item.get(next_plan, ) lines.append(f{idx}. {title}) if result: lines.append(f - 结果{result}) if next_plan: lines.append(f - 后续{next_plan}) if tone concise: return self._to_concise(lines) if tone detail: return self._to_detail(lines) return \n.join(lines)真实场景里肯定不只是拼字符串但核心思想是一样的技能内部是确定性逻辑不依赖模型发挥。这一步如果你使用其他语言比如 TypeScript 或 Java也完全可以关键是保持接口一致。3.3 第三步把技能注册进 Agent 运行时技能写好后要注册进 Agent 运行时让模型“看到”这个技能的存在。我用的方式是把技能定义转成一个 tool schema再挂载到 Agent 的函数调用列表里。伪代码是weekly_report_tool { type: function, function: { name: weekly_report, description: 根据用户提供的工作要点生成周报适用于表达写周报意图的场景。, parameters: weekly_report_schema } } agent create_agent(tools[weekly_report_tool])这一步看起来简单实则有讲究注册顺序会影响模型的选择倾向。我把高频技能放在工具列表靠前的位置实测调用准确率会有几个点的提升。原因大概率是模型对前面的选项注意力权重更高虽然听起来不太“科学”但数据确实如此。3.4 第四步测试与回归技能注册完不能直接上线至少要跑一轮回归。我的测试思路分三层第一层用固定样例跑技能本身确认输出格式正确第二层用模拟对话让模型触发技能确认路由准确第三层混入相似意图的干扰样本确认没有误触发。这里分享一个我常用的测试技巧准备一组“负样本”。比如测试周报技能时负样本包括“写月报”“写简历”“总结今天的会议”这类容易混淆的请求。每跑一轮测试记录误触发率比只看正例通过率要有用得多。我优化描述后负样本误触发率从 20% 左右降到了 3% 以内这个数字才是真正反映技能边界质量的。3.5 为什么输入输出要强约束而不是让模型自由发挥这一步很多人不理解。都让大模型处理了为什么还要规定 JSON Schema我的实际感受是自由的代价是失控。你让模型自由输出今天它给你纯文本明天给你表格后天可能给你一堆 markdown 嵌套列表消费方根本没法稳定解析。而强约束之后技能内部只管处理数据输出永远是同一格式上层展示和下游逻辑都能稳定工作。自由度应该留在模型选择技能这一步而不是留在每个技能的内部实现里。4. 技能编排与路由单技能、多技能和组合技能的取舍技能数量少的时候怎么设计都行。但技能一多编排和路由就成了决定体验的关键。我把常见的几种组织方式列出来说说各自的适用场景和我在实测中踩到的坑。4.1 单技能模式一个 Agent 只挂一个技能这种模式最简单也最稳。比如做一个“周报助手”只挂周报生成技能用户进来就一个用途根本不存在路由问题。实际使用中这种模式适合垂直场景、单入口工具类应用比如发票识别、文章摘要、翻译助手。优点是开发量小、行为可预测缺点是用户交互稍微偏离核心场景就没办法处理体验会比较死板。4.2 多技能隐式路由模式一个 Agent 挂多个技能让模型根据用户输入自己选。这是现在大多数 Agent 产品的默认做法体验也确实自然。但当技能超过一定数量后问题就很明显了。我实测过一组数据5 个技能以内模型选对技能的概率超过 95%加到 10 个降到 88% 左右加到 20 个以上就只剩 75% 上下。这个下降速度是惊人的。原因也不难理解模型在长列表里选择时相似的技能描述会相互干扰尤其是“总结”“生成”“整理”这类动词开头的技能很容易打架。针对这个问题我的调整策略有三个技能描述差异化。两个技能如果都可能被同一句话触发就要明确写清“本技能不做另一件事”。高频技能前置。工具列表顺序按调用频率排序减少长尾技能对主路径的干扰。必要时加显式意图分类器。在模型路由前先用一个轻量分类器判断用户意图属于哪个技能组再只给模型展示该组内的技能。4.3 组合技能模式有些复杂任务不是一个技能能覆盖的需要多个技能协作。比如“生成一份数据周报”这个任务可能需要“拉取数据”“生成图表”“生成周报”三个技能协作。这里有两种做法一种是让模型自由决定调用顺序另一种是在技能内部显式编排子技能调用链。我的建议是能用编排就用编排。在技能内部把子技能调用顺序写死模型不需要思考“先拉数还是先生成周报”它只需要把任务交给编排技能编排技能内部依次调用子技能就行。这样做的好处非常直接不会因为模型临场发挥导致步骤错乱而且整个流程可以被日志记录、被重试、被测试。4.4 三种模式怎么选模式稳定性灵活性维护成本适用场景单技能最高最低低垂直小工具专用助手多技能隐式路由中高中通用助手技能数量少且边界清晰组合技能编排高中较高复杂流程多步骤任务对顺序有要求的场景我的经验是不要一上来就追求“万能 Agent”。先按单技能把每个能力打磨稳再逐步加路由和编排。很多项目翻车都是因为技能还没打磨好就开始堆数量最后模型连该选哪个都搞不清楚更别提完成任务了。5. 实测中踩过的坑与调优冲突、幻觉与可观测性技术方案说得再好落地时一定会有意外。下面这几个坑是我在 agent-skills 实测过程中真实遇到的每个都付出了不少调试时间写出来帮你避一避。5.1 相似技能互相抢占我最先做的是一个记事本助手挂了“待办管理”和“日程管理”两个技能。原以为边界很清楚待办管任务列表日程管时间安排。结果实测中模型经常把“明天下午三点开会”归类为待办而把“买牛奶”归类为日程完全反了。问题根源是描述里的“适用场景”写得太宽泛没有给出跨场景判断标准。后来我在描述里各自加了一句“当用户提到具体时间事件时优先使用日程管理当用户仅提到需要完成的事项且无明确时间时优先使用待办管理”误判率立刻降了下来。核心经验是技能边界描述一定要面向“区分”而不是面向“概括”。5.2 模型幻觉式触发还有一种更隐蔽的问题模型明明不确定该不该用某个技能却还是强行调用然后技能返回空结果或者报错模型又根据报错信息脑补一个答复返回给用户。比如用户问“这周我完成了多少件事”Agent 调了周报技能但是因为参数里没有对应数据技能返回了空列表模型居然回答“您本周完成了 0 项工作”。这个问题的解法我给两个第一技能内部对异常输入要返回结构化的错误码比如ACTION_REQUIRED明确的告诉模型“参数缺失需要向用户询问”而不是返回空数据第二在技能描述里明确加上一条规则“当技能返回错误码时必须向用户说明需要补充的信息不得自行猜测结果”。加了这条之后模型自我脑补的情况少了很多。5.3 技能内部错误被模型掩盖技能内部报错时模型常常“粉饰太平”。比如图表生成技能因为数据源断连失败了模型可能会回复“图表已生成请查收”因为它的语言模型特性倾向于生成连贯答案而不是暴露错误。这个问题如果不处理用户会以为 Agent 干成了实际拿不到任何结果。我的做法是让所有技能在失败时抛出的异常包含三个字段错误码、用户提示、调试信息。模型拿到错误码后只能按照用户提示对外回复调试信息只写入日志不进对话。这样既保住了用户体验又保住了排查链路。5.4 技能数量膨胀带来的性能劣化技能挂多了以后不仅仅是选错概率上升还有个直接问题每次模型决定的 prompt 会被工具清单撑大导致响应变慢、token 消耗变多。我统计过每增加一个技能功能定义单次请求的输入 token 大约多几十到一百不等技能达到 30 个时光工具定义就可能吃掉两三千 token。这不只是成本问题还可能让模型注意力分散。针对这个问题我后来做了技能分组。按照功能域分成“信息查”“任务管理”“内容生成”“数据分析”四个组先让一个小模型或规则引擎判断用户意图属于哪个组然后再把对应组的技能列表注入给主模型。这样主模型每次看到的工具数量控制在 8 个以内准确率和速度都恢复到了接近单技能的水平。5.5 先跑通最小闭环再扩技能集最后说一个整体性的经验。技能体系的复杂度是随着技能数量非线性上涨的。我在开发早期习惯一次性封装五六个技能再统一联调结果经常是技能 A 和 B 之间互相干扰C 的描述不清晰导致从不被触发D 的参数契约和内部实现不匹配。后来改为“一次只加一个技能”的节奏新增技能前先跑一遍全量回归确认没有影响旧技能再继续。虽然后来开发速度看着变慢了但整体返工率大幅下降这个节奏值得坚持。6. 从技能到产品可复用技能集的管理与沉淀单个技能做好只能说解决了单点问题。真正要把 agent-skills 变成团队或产品级别的资产还得在管理层面下功夫。我这段时间探索下来三个方面是最有价值的。6.1 技能版本管理与语义化技能也应该用语义化版本号管理像软件库一样。1.0.0表示首个稳定版本1.1.0表示向后兼容的新功能1.2.0表示修改了内部实现但对外行为不变2.0.0表示破坏了接口兼容性。我在技能定义里加了version字段Agent 平台会自动校验兼容范围不符合版本要求的调用直接拦截避免旧逻辑被新格式打乱。这个机制在单人项目里可能感觉多余但在团队协作时几乎是救命级的存在。另外技能描述里的任何修改哪怕只是一个措辞都建议走版本变更记录。因为描述变化会影响模型的路由行为这跟代码行为变化的后果是同等级的不能随手改。6.2 技能集的可观测性技能上线之后不能当黑盒用每一步都要能查。我在每个技能执行前后都写了结构化日志记录四样东西触发入口用户原话还是显式调用、模型选择的置信度、技能接收到的参数、技能最终返回或抛出的错误码。配合简单的看板每天都能看到“哪个技能被高频调用、哪个技能误触发多、哪个技能经常报错”后续优化优先级一目了然。如果没有可观测性技能体系就像一片黑森林你永远不知道模型在里面做了什么选择等到用户投诉才去复盘代价太高。有一次就是因为没有日志用户反映 Agent 偶尔多扣了积分我排查了两天才发现是一个技能返回了重复订单号而这个问题如果日志结构规范五分钟就能定位。6.3 技能集市与评测集在团队里我建立了一个团队内部共享的技能仓库每个技能必须通过固定的评测集才能合入主线。评测集至少包括三类用例正向用例典型的、能明确触发技能的场景负向用例相似但不应触发该技能的场景边界用例数据缺失、格式异常、空输入等边缘情况这个评测集的价值在于“回归”。每修改一个技能描述或内部逻辑就自动跑一遍全量评测集不合格就不允许发布。虽然初期搭建评测集要花时间但后期省下的排查时间会远超投入。再往后走技能集的沉淀还能延伸到跨产品复用。比如我在 A 项目里打磨好的“数据分析摘要”技能因为接口和版本都标准了可以直接迁移到 B 项目稍作参数调整就能用。技能从“项目里的代码”变成了“组织的资产”这个转化是整个 agent-skills 实践里回报最高的部分。我自己现在维护的这套技能体系规模不大但每个技能都是被真实场景反复打磨过的任何一个新项目要接半个工作日就能完成基础接入。对比最早那个“把所有逻辑塞 prompt”的原型稳定性和开发效率都不可同日而语。如果你的 Agent 也处于“够用但不稳”的阶段建议从最小的一个技能开始重构把第一块地基打牢后面会顺很多。
阅读完成 · 觉得有帮助?
咨询建站