☰
Agent Skills实战:用技能文件解决Prompt工程难题
2026/9/26 8:35:22 网站建设 项目流程

年初我在做一个客服工单自动分类 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.yaml

2.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 --pretty=format:"%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 方案——我相信你会有立刻把其他任务也技能化的冲动。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询