技能工程:从提示词工程到Agent Skills智能体技能库
这两年做大模型应用我最大的感受是提示词工程已经是上个版本的事了。现在圈里聊得最多的是agent-skills也就是把Agent会用的能力从“一段临时发挥的提示词”升级成“一套可复用、可组合、可维护的技能库”。我是在做客户支持机器人的时候被这个思路彻底点醒的。当时每个新需求都要重新调提示词模型一会儿理解对了一会儿又跑偏排查起来极其痛苦。后来我把高频动作——查订单、算价格、写工单、转人工——按技能的方式拆开每个技能一个目录、一份说明、一段代码逻辑模型只在需要的时候把对应技能“加载”进来。效果出乎意料地稳调试效率翻了几倍后来我把这套做法延伸到了十几个内部项目里成了标准姿势。这篇文章不会去讲空泛的概念我直接分享agent-skills是什么、怎么设计、怎么从零实现一个可用技能以及我在实际项目中踩过的一系列坑和排查方法。不管你是刚开始接触智能体开发还是已经做出了几个Agent原型这篇都值得看完。1. 为什么需要Agent Skills从提示词工程到技能工程1.1 Agent开发逻辑的三次升级最早做AI应用的时候大家靠的是提示词工程。把所有规则、例子、输出格式全塞进一段Prompt里指望模型一次性输出正确内容。这个模式在小任务上确实管用写个文案、做个摘要、翻译一段话效果都不错。但一旦任务复杂起来问题立刻暴露指令一长模型开始丢三落四规则一多互相矛盾上下文一长注意力分散输出质量直线下降。我见过最夸张的一次一个Prompt里有十几条规则和五个示例结果模型在第六条规则之后基本就开始“选择性遗忘”了。于是进入了第二阶段Function Calling / Tool Use也就是给模型配工具。需要算数就调计算器需要查数据就查数据库需要发消息就调接口。这个模式解决了不少问题模型不再需要“背”所有规则而是按需调用工具。但实践中又会遇到新麻烦工具越来越细碎。举个例子你想让Agent帮你分析一份财务报表它可能需要调解析文件的工具、调计算指标的工具、调生成图表的工具、调格式化报告的工具。如果每个工具都零散地裸露给模型模型面对几十个工具描述单是“选哪个工具”本身就成了一个难题经常选错或者反复尝试浪费大量token和时间。第三阶段也就是现在大家在做的事情就是技能工程Skill Engineering。核心思路是把某一类完整任务的“做法”打包成技能一个技能内部可以包含多个工具调用、多步骤流程、专门的说明文档甚至自带默认参数和错误处理。模型面对的就不再是几十把螺丝刀而是一排“工具箱”每个箱子已经按场景配好了该用的工具和操作手册。1.2 技能、工具、插件与工作流的边界很多刚开始接触的人会把技能、工具、插件和工作流混为一谈。我画一个简单但不严谨的区分工具是最小动作技能是完整流程插件是技术载体工作流是固化流程。举个例子一个“发送邮件”的操作是工具而“给客户写一封跟进邮件先查历史沟通记录再根据客户语气生成草稿最终通过邮件接口发出”就是一个技能。技能内部要用到好几个工具但模型不需要关心内部怎么编排。插件更多是工程形式上的定义主流Agent框架里的插件可能就是技能的运行容器加载技能文件、暴露入口、管理依赖。至于工作流通常指流程已经固定、不需要模型现场决策的脚本化过程而技能往往保留了一定的模型自主判断空间。这个区别非常重要因为它决定了你用什么方式去设计和维护。如果你发现某个流程无论如何都是固定的那应该做成工作流而不是技能如果某个动作就是一次原子调用那做成工具就够了只有在“流程需要模型根据实际情况灵活编排”的时候才值得做成技能。1.3 agent-skills解决了什么核心痛点我总结下来agent-skills解决的核心问题有三个第一上下文瘦身。以前为了让模型知道怎么处理某类任务所有规则、示例、边界条件都要常驻在上下文里。有了技能后这些内容只在需要的时候加载平时不占上下文空间。以我实际项目为例一个包含完整操作手册的技能文件可能有三四千词但只有用户真的触发相关任务时这部分才会被注入其余时间里宝贵的上下文窗口都留给对话内容。第二行为一致性。同一个任务今天这么做、明天那么做是AI应用最让人头大的问题。技能把一套做法固定下来写成标准操作流程模型每次调用技能都是按照同样的说明执行输出稳定性显著提升。第三能力复用与迭代。以前在项目A里调好的Prompt搬到项目B基本要重写。技能不一样它本质上是独立文件和代码一套技能可以直接复制到另一个项目中使用也可以像开源软件一样维护升级。我现在的做法是公司内维护了一个技能仓库新项目直接从仓库引入技能省掉了大量重复开发。2. Agent Skills的核心设计与配置解析2.1 技能的目录结构与SKILL.md目前我接触过的主流技能实现目录结构大同小异。以一个“生成会议纪要”的技能为例典型的结构长下面这样meeting-minutes-skill/ ├── SKILL.md # 技能说明与使用指南 ├── requirements.txt # 依赖的Python包 ├── script.py # 核心逻辑代码 └── reference/ ├── template.md # 纪要模板 └── examples/ # 示例输出 ├── example1.md └── example2.md这里面最核心的是SKILL.md相当于技能的“说明书”。我曾经看到有的团队把SKILL.md当作可有可无的装饰这是大错特错的。对基于大模型的Agent来说SKILL.md就是驱动模型执行任务的核心依据重要性远超具体代码。在Claude Agent Skills的规范里SKILL.md通常包含YAML格式的元信息区和自由文本的说明区。元信息区定义技能名称、描述、使用场景自由文本区则详细描述任务目标、执行步骤、注意点和示例。文件的开头部分示例--- name: meeting_minutes description: 根据会议录音转写文本生成结构化会议纪要自动提取决议与待办事项 ---模型判断“什么时候使用这个技能”主要就是读description。如果description写得模糊模型就容易误触发该用的时候不用不该用的时候乱用。2.2 触发机制与上下文加载理解技能的触发机制是掌握这套体系的关键。技能并不是常驻在上下文里的它更像一个“按需加载的模块”。当用户提出请求后Agent会先评估用户的意图如果匹配到某个技能就把这个技能的完整内容注入上下文再进行推理。这个机制可以类比成“大脑不把所有知识都装进工作记忆遇到问题才去翻对应手册”。好处显而易见上下文利用率高任务切换灵活。但它也带来了一个隐含要求技能触发必须足够可靠。如果该触发没触发用户的问题就用通用能力硬解效果会很差如果不该触发却触发了浪费上下文不说还可能给出张冠李戴的回答。所以设计技能时description字段的措辞需要反复打磨。我常用的套路是“场景任务边界”。比如一个技能负责处理退款description我写成“当用户要求退款、退货或投诉订单金额错误时使用。不适用于普通商品咨询和物流查询。”这样既给了正向信号也给了负向边界模型误判概率明显下降。2.3 上下文预算与技能体积的平衡这是我在实践中最常纠结的问题。技能体积越大信息越多模型执行越精准但技能加载后占用的上下文也越多留给对话和用户数据的空间就越少。尤其在技能套技能、多技能连续调用的场景里上下文管理稍有不慎就会撑爆窗口。我的经验是SKILL.md文档保持精简把冗余信息拆到独立文件。SKILL.md里只写核心步骤、判断逻辑、关键规则模板、长示例、参考手册单独放文件等真正需要时再按需读取子文件。比如一个生成月度运营报告的技能SKILL.md里写报告结构和指标口径长格式的示例报告放在reference文件夹模型执行时如果对格式没把握才去读取。另外还有一个小技巧技能内部尽量少放“众所周知”的内容。你不需要告诉模型“什么是销售额”只需要告诉它“本场景下销售额订单实付金额-退款金额不含运费”。上下文里每一句话都应该有信息增量否则就是在烧钱。3. 从零实现一个完整技能实操全过程为了避免话题悬在空中我拿一个真实做过的技能来完整走一遍流程——客户邮件分类与紧急回复建议。这个技能在客服场景里非常典型既能体现技能决策的价值又不会因为业务逻辑太复杂而模糊掉重点。3.1 需求分析与决策点提取第一步不是写代码而是把任务拆清楚。邮件分类任务看起来简单实际牵涉多个决策点邮件属于哪个类别咨询、投诉、退款、合作、垃圾邮件紧急程度多高需要立即处理还是24小时内即可当前是否需要回复模板还是要转人工邮件里有没有必须提取的结构化信息比如订单号、金额、产品名这些决策点决定了技能“需要什么输入、做什么判断、产出什么结果”。我在这个阶段习惯先在纸上画一遍流程不画图就列文字逻辑。列完发现这个技能内部可以用一个两阶段结构第一阶段分类打标第二阶段根据分类产出回复建议和转人工决策。3.2 技能文件与代码的落地目录结构确定如下email-triage-skill/ ├── SKILL.md ├── classifier.py └── reference/ ├── categories.json ├── urgency_rules.md └── response_templates.mdSKILL.md里我写了这些内容技能用途、输入输出格式、两步执行流程、几个关键示例、几条明确的禁忌。写的时候一直提醒自己这份文档是要给模型“读”的不是给人类“看”的所以语气是操作指令式的而不是说明文式的。--- name: email_triage description: 用于客户邮件的自动分类、紧急程度判断与回复建议。当收到客户邮件、工单消息或需要判断消息应如何处理时使用。 --- # Email Triage Skill 对一封客户邮件执行分类与处理建议。 ## 输入 - 邮件全文正文即可标题可选 ## 执行步骤 1. 判断邮件类别从以下选项中选出最匹配的一项 - complaint投诉表达不满要求解决问题。 - refund退款涉及退钱、退换、订单取消。 - inquiry咨询询问产品、价格、物流、操作方式。 - partnership合作商务合作、推广、渠道。 - spam垃圾广告、无关内容。 2. 判断紧急程度high / medium / low。 - 退款、投诉、支付失败属于 high。 - 普通咨询属于 medium。 - 合作、广告属于 low。 3. 根据类别与紧急程度输出结构化处理建议 - 是否选择回复模板 - 是否需要转人工 - 需要从邮件中提取的关键信息字段。 ## 输出格式 以JSON对象返回包含 category、urgency、should_escalate、suggested_reply、extracted_fields 五个字段。 ## 注意事项 - 订单号格式为 ORD- 后接8位数字提取时务必完整。 - 如果邮件语言非中文回复建议默认使用原语言。 - 当无法确定类别时选择 closest match 并设置 should_escalatetrue。classifier.py里面就是核心代码逻辑负责兜底解析和常规处理。但这里有个重要认知对于基于大模型的Agent技能代码不一定是主体。模型的推理能力才是核心代码只负责那些模型不擅长的事情比如精确计算、数据库查询、格式化输出、调用外部API。所以我的classifier.py主要做了一件事接收模型产出的JSON结果校验字段完整性把结果存成标准格式方便下游使用。import json import re from typing import Any, Dict def validate_email_triage_result(data: Dict[str, Any]) - Dict[str, Any]: 校验模型输出的邮件分类结果补齐缺失字段并检查订单号格式。 required [category, urgency, should_escalate] for field in required: if field not in data: data[field] None order_pattern re.compile(rORD-\d{8}) if isinstance(data.get(extracted_fields), dict): order_id data[extracted_fields].get(order_id, ) if order_id and not order_pattern.fullmatch(order_id): data[extracted_fields][order_id] None data[should_escalate] True return datareference/目录下的文件用来放模型“按需查阅”的扩展资料。categories.json存类别定义和判定关键词response_templates.md放各分类的回复模板urgency_rules.md放紧急程度判断细则。这样SKILL.md主文件保持了轻量模型只有在不确定时才会去看辅助文件。3.3 技能测试与效果优化技能写完后测试环节我建议不要省。我通常准备三类测试用例典型场景、边界场景、恶意或异常输入。典型场景就是最常见的情况比如“我要退订单ORD-12345678的款东西有质量问题”期望输出refund/high/escalatetrue。边界场景比如邮件内容模棱两可同时涉及咨询和投诉看模型能不能合理兜底。异常输入比如空邮件、纯广告、乱码看技能会不会崩溃或者瞎输出。测试过程里我发现了个很有意思的问题模型对“spam”类别特别敏感有封客户说“你们的广告邮件真烦人”被分到了spam但实际上这是一封投诉。原因就是我给的广告定义太宽了。后来把描述改成“spam仅限无关的商业推广内容不包括用户对服务本身的评价”问题才解决。另外还有一个效率优化点。参考文件里response_templates如果内容太长每次调技能都加载会很浪费。后来我改成模板不常驻只有模型判定“需要回复模板”时才通过一次工具调用从文件里读。这样大多数简单咨询的邮件不需要加载大段模板上下文消耗降低了很多响应速度也上来了。4. 常见问题排查与技巧实录4.1 技能触发不准该触发没触发不该触发乱触发这是所有技能开发者的第一大痛点。我排查这类问题有一套自己的顺序。先看description是否写清楚了正向场景和负向边界。很多人的description只有“用于处理客户邮件”这种级别模型根本不知道“什么时候算处理邮件”。我会把description写成“当用户发送邮件内容、转发邮件、粘贴工单消息或要求对一段客户沟通记录进行分析时使用”。负向边界更关键“用户仅询问如何写邮件时不使用、用户要求翻译邮件时不使用。”然后看技能数量。如果系统里挂了二三十个技能模型的选择负担指数级上升。我试过一个项目挂了25个技能单是技能选择这一层就频繁出错。后来把部分技能合并成“大类技能内部路由”技能数量降到9个触发准确率显著提升。最后要记得看模型本身的版本和推理能力。触发判断依赖模型的语义理解弱模型对微妙区分的把握确实不够。如果技能已经做了极致简化还是频繁误判可以考虑换更强的模型来做路由层或者用上一层的分类器先把意图粗分类再进入技能选择。4.2 上下文膨胀与技能加载影响有一次压测我发现一个简单的查询请求消耗了6000多个token。一查链路原来是Agent先触发了一个通用文档技能加载了一份超长的使用手册然后发现技能不适用又触发另一个技能又把第二份文档加载了一遍。等于一个问题加载了两份大文档其中一份完全是浪费。这个问题的根治方法是分层加载。SKILL.md作为第一层控制在500词以内细节文档作为第二层通过按需读取的方式访问示例长文本作为第三层只在明确要求时提供。我还额外给每个技能设了一个“加载风险”标记如果技能体积超限在元信息里强制要求模型先自我确认“触发该技能是否必要”多一道确认能减少不少误加载。另外提醒一个细节技能内的输出格式定义要非常明确。有些技能输出的字段顺序一旦不固定下游解析就要写出一堆兼容逻辑这也是隐性的上下文开销。4.3 技能版本演进与组织架构技能开发和普通软件开发一样会面临维护问题而且更加隐蔽。我踩过的坑是一个技能文件更新了但引用它的其他项目还在用旧版本出了一次上线事故后我把技能仓库纳入了版本管理每个技能目录里都放一个CHANGELOG文件记录每次改动的时间、原因和影响面。另一个经验是技能要控制“粒度”。太粗的技能什么都能干但什么都干不精太细的技能数量爆炸维护困难。我现在判据是一个技能至少包含两步不同的决策或动作同时它的所有能力都围绕一个明显主题。满足这两点粒度才合适。在不同项目间共享技能时我有几个强制规范技能代码不写死业务常量全部走配置技能输出使用统一JSON格式字段名要起得自解释技能日志必须输出关键决策点方便回溯。这些规范最初落实的时候有点麻烦但撑过前两个项目后面就全是收益。4.4 实用的调试技巧速查表排查Agent技能问题我建议工具方面准备好三件套日志记录、回放测试和干扰排除。日志记录指的是技能调用的完整trace包括哪一步加载了技能、模型输出是什么、后续工具返回什么。回放测试就是把线上真实输入保存下来改完技能后反复跑同一批测试看行为变化。干扰排除则是把所有技能都禁用只留当前排查的那一个确认它不是被其他技能影响。下面这个表可以当作速查手册遇到问题直接对照过一遍症状常见原因排查动作技能未被触发description场景覆盖不全补充触发场景正例和负例技能选了但没效果SKILL.md步骤不够具体按执行流程逐步写清每个动作输出格式混乱格式定义不明确在SKILL.md给出严格JSON模板上下文耗太多技能体积过大或误加载拆分长文档按需读取同类问题时好时坏示例覆盖不足补充典型与非典型示例各2-3条技能间互相干扰边界重叠明确边界减少技能数量5. 技能工程的组织与落地经验单独做好一个技能只是起点真正让agent-skills发挥威力的是把它当作一套体系来经营。我在公司内部推了这么几件事效果都还不错。第一个是建设统一技能仓库。所有技能文件放在同一个仓库里按领域分子目录客服类、数据分析类、内容生成类、代码辅助类等。每个技能有统一的README、CHANGELOG和版本号新项目引入技能就像装依赖一样直接声明“我要用到哪几个”。第二个是技能评审机制。新技能上线前至少由另一个工程师做一次代码评审和一次效果评审。代码评审检查结构、性能和安全性效果评审拿一组标准测试用例跑一遍确认技能输出稳定达标。这个流程很轻量但对质量底线的保障非常有效。第三个是建立技能性能看板。每个技能记录调用次数、平均token消耗、成功率、人工干预率。通过数据看板很快就能识别出哪些技能是高频高价值的哪些技能虽然没人用但一直在消耗哪些技能经常被触发却总是输出不达标。数据不会骗人在数据指导下做减法比凭感觉优化强得多。我已经看到这个思路在社区里被广泛讨论工具链也在快速成熟。主流的Agent开发框架几乎都已经支持技能机制开发环境也出现了专门用于调试技能的可视化工具。可以做预判的是技能工程会成为智能体应用的“工程化底座”就像当年的MVC之于Web开发。为它打好目录结构、写清文档、定好规范等生态成熟时可以平滑迁移这些投入会变成长期资产。最后分享一个我在实际摸索中沉淀下来的经验技能设计得好不好有一个很简单的判断标准——把一个没有读过代码的新人叫来让他只靠SKILL.md和几天示例来“扮演”这个技能如果他能稳定做出接近你预期的输出技能就是过关的如果他频频提问、不知所云那就是说明文档失败了。这套标准比任何测试指标都直观。Agent的能力边界一直在变但“把一件事讲清楚”这件事的工程价值从来没有变过。