资讯详情

Agent Skills实战:用技能文件解决Prompt工程难题

📅 2026/9/26 8:35:23 | 华诺云谱 👁 阅读
Agent Skills实战:用技能文件解决Prompt工程难题
年初我在做一个客服工单自动分类 Agent 的时候被同一个问题反复折磨系统提示词system prompt越写越长从 800 字膨胀到 3000 字模型依然会在某些边界 case 上犯糊涂。今天要求它生成 SQL明天让它提取客户意图后天又要它写邮件草稿——所有规则堆在一个 prompt 里改一处另外几处的行为就跟着漂移。后来我把目光转向了 agent-skills 这套思路也就是把模型需要执行的某类任务封装成独立、可复用、按需加载的技能文件。这篇文章不聊概念 PPT而是把我从 prompt 泥潭里爬出来的完整过程写下来包括技能文件怎么写、目录怎么组织、模型为什么该触发时不触发以及我踩过的几个实打实的坑。如果你也在做 Agent 应用已经觉得单一 prompt 撑不住越来越复杂的任务或者团队里几个人各自维护一套祖传提示词那这篇文章大概率对你有用。Agent Skills 不是换汤不换药它实际上是把你脑子里那套怎么判断、怎么分步、怎么输出的经验结构化地交给模型按需调用。下面直接进入正题。1. 为什么 Agent 突然需要技能——Prompt 工程解决不了的三个问题1.1 长 prompt 的维护灾难先说我遇到的最直观的问题prompt 一长维护成本不是线性增长而是指数级增长。早期我给工单分类 Agent 写需求时把分类规则、输出 JSON 格式、SQL 生成规范、语言风格、禁忌清单全部塞进一个 system prompt。上线第一周没事第二周产品经理说分类逻辑要调整——好我改了中间一段。结果第三周发现模型开始给部分工单返回多余字段仔细排查才发现是我改分类规则时不小心让新增的示例和后面的格式要求产生了冲突。这种问题在纯 prompt 方案里几乎无解。你没有一个清晰的模块边界所有规则共享同一个上下文窗口互相干扰是常态。Agent Skills 的思路则是把每类任务的完整处理方案隔离成独立文件只有任务真正触发时对应文件的内容才会被注入上下文。改分类逻辑就只动分类技能文件SQL 生成技能完全不受影响。1.2 任务边界模糊模型陷入了既要又要第二个问题更隐蔽。一个 Agent 同时承接多种任务时模型经常犯一种错误——把任务 A 的处理流程误用在任务 B 上。我的客服 Agent 曾出现过一个经典翻车用户问订单状态模型调用了分类技能然后按分类技能的规则输出了一堆意图标签和置信度而不是直接回答订单状态。原因很简单分类技能在 prompt 里的描述优先级太高模型判断用户输入了一句需要理解意图的话就触发了它。Skills 的触发机制不一样。每个技能文件开头有一段精心设计的描述description模型先读所有技能描述再决定调用哪个。这个先描述、后内容的结构天然把什么时候用和怎么用分开了模型误判的概率大幅下降。当然描述怎么写也有讲究我后面会专门讲。1.3 上下文预算的浪费你让模型背了一整本书第三个问题关乎成本和质量。大家应该都有感觉模型表现最好的时候往往是上下文干净、任务清晰的时候。你塞给它 3000 字 prompt其中 2000 字和当前用户提问毫无关系——这部分不仅浪费 token更糟糕的是它会稀释模型对关键指令的注意力。我做过一个粗略统计客服 Agent 的 system prompt 有 2800 字但单次会话平均真正用到的规则只有 400 字左右。剩下 2400 字全是可能用到但这次没用到的规则。Agent Skills 按需加载的特性正好解决了这个问题一个工单分类技能文件大概 600 字触发时才加载大多数时候上下文是干净的。实测下来单次 token 消耗下降约 38%分类准确率反而提升了近 6 个百分点。这就是把冗余背景知识改成精确任务知识带来的收益。1.4 agent-skills 到底改变了什么简单一句话它把 Agent 从一个巨大的、什么都会一点的脑子变成了一个会挑工具的师傅。脑子仍然重要但师傅真正厉害的地方是——看到什么活知道该抽哪把工具工具用起来又是另一套完整的手艺。Skill 就是那把工具而 SKILL.md 是刻在工具上的使用说明书。后面所有内容都是围绕这个说明书展开的。2. SKILL.md 解剖一个技能文件的四个关键层Skills 通常以目录形式组织核心是每个技能目录下的 SKILL.md 文件。我先展示一个我实际在用的目录结构再逐层拆解。skills/ ├── release-notes/ │ ├── SKILL.md │ └── templates/ │ ├── weekly.md │ └── hotfix.md ├── sql-review/ │ ├── SKILL.md │ ├── rules/ │ │ ├── idx_naming.md │ │ └── join_limit.md │ └── examples/ │ ├── bad.sql │ └── good.sql └── customer-triage/ ├── SKILL.md └── labels.yaml2.1 frontmatter技能的身份证每个 SKILL.md 开头是一段 YAML 格式的元信息类似下面这样--- name: release-notes description: 根据 git commit 记录或 PR 列表生成规范的发布说明。当用户要求生成 release notes、更新日志、版本变更说明或给出 commit/PR 链接并要求总结时使用。不要用于一般的代码解释。 ---这段描述是整个技能最重要的部分没有之一。模型就是靠它来判断当前任务该不该调用这个技能。很多人的 description 写成了这是一个用于生成发布说明的技能这种写法信息量太低。我的经验是description 里必须包含三样东西——任务的行为特征、任务的触发信号、任务的排除信号。触发信号是用户提到 commit、PR、release notes 这些词时排除信号是不要用于一般的代码解释这能有效避免误触发。2.2 正文流程把怎么做写成可执行的步骤frontmatter 之后就是正文标准的 Markdown。这一部分要回答的问题是当技能被触发后模型应该按什么顺序做什么每一步有什么判断标准和产出物。我写正文时习惯用目标-步骤-检查点三段式。先明确产出物长什么样再给出步骤最后在步骤里埋检查点。下面是 release-notes 技能正文的一部分# Release Notes 生成 ## 目标 产出一份按变更类型分组、带 PR 号/commit 短哈希、可对外发布的 Markdown 更新日志。 ## 步骤 1. 收集输入。如果用户给了 commit 范围先执行 git log --prettyformat:%h|%s|%an from..to如果给的是 PR 列表直接解析。 2. 按类型分组feat / fix / refactor / docs / test / chore。每个条目写清做了什么 为什么做句子用过去式。 3. 生成输出。类型顺序固定为 feat 在前chore 垫底每条目格式为 - **类型**: 摘要#PR号 / commit前7位。 ## 检查点 - 如果用户只给了一个 commit 或一个 PR不要使用列表格式直接输出一行说明。 - 如果 commit 信息里有 breaking change 关键字如 !: 或 BREAKING必须在输出顶部单独加 ⚠️ Breaking Change 提醒。 - 如果输入信息不足以判断变更类型标注待补充不要猜。这个结构的价值在于模型不需要自己临场发挥怎么组织一份发布说明它只需要按步骤执行每一步的产出都被约束住了。实际跑下来输出格式的稳定性非常高基本上不需要二次人工整理。2.3 参考文件技能的知识库SKILL.md 可以引用同目录下的其他文件比如模板、示例、规则列表。这相当于给技能配了一个小型的知识库模型在需要时才会读取这些参考文件的内容。2.4 示例与反例给模型一把尺子我在 sql-review 技能里放了一个examples/目录里面全是真实的正反例子。模型看两条good.sql再对照一条bad.sql比读十行文字规则都管用。这个做法强烈推荐后面第 5 章我会重点讲为什么只写规则不写示例是新手最容易犯的错误。3. 实战拆解做一个发布说明生成技能光说不练假把式这一章我把 release-notes 技能的完整设计过程走一遍包括我为什么要这样定义触发边界以及一次真实调用全流程长什么样。3.1 设计触发边界宁可漏不可错技能设计第一步不是写内容而是想清楚什么时候该触发。我给 release-notes 技能定的触发边界是这样的触发用户说生成 release notes总结一下这两个版本的变更本周更新了啥或者直接丢过来一段 commit 范围。不触发用户问这段代码是什么意思——虽然也会涉及 commit但那是代码解释不是发布说明。不触发用户要求写产品更新公告那是营销文案和我们内部技术发布说明的格式差异太大。我在 description 的排除信号里显式写了不要用于一般的代码解释实测下来误触发率降了一半。这个思路值得抄每个技能都明确写出不要用于什么,比只写用于什么更能减少模型的漂移行为。3.2 编写 SKILL.md 正文步骤越具体模型越听话正文部分我遵循一个原则让模型当一个会读说明书的执行者而不是有创造力的自由职业者。前面展示的正文已经比较完整这里补几个我在迭代中加的细节。第一输入源要写清楚。模型天生不知道自己该跑什么命令、该解析什么格式所以我在正文里直接写了git log的命令模板它照着跑就行。第二输出格式要给样例。我在正文末尾加了一个短示例比如## 输出示例 - **feat**: 工单列表支持按优先级筛选#1042 / 7f3a9c1 - **fix**: 修复订单超时误判问题#1038 / b02aa4e - **chore**: 更新依赖版本#1030 / ce21da7有了这个样例模型生成的格式基本不会跑偏。一个技能如果能让模型照着抄,就不需要它照着发挥。3.3 一次真实调用全流程我模拟一次完整调用过程方便你理解技能系统是怎么运转的。这是用户输入用户帮我把 v1.2.0 到 v1.3.0 的改动整理成 release notes我一会儿要发群。 Agent内部判断 1. 读取可用技能列表看到 release-notes 的 description判断整理改动并生成发布说明符合触发条件。 2. 加载 skills/release-notes/SKILL.md看到步骤①要求收集 commit 范围执行 git log。 3. 按正文规则分组、格式化并在发现存在 breaking change 时加了顶部提醒。 4. 输出最终 Markdown。整个过程用户只给了一句话模型内部完成判断-加载-执行-输出。这就是 Skills 的核心体验对模型来说它不再是临场思考这个问题该怎么解决而是照着一份成熟的操作手册执行。3.4 为什么这个技能比一段 prompt更稳同样的事情如果用 prompt 实现我会写你是一个发布说明助手请根据 commit 生成发布说明……——这行字会一直待在 system prompt 里无论用户问什么都占着上下文。而 Skills 方案不同release-notes 技能文件平时不占用任何空间只有当模型判定需要它时才加载。更关键的是隔离性。我后来把 release-notes 技能拆成了 weekly 和 hotfix 两个模板改了 weekly 模板hotfix 完全不受影响。这在纯 prompt 方案里几乎不可能做到——因为所有规则在同一个上下文里你无法精确控制模型只看这部分规则。4. 技能库的工程化粒度、依赖、版本与检测等你写了三五个技能就会面临一个新的问题技能库本身的工程质量。这一章谈谈怎么把技能当成代码来维护。4.1 粒度一个技能该拆多大粒度问题是技能设计里最容易被低估的。我拆技能的判断标准很简单如果两个任务经常被同时触发且共享大量规则就合成一个如果两个任务触发场景完全不同且各自内容超过 500 行就拆开。举个例子最早我把 SQL 审查和 SQL 生成写成一个技能结果经常出现用户只问某条 SQL 写得怎么样模型却把生成规范也读进去了。拆成sql-review和sql-gen两个技能之后各自描述更聚焦触发准确率高了不少。一般来说单个 SKILL.md 的正文控制在 300 到 600 行是合理区间。太短说明里面没有真正的干货太长则说明这个技能可能包含多个子任务、该拆了。4.2 技能之间的引用与依赖当一个技能的产出是另一个技能的输入时比如 release-notes 生成之后用户接着要求按模块拆分我不建议在技能里写如果用户要求 X请调用 Y 技能这种硬编码——实测下来模型并不总是响应这种指令。更稳的做法是在技能文件末尾的相关技能区域列出可能相关的技能名称和触发描述让模型自己判断。比如 release-notes 的 SKILL.md 末尾可以加## 相关技能 - changelog-translate: 当用户要求把生成的发布说明翻译成英文时使用。这样既不会强制模型调用错误的技能又给了它一个可选的路径。依赖关系越是松耦合技能库的维护就越省心。4.3 技能测试与回归验证像测代码一样测技能技能会随需求迭代每次改完都可能引入行为漂移所以我强烈建议给技能建一个最小测试集。我会为每个技能准备 5 到 10 条典型的输入-期望输出测试用例每次改动后把这些用例跑一遍看模型是否还按预期触发、输出格式是否还稳定。这个测试集直接放在技能目录下的tests/文件夹里格式类似### T001: 用户提供 commit 范围 输入: 帮我把 2d3a1c 到 8b9f2e 的改动整理成 release notes 期望: 触发 release-notes 技能输出按类型分组的列表包含 commit 短哈希有人可能会问这不等于是手动回归测试吗没错Skill 的本质就是可回归的 prompt不测它你就不敢改它。我所有技能都经历了改一次、测一轮的循环发布说明技能的准确率就是这么一点点磨上去的。4.4 命中率与误触发的度量最后是度量。我自己的做法是给每个技能日志里打两个标记skill_triggered和skill_used。前者表示模型加载了这个技能后者表示最终输出真的用到了技能内容。两者之差就是加载了但没用上的浪费情况。理想状态下命中率skill_used / skill_triggered应该在 85% 以上。如果低于这个数优先检查 description 是否太宽泛导致模型什么任务都往这个技能上靠如果一个技能经常该触发却没触发优先检查 description 里是否覆盖了足够的触发信号。5. 我在真实项目中踩过的五个坑这一章全是实战教训每一条都是真金白银换来的。5.1 描述写得太抽象模型不知道该不该用我第一次写技能描述时写的是这个技能用于处理与客户相关的任务。结果模型几乎从不触发它——因为客户相关太模糊了工单分类、邮件回复、投诉分析都算模型一犹豫就不调用了。后来我把描述改成包含明确的触发动词和名词当用户要求对工单/反馈进行分类、打标签、统计类别占比时使用触发率立刻就上来了。5.2 技能加载后输出被带偏还有一个翻车现场我给客户工单分类技能里加了很详细的标签定义结果模型输出分类结果时把标签描述里的冗长说明也带出来了导致输出 JSON 里出现大段多余字段。原因是我把给模型补背景知识和给模型规定输出格式混在同一个段落里了。现在的做法是背景知识放最后输出格式放最前并且用分隔线隔开模型参考优先级明显不同。5.3 示例中的坏例子反而教坏了模型一开始我在 sql-review 技能里只放了一个 bad.sql本意是告诉模型这种写法不对结果模型反而模仿了坏例子的风格生成一堆带问题的 SQL。后来我调整了示例结构每个 bad 例子旁边必须有配套的 good 例子并且明确标注该写法被拒绝的原因和推荐的替代写法。坏例子和好例子同时出现模型才能学会对照。5.4 技能拆得太碎模型频繁切换导致上下文混乱我把客户反馈处理拆成了客户情绪识别工单分类回复起草三个技能结果一个简单的工单任务模型要连续加载三个技能中间还要来回切换上下文token 反而更贵了而且切换过程中容易丢失信息。这个教训是技能拆分的粒度要跟用户任务的自然边界走而不是跟子步骤走。情绪识别、分类、起草本质上是同一个处理流程的三个阶段合成一个技能才是对的。5.5 只写规则不写示例模型永远跟你隔着一层这一点是最抽象也最关键的。文字规则无论写得多细模型始终存在理解偏差但一个具体示例几乎是零偏差的。release-notes 技能加上了输出示例之后生成结果的格式一致性从 72% 直接跳到 95%这是我在所有技能优化里单次收益最大的一次改动。以后你写任何技能正文可以没有长篇大论但一定要有示例一个不够就放两个。6. 工具类技能与团队协作从个人技巧到团队资产6.1 技能与普通工具调用、MCP 的关系聊到这里你可能有个疑问技能和工具调用function calling、MCP 服务到底什么关系我用一句话区分技能是教模型怎么思考一个任务工具是给模型提供一种执行能力。MCP 服务提供数据库查询、发邮件、调外部 API 等可执行动作而技能是告诉模型接到什么任务时该用什么动作、按什么顺序、产出什么格式。一个好的 Agent 通常两者都需要技能负责编排和规范工具和 MCP 负责执行。技能文件里可以写调用send_email工具发送草稿但由流程规则决定什么时候调用、发什么内容。6.2 把技能库当成团队代码库维护当技能数量超过 10 个我就会建议把它纳入 Git 版本管理并且和代码走同样的 review 流程。技能的改动会影响 Agent 行为本质上就是改代码。我的团队里现在有强制约定任何技能改动必须附带测试用例的更新并且要贴一次改动前 vs 改动后的实测输出对比。有了这套流程技能库才能从个人的小工具变成团队的稳定资产。6.3 个人实践中的体会做 agent-skills 大半年我最大的体会是技能化的本质是把模型的临场发挥变成模型的按图索骥。模型依然有创造力但关键任务的处理方式被你用结构化的方式固化下来了结果就是稳定、可控、可回归。如果你正在做 Agent 应用不妨从手头最常做的那类任务开始把它封装成第一个技能跑上几天再对比一下之前的 prompt 方案——我相信你会有立刻把其他任务也技能化的冲动。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑