Agent Skills 设计:让大模型 Agent 稳定执行任务的关键工程实践
如果你最近在折腾 AI Agentagent-skills 这个词应该不陌生。它不是一个具体框架的名字而是过去一年里 Agent 工程化最值得单独拿出来讲的一层东西。简单说一个 Agent 能不能稳定干活不取决于它接入了多少个工具而取决于它有没有把某个任务该怎么做、边界在哪、输出什么格式固化成一整套可复用、可评估、可升级的技能资产。我在一个自动化项目里因为没搞懂这层连续踩了两周坑今天想把它彻底聊透。这篇文章会从概念、结构、描述词、实战案例、多 skill 冲突处理到长期维护的安全问题一次性讲完。适合两类人一是正在搭建自己 Agent 应用、但总觉得效果忽好忽坏的开发者二是技术管理岗位上要给团队定 Agent 落地规范的人。看完之后你至少能回答一个问题agent-skills 到底该怎么设计才不会让 Agent 变成一堆工具乱飞的多头怪。1. 为什么 agent-skills 值得单独拆出来聊1.1 没有 skill 的 Agent通常是这个画风先还原一个真实场景。几个月前我的项目里接了一个大模型 Agent给它配了十几个工具有查数据库的、有发邮件的、有解析 PDF 的、有做日历提醒的。刚开始觉得很强大什么都能干。但用了一个星期之后发现它就像一个大促期间的客服新手每个工具都会用但经常用错时机。问它下周三的会议改成几点合适它可能先去查了数据库里的用户表让它总结一下这份合同的风险条款它可能在回复里带着 PDF 解析工具的原始输出。问题不在模型本身而在它缺少一层行为规范——它不知道每个任务对应什么动作、按什么顺序做、输出成什么结构。这正是 agent-skills 要解决的核心矛盾大模型擅长理解语言但不懂业务流程工具擅长执行特定动作但不会自我约束。skill 就是夹在两者之间的操作手册把业务流程翻译成模型能稳定执行的指令。1.2 skill 与 prompt、tool、workflow 的分工很多人容易把 skill 跟三个相邻概念搞混prompt、tool、workflow。我用自己的话说清楚他们之间的边界。prompt是一次性的、面向对话场景的提示词它不区分任务类型也不会自动复用。你这次写得好下次换一个话题又得重新写。tool是一个可调用的函数比如发邮件查天气它解决的是能不能做的问题不解决什么时候用、怎么组合用的问题。workflow是固定的流程编排比如先查库存再生成订单再发通知它的缺陷是太死板遇到流程外的情况就卡住。skill则是把特定任务的触发条件、执行步骤、输入输出约束、备用方案打成一个包让模型在合适的时候自动发现并使用它。它比 prompt 更工程化比 tool 多了判断逻辑比 workflow 更灵活。可以这么理解tool 是工具箱里的扳手workflow 是流水线上的固定工序skill 则是一个老师傅遇到什么情况就知道该拿哪把扳手、怎么拧、拧完怎么检查的完整经验。1.3 一个 skill 要解决的三个核心问题基于上面的背景我认为一个合格的 agent-skills 体系必须回答三个问题什么时候用模型怎么知道当前这个用户请求属于这个 skill 的职责范围怎么用模型拿到输入后应该按什么顺序执行哪些步骤调哪些工具如何兜底如果执行过程中遇到缺失信息或工具报错skill 应该怎么处理这三个问题听起来简单实际设计时几乎每个都会踩坑。尤其是第一个什么时候用它直接决定模型是否误召而误召率高了之后整个 Agent 会变得完全不可信。后面我会专门用一节来讲描述词的设计。2. 一个 skill 的解剖结构与完整生命周期2.1 一份合格的 skill 文件长什么样先抛结论一个标准的 skill 文件本质上是一份带有自我说明的 YAML 或 JSON 文档里面包含元信息、输入输出契约、执行指令三大部分。我写过一个最简但完整的会议纪要归档类 skill结构大概是下面这样id: meeting-minutes-archiver name: 会议纪要归档 version: 1.3.0 description: 当用户提供会议转写文本、录音转写稿或会议对话内容并希望提取 决定事项、待办任务、责任人和截止时间时使用。也适用于需要把 会议信息整理成结构化记录并保存到指定表格的场景。 不适用于纯闲聊、非会议内容的文本总结、不含任务分配的活动安排。 input: meeting_transcript: string # 必填会议转写全文 meeting_title: string # 可选会议主题 meeting_date: string # 可选会议日期默认当天 output: format: json schema: summary: string decisions: array[string] action_items: - owner: string task: string due_date: string steps: - 读取 meeting_transcript识别参会者角色 - 逐段提取所有决定事项去重后放入 decisions - 提取所有带责任人的待办任务补齐截止时间 - 将结果组装成 JSON校验 schema 后返回 fallback: - 如果输入内容不是会议记录立即返回 error_code: NOT_MEETING - 如果缺少 meeting_transcript返回 error_code: MISSING_INPUT这一份文件里已经有完整的信息模型通过description判断自己该不该接管这个请求通过input和output知道该拿什么、该给什么通过steps知道执行顺序通过fallback知道异常情况怎么处理。2.2 从注册到执行的四个阶段skill 并不是写出来就自动生效的它的完整生命周期可以拆成四个阶段。第一阶段是注册。把 skill 文件放到 Agent 能扫描到的技能目录里或者在某配置文件中登记。这个阶段最常见的错误是skill 文件语法没问题但 id 重复了导致后加载的覆盖先加载的。我建议每个 skill 的 id 使用团队名-功能名的命名规则比如team-a-meeting-archiver降低冲突概率。第二阶段是选择。当用户输入到达时Agent 会结合所有候选 skill 的描述词做一次语义匹配选出一个或几个最相关的 skill。这个阶段最影响体验我见过不少生产事故都发生在选择阶段——模型把任务派给了看似相关、实则错误的 skill。第三阶段是执行。被选中的 skill 会把它内置的步骤和工具调用逻辑注入当前上下文模型开始一步步执行。这个阶段的问题通常是步数过多导致 token 膨胀或者模型在中间步骤擅自偏离了 skill 预设的约束。第四阶段是回归。执行完成后应该有一个轻量级的校验环节确认输出格式符合 schema必要时对结果做格式化修复。很多团队省掉了这一步结果就是模型偶尔给出的 JSON 字段少了一个逗号下游系统直接崩。2.3 别把 skill 写成一个挥舞所有工具的八爪鱼这是我在实际开发中总结出的一条最重要原则一个 skill 只做一件事。我见过有人把一个超级助手 skill 写了三十多个步骤既能查天气又能写周报还能订会议室。表面上看很高效实际运行起来一塌糊涂——因为模型在这么长的指令里经常迷失重点用户实际上只是问了一句今天几度它却把后面二十步全执行了白白烧掉大量 token。正确做法是拆细。宁可多设计几个专用 skill也不要让一个 skill 去覆盖多个职责区间。每个 skill 的职责越窄描述词越容易写选择阶段越不容易误判后续维护也越轻松。我现在的项目里有 40 多个 skill每个都是单一职责整体稳定性比之前那个全能怪高了一个量级。3. 描述词是 skill 的命门决定它在正确时机被选中3.1 为什么模型看不见你已经写好的 skill很多人写完 skill 后遇到一个奇怪现象Agent 明明挂了某个 skill但用户提出相关请求时它就跟完全没这回事一样非要自己做一遍或者调用了别的工具。问题往往出在description字段上。在大多数 Agent 框架里模型选择 skill 的依据就是这一小段描述词的语义相关性。它不是读完整的 skill 文件再判断而是先把所有 skill 的description当作目录浏览一遍再决定展开哪个。如果你把描述词写得太笼统比如处理会议相关任务模型就很难区分它和另一个日程管理 skill 的边界。更常见的坑是描述词只写了做什么没写不做什么。模型在语义接近的两个 skill 之间犹豫时缺乏明确负面约束的一方更容易被误选。3.2 五种必须写进描述词的信息我在实践中总结出一套描述词模板覆盖五种信息任何一种缺失都可能导致选型异常。第一任务触发条件。用户说什么话、提交什么内容时这个 skill 应该被触发。越具体越好直接写当用户提供xxx时使用。第二强烈负面条件。明确写不适用于什么情况。这是很多团队忽略的但它对抑制误召非常关键。第三输入边界。告诉模型这个 skill 需要哪些输入哪些输入是可选的。这能避免模型在缺失关键信息时就鲁莽执行。第四,输出预期。简要说明返回的是结构化 JSON、文本还是结果表格让模型在进入执行前就有预期。第五典型使用意图。写一个高度概括的使用场景示例比如把周会转写稿整理成待办并写入项目看板帮助模型建立场景关联。下面是我常用的一段描述词示例你可以直接参考这五种信息的组织方式description: 当用户给出会议转写文本、录音转写结果、聊天记录等素材并要求 提取决定、任务、责任人、截止时间或要求生成结构化会议纪要时 使用本技能。 也适用于帮我整理今天开会的内容这类需要从对话中恢复会议信息的请求。 不适用于普通文章总结、不含会议性质的对话摘要、 需要安排日程但不涉及提取任务的请求。对比一下很多项目里经常出现的劣质描述词处理会议相关内容。二者在误召率上的差距非常明显。3.3 一个反面案例的修改全过程我有一个真实案例可以分享。之前我做了一个风险条款扫描 skill最初的描述词是这样写的description: 扫描合同文本中的风险条款。结果上线后误召率极高。用户发来一份普通产品说明文档说帮我看看有没有风险模型也把这个 skill 调起来了输出一堆不相关的内容。我后来把它改成description: 当用户提供合同、协议、法律函件等具有法律约束力的文档内容并 希望识别其中可能带来赔偿责任、违约责任、知识产权争议、解约条件 等风险条款时使用。适用于这份合同有没有坑帮我审一下协议等请求。 不适用于普通产品文档、营销文案、无法律效力的说明材料也不适用于 需要生成新合同条款的写作任务。改完之后误召率从肉眼可见的高频降到了几乎不再出现。经验就是描述词里的不做清单往往比能做的事情列表还重要。4. 实战让 Agent 学会会议纪要归档这类技能4.1 定义输入输出边界先写出一份契约空谈理论没有意义我们直接做一个完整可落地的案例。假设现在要给 Agent 新增一个技能把会议转写文本整理成结构化会议纪要并归档到项目文档库。做之前先定契约输入会议转写文本必填字符串、会议标题可选、会议日期可选输出固定结构的 JSON至少包含summary、decisions、action_items三个字段成功标准能从一段 3000 字左右的转写文本中准确识别所有决定事项和带责任人的待办任务失败标准输入内容不是会议记录时不能强行执行缺少必要字段时不能编造契约写清楚之后整个开发过程会顺畅很多。很多人拿到需求就开始写 prompt写出来的东西自然不稳定就是因为没有先定义输入输出的边界。4.2 落地一份可直接用的 skill 定义基于上面的契约我直接给出一份完整的 YAML 定义。你可以把这份内容理解成一个作业模板改一改就能用到自己的场景里。id: meeting-minutes-archiver name: 会议纪要归档 version: 1.0.0 description: 当用户提供会议转写文本、录音转写稿、钉钉或飞书会议记录 并要求提取决定、任务、责任人、截止时间、生成会议纪要时使用。 适用于帮我整理会议内容记录一下开会结果等请求。 不适用于普通聊天记录摘要、非会议材料的文本总结、 不包含任何决策或任务分配的日常对话。 input: transcript: type: string description: 会议转写全文 required: true title: type: string description: 会议主题缺省时从文本推断 required: false output: type: json schema: meeting_title: string meeting_date: string summary: string decisions: type: array items: string action_items: type: array items: owner: string task: string due_date: string steps: - 判断输入是否属于会议记录若不具备会议特征走 fallback - 识别会议主题、日期、主要参与者 - 逐段提取讨论结论合并去重为 decisions - 提取所有负责人 待办 截止时间三元组写入 action_items - 生成 150 字以内的 summary聚焦结论而非过程 - 按 output.schema 组装 JSON fallback: - NOT_MEETING: 返回错误码 NOT_MEETING并说明需要提供会议材料 - MISSING_INPUT: 如果 transcript 为空返回错误码 MISSING_INPUT这份定义给模型提供了完整的行为约束。我实际用下来最关键的其实是fallback段——它让模型在遇到异常时有路可走而不是硬着头皮继续执行。4.3 联调时我遇到的两个反复横跳问题写完之后进入联调阶段这里有两类问题最折磨人。第一类是输出结构不稳定。模型有时候把action_items输出成对象而不是数组有时候due_date给了个相对时间比如下周五而不是具体日期。解决思路是在steps里增加一条硬约束所有时间字段必须先转换为 YYYY-MM-DD 格式转换失败时使用 unknown 填充同时在输出校验逻辑里加一个轻量级后处理遇到结构不匹配时自动修复。第二类是过度执行。有一段测试文本其实是产品介绍不是会议记录但模型仍然尝试提取行动项结果造出了三个看起来像任务的项目。这就是描述词边界没写清楚。我后来在描述词里加了一句如果文中没有明确出现‘决定/结论/待办’等指向必须返回 NOT_MEETING这个问题才彻底解决。联调阶段不要只看一次两次的输出就下结论。我会准备至少 5 份覆盖不同情况的测试样本包括正常会议、只有讨论没有结论的聊天、包含多个子议题的长时间会议、输入为空、带噪点文本等逐项跑一遍再决定是否上线。5. 多 skill 共存时冲突和误召回怎么收敛5.1 两个 skill 抢活的真实复盘当 Agent 挂载的 skill 数量增加到 10 个以上选择阶段就不再是有没有选到正确 skill的问题而是会不会同时选中好几个 skill、互相抢活的问题。我遇到过最典型的案例是会议纪要归档和待办事项管理两个 skill 的冲突。用户说把今天开会提到的事情记到待办里结果两个 skill 同时被选中。会议纪要归档 skill 把整段文本解析了一遍待办管理 skill 又把里面的任务提取了一遍最后回复里出现了两套结构不同的内容用户完全看不懂该以哪个为准。复盘后发现根因是两个 skill 的描述词包含了大面积的语义重叠会议任务待办提取这些词在两边都出现了。模型无法通过描述词判断到底该把控制权交给谁。5.2 用显式负面语义和路由前缀给 skill 划地盘解决这类冲突我总结出两个有效手段。第一是显式负面语义。在两个互相竞争的 skill 描述中分别加入针对对方的排除语句。会议纪要归档 skill 里加一句本技能不负责把任务写入待办系统只负责提取和生成结构化纪要待办管理 skill 里加一句本技能不负责解析会议原文只负责接收已经整理好的任务描述并写入系统。这样模型在语义相似时也能靠明确的边界排除干扰项。第二是路由前缀。如果 skill 实在太多可以在描述词里给每个 skill 打一个语义标签前缀比如[meeting-processing]、[todo-management]、[data-cleaning]并约定用户请求里出现某个前缀时优先选择对应 skill。这个方法的原理是降低语义匹配的模糊性让模型以更接近规则匹配的方式做判断。5.3 版本与灰度改一个 skill 不该影响其他任务skill 升级也是一门学问。我曾经犯过一个错误把会议纪要归档里提取任务的方式升级了一下结果影响到了项目周报生成 skill 的调用。因为周报生成也依赖会议纪要的结果而新版纪要把due_date字段从纯文本改成了对象结构周报生成读取时就崩溃了。从那以后我强制要求每个 skill 必须带version字段且升级时需要保留旧版本至少一周。上线新版本后先做灰度也就是让新版本只处理一部分请求观察调用命中率、输出格式正确率、下游报错率确认稳定后再全量切换。如果多个 skill 之间存在依赖关系更要先检查依赖方的数据格式兼容性。这个版本灰度的经验可能看起来有点重但只要你的 Agent 要接入真实业务流程它就是必须付出的管理成本。6. 长期维护会把两个问题放大上下文成本与执行安全6.1 全量注入 skill 的 token 消耗比你想象的大skill 数量一旦多起来第一个直观问题就是 token 成本暴涨。每个 skill 的描述词加上完整步骤可能在 500 到 1500 token 之间如果你做的是把所有 skill 全量注入模型上下文的方式20 个 skill 就是 2 万到 3 万 token。这些 token 不一定是用户看得见的输出但每一轮对话都会被计入成本。更要命的是上下文空间是有限的全量注入会挤占用户输入和中间结果的空间导致模型看得完 instructions却没地方放对话历史。我见过一个项目就是这样加了十几个 skill 之后反而连基本的上下文记忆都变差了。6.2 分层加载按场景把 skill 分组常驻我的解法是分层加载而不是全量注入。具体做法是常驻层只放最常用的几个 skill比如身份设定、通用问答、上下文记忆整理数量控制在 3 到 5 个以内。按工作区分组层每个业务域比如会议数据分析客服售后各自维护一个 skill 集合只在该场景下把对应集合注入。按需检索层对于长尾 skill不直接注入描述词而是利用向量检索或关键词匹配在用户请求进来时动态召回最相关的 1 到 3 个 skill再注入现场。这样改完之后我项目的上下文 token 占用几乎减少了一半误召率也下降了。因为模型在同一时刻需要对比的 skill 数量变少了选择难度自然降低。6.3 最小权限原则skill 不该拥有无限权力安全问题是另一个容易被忽视的大坑。你写了一个 skill里面带着工具调用逻辑它就有了替你做某件事的权力。如果这个权力没有边界一旦被恶意或者误用后果会非常严重。我的安全清单有三条硬规定白名单域名和路径skill 内部调用外部 API 时只允许访问预先配置的白名单域名禁止动态拼接 URL 访问任意地址避免 SSRF 类风险。最小文件权限读取文件类 skill 只能访问指定目录下的指定扩展名文件禁止使用通配符读取全盘。写操作二次确认涉及删除、修改、发送、支付等不可逆操作的 skill必须在返回结果里显式提示用户确认不允许模型直接执行。这个清单不是危言耸听我在真实项目中就遇到过 skill 向外部接口发送内部数据的风险幸好当时有白名单拦截否则就是一次安全事故。6.4 我的日常维护清单最后分享一下我现在每个迭代周期固定要做的维护事项。这不算什么高深方法论但非常实用抽查调用命中日志每周挑 20 条真实请求看模型是否选择了预期 skill发现一次误召就去改对应描述词。检查 skill 版本落后看有没有 skill 改了依赖接口但自身版本号没升级的情况。做一次 token 成本审计统计每个 skill 的平均调用 token把排名前 3 的高消耗 skill 拿出来精简步骤或描述词。测试新增 skill 的冲突面任何新 skill 上线前必须拿 3 条可能混淆的请求去测试它是否会抢已有技能的工作。这套维护流程坚持了一个季度之后我的 Agent 项目从能用但偶尔抽风变成了稳定到可以交给业务同事天天用。这中间的差距几乎全在 agent-skills 这层设计是否用心上。我个人在实际操作中的体会是skill 的设计没有玄学做好边界清晰、描述具体、单一职责、安全兜底这几件事它就能稳定发挥。如果你现在正被 Agent 的能力失控困扰不妨先从检查自己的 skill 描述词开始大概率会发现突破口。