1. 为什么Agent需要"技能"而不是更多提示词
1.1 从一次失败的提示词堆积说起
做智能体工程化做到中期,我踩过一个很典型的坑:为了让Agent在特定场景下表现稳定,我不断往系统提示词里加规则。第二版加了两条输出格式约束,第三版补上了三个负面清单,第四版又塞进去一段示例对话。结果提示词从2K涨到8K之后,模型反而出现了很奇怪的"行为漂移"——旧任务开始变得不稳定,同一个输入在不同轮次里给出完全不一致的处理逻辑。
后来我接触到agent-skills这个方向,才意识到问题根源:我们一直在用"上下文"去承载"能力",但上下文是有限的、静态的、不可组合的。技能(Skills)的思路完全不同——它把Agent要掌握的每一项能力,封装成一个自包含、可发现、可调用的文件夹。Agent平时只带一张"技能清单",真正要用到某件事时,才加载对应的技能说明和脚本。这个思路不是简单的"提示词模块化",而是把Agent的能力从"内嵌在系统提示里"彻底转化为"按需挂载的资产"。
这篇文章我打算结合自己从零搭建技能库的完整过程,把agent-skills的核心机制、技能文件的标准结构、手写技能的实操步骤、以及我在工程落地过程中踩过的坑都梳理一遍。不管你是在做Claude、GPT这类大模型应用的Agent编排,还是自己基于开源框架搭智能体,这套方法都值得参考。
1.2 技能、工具、插件:三者的边界到底在哪
社区里经常有人把Agent Skill、Tool(工具)、Plugin(插件)混着说,它们在工程语境下确实有交叉,但定位完全不同。我做个表格说明:
| 维度 | Tool(工具) | Plugin(插件) | Skill(技能) |
|---|---|---|---|
| 本质 | API能力入口 | 软件能力扩展 | 方法论+过程封装 |
| 载体 | 函数/接口描述 | 插件SDK | SKILL.md + 支撑文件 |
| 触发方式 | 模型决策后调用 | 用户主动启用 | 模型在对话中按需加载 |
| 输出 | 执行结果 | 软件功能 | 完整工作过程 |
| 典型问题 | "AI不会主动拆步骤" | "能力绑定平台" | "描述不清导致误用" |
一个工具解决的是"AI能不能做这件事"的问题,比如给你一个查天气的API;而一个技能解决的是"AI知不知道怎么把这件事做得像样"的问题,比如给你一套"天气信息整理成穿衣建议"的完整流程、判断规则和输出模板。
技能本质上做的是三件事:提供决策上下文、约束思考链路、固化产出标准。它不需要调用任何外部API,却能让Agent在处理某个任务时按照你预期的路径走完流程。这一点是我在用了很久之后才真正理解的:Skill不一定需要"动手"的能力,它更多的是一种内化和外化的"经验",让模型不必从零开始想任务该怎么做。
2. 技能的标准结构:一个技能就是一个自包含的文件夹
2.1 SKILL.md从哪来,为什么要用Markdown
Agent需要一种"能读懂的说明书"来理解一个技能该怎么用。在agent-skills这个体系里,核心文件约定为一个叫SKILL.md的Markdown文件。选Markdown而不是JSON或YAML是有原因的:
- Agent的语义理解对自然语言最友好,Markdown天然有标题、列表、代码块这些结构信号,模型可以快速定位"何时用"和"怎么用"这两个最关键的信息块。
- Markdown可以内嵌代码示例,这对"给模型看"特别重要。你可以在里面贴一小段输入输出示例,模型马上能get到这个技能的工作模式。
- 它是纯文本,diff友好,放Git里做版本管理、同行评审都很自然。
SKILL.md的头部是一个YAML frontmatter,用来登记技能的元信息。最核心的字段是name和description。这个description不是给你看的,是给Agent的调度决策器看的——模型会先扫描所有技能的description,判断当前对话是否命中某个技能。所以description的写法直接影响技能能被多大概率正确触发。
2.2 一个技能目录里到底该放什么
先看一个我实际在用的技能目录结构,以"代码仓库结构梳理"这个技能为例:
skills/ repo-mapper/ SKILL.md scripts/ map_tree.py templates/ report_template.md reference/ common_ignore_patterns.md architecture_questions.md各部分的角色:
- SKILL.md:技能的主说明书。必须包含"何时使用本技能"、"工作流程"、"关键规则"、"输入输出格式"这几大块。
- scripts/:需要执行的具体脚本。技能不一定要有脚本,但如果这个技能需要读取文件、调命令、做数据转换,就放在这里。
- templates/:输出模板。比如要求Agent按某种格式输出报告、文档时,模板文件可以让输出结构保持一致。
- reference/:参考知识。这是给Agent在技能执行过程中按需读取的资料,用来补充上下文,而不是一开始就被全部吞进去。
这样设计的好处是:Agent在决定使用技能时,最初读取的信息量是可控的。SKILL.md会告诉它"如果需要更详细的忽略规则,打开reference/common_ignore_patterns.md"。这样技能内容可以做得非常丰富,但对上下文的初始挤占又很小。关于这一点,第四部分我会详细讲怎么控制"上下文膨胀"。
2.3 description的措辞决定了Agent会不会在关键时刻想起你
这是我认为整个SKILL.md里技术含量最高的一小段。description写得太泛,Agent会在无关场景里误触发;写得太窄,Agent永远想不到用它。
我的经验是,一个好的description应该包含三层信息:任务的典型触发场景、输入的主要特征、输出要达到的目的。举个例子,同样描述一个代码评审技能:
比较差的写法:
代码审查技能,用于review代码。
这种描述在Agent的决策空间里几乎是隐形状态,因为"review代码"这个表述太口语、太宽泛,模型很难识别出"当前这个需求是否应该调用它"。
我实际使用的写法:
负责对指定代码改动进行系统性审查,识别潜在缺陷、安全隐患与性能风险。当用户提供git diff、PR链接或一段待审查代码,且期望得到结构化审查意见时使用。输出结果包含严重程度分级、问题定位、修复建议。
这里的关键词是"git diff""PR链接""待审查代码"——这些都是模型在对话上下文中容易捕捉到的实体;而"结构化审查意见""严重程度分级"则界定了输出形态,让模型判断当前任务与技能的匹配度。
3. 从零手写第一个技能:发布说明转Changelog
3.1 为什么第一个技能选"Changelog生成"
对于一个刚接触agent-skills的人来说,最好的练手项目应该满足三个条件:输入容易获取、输出有明确标准、流程不需要外部系统依赖。"发布说明转Changelog"完美满足这三点。你只需要一段git提交记录,就能完成完整的技能编写、调试和验证闭环。
我们先看一下这个任务在没有技能时是什么状态。你让一个Agent根据git log生成Changelog,模型通常能给你一个看起来合理的Markdown列表,但细看你会发现:版本号可能会被它自己编出来、日期格式不统一、分类逻辑随心所欲、删改commit信息。原因很简单——模型对这个任务的心智模型来自通用训练语料,而不是你团队的实际规范。技能的价值在于把你对"规范"的定义固定下来,让Agent变成一个不会偷懒的执行器。
3.2 编写SKILL.md的实操与完整示例
在技能目录下新建changelog-writer/SKILL.md。我的完整文件长这样:
--- name: changelog-writer description: 将零散的git提交信息或发布说明整理成符合团队规范的CHANGELOG.md。当用户提供git log文本、提交编号范围,或要求生成、补充、修复Changelog时使用。 --- # Changelog Writer 这个技能用于把开发过程中的零散提交记录整理成结构化的CHANGELOG.md。 ## 核心规则 1. 分组规范:按 Added(新功能)、Changed(变更/破坏性更新)、Fixed(修复)、Removed(移除)四组归类。 2. 版本号规则:遵循语义化版本。破坏性更新必须升级Minor版本,并优先列出。 3. 日期格式:统一使用 YYYY-MM-DD。 4. 不臆造内容:如果commit信息模糊,使用 `[待确认]` 标记并在末尾向用户提问。 5. 合并同类项:同一功能相关的多个commit自动合并为一条,并在末尾附上主要PR编号。 ## 标准输入格式 用户提供以下内容时直接处理;如果提供的是PR列表或发布说明,先提取关键变更点再归类。 ```text git log --oneline v1.2.0..HEAD --no-merges工作流程
- 收集输入源,必要时向用户请求补充git日志范围。
- 将每条记录按关键词预分类:feat/add/feature 归入 Added;fix/bug/patch 归入 Fixed;breaking/refactor/change 归入 Changed;remove/drop 归入 Removed。
- 对分类结果做同义聚合,去除重复条目。
- 按严重程度排序:Breaking Change 在最前,其次 Added,最后 Removed。
- 输出完整的一版CHANGELOG.md,包含版本号和日期。
输出模板
## [Unreleased] ### Added - 新功能A(#PR号) ### Changed - breaking变更B(#PR号) ### Fixed - 修复问题C(#PR号) ### Removed - 移除模块D(#PR号)参考示例
输入:
feat: add user profile page fix: correct typo in login form feat: add profile avatar upload refactor: change data loading to server-side for profile page期望输出:
### Added - 新增用户资料页,包含头像上传能力(#123,#125) ### Fixed - 修正登录表单文案错误(#124)质量检查
在返回任何输出前,逐项检查:
- [ ] 是否每一行都归类到四组之一?
- [ ] 是否存在"待确认"项没有向用户提出?
- [ ] 是否保留了原始PR编号?
- [ ] 日期和版本号是否格式一致?
这里有几个设计细节值得展开: **核心规则区的"不臆造内容"**是最重要的一条。Agent有很强的"补全倾向",你只给它片段它可能脑补出并不存在的功能描述。明确告诉它标记待确认并提问,它才会停下来,而不是编。这也是Agent工程里常说的"约束幻觉的刹车片"。 **参考示例部分的输入对比**是给模型"喂一个few-shot的例子"。LLM在看过一个输入输出对照后,对任务的理解会显著提升。这个示例不用多,一段输入对应一段输出就够了。 **质量检查清单**是我建议大家从第一个技能起就养成的习惯。它相当于在技能末尾放一个"自我校验钩子",让模型在输出前先自检一遍。这个设计看起来简单,但对输出质量的提升非常明显——因为模型的生成过程是序列式的,如果能在最后一步强制它做一次"checklist扫描",很多低级错误会在生成阶段就被拦截。 ### 3.3 怎么测试技能是否真的被"正确触发" 写完SKILL.md只完成了第一步,第二步是测试。我建议在专门隔离的会话环境或测试项目里做三组验证: 1. **直接命中**:给你一个明确的输入,比如直接贴一段git log,问"帮我生成当前版本的Changelog"。看Agent是否主动使用changelog-writer技能。 2. **间接命中**:给一个模糊指令,比如"发布说明这块帮我整理下"。看Agent能否从技能清单里召回正确的那个。 3. **负向测试**:给一个完全无关的需求,比如"写一首关于数据库的诗"。观察Agent是否错误触发了changelog技能。 测试时重点关注第三个场景。我发现很多技能误触发的原因,都是description里的"publish"这个词——模型会把"发布动态""发布文章"和"发布版本"混在一起。解决方式是在description里补充一句"本技能仅适用于软件版本发布场景",给决策器一个明确的排除条件。 ## 4. 技能库的工程化管理:版本、测试与多Agent复用 ### 4.1 技能库怎么分区才不乱 技能数量一多,管理就会乱。我的经验是把技能库分为三个区: - **builtin/**:团队统一的标准技能,所有人共享,不允许个人随意改动。变更必须走评审。 - **community/**:团队内分享但未经严格评审的技能,可以作为builtin的候选。 - **personal/**:个人调试中的技能,方便实验,不影响主工程。 分区的意义不只是目录好看,更重要的是和Git权限、CI流水线绑定。比如builtin目录的改动需要至少一名其他成员的approve,personal目录则可以随时自由提交。这个约定帮我避免了很多人为冲突——你不会想让一个同事刚实验到一半的技能"漂"到生产Agent里去。 ### 4.2 Git版本化与技能依赖的坑 一个技能自己是一个git仓库,还是一整个技能库一个仓库?两种模式我都试过。初期技能少时,整个技能库一个仓库最方便,`git clone`一次搞定;但技能多到十几个后,任何一个技能的改动都会让整个仓库的diff变得混乱,Code Review时也很难聚焦。 后来我切到了"单技能单仓库 + 主仓库用submodule管理"的模式。每个技能repo里放一个`SKILL.md`、一个`version.txt`,版本号遵循语义化版本。主仓库在一个`manifest.json`里记录各个技能的git地址和当前版本号: ```json { "skills": [ { "name": "changelog-writer", "version": "1.2.3", "repo": "git@internal:skills/changelog-writer.git" } ] }这个manifest就是技能的"应用市场索引"。Agent在启动时只读manifest,拿到技能名单;真正决策到某个技能时,再从对应仓库加载SKILL.md和资源文件。
依赖方面最大的坑是技能间的隐式依赖。比如你的changelog-writer技能里用了semver库,而semver的解析逻辑在repo-mapper里维护——一旦两边不同步,行为就会非常诡异。我的建议是:技能内部需要的通用逻辑一律复制到该技能的scripts目录下,不要跨技能共享函数。宁可有点代码冗余,也要保证每个技能自包含、可独立运行。这也是"一个技能就是一个自包含的文件夹"这条原则最重要的理由。
4.3 用自动化测试给技能上个保险
Agent的行为有随机性,所以技能测试我们不能像普通单元测试那样断言精确输出,而是做"关键行为断言"。我用的是一套非常轻量的Python测试框架,核心思路是这样的:
# test_changelog_skill.py from agent_runner import invoke_agent def test_changelog_direct_trigger(): result = invoke_agent( skill="changelog-writer", conversation=[ {"role": "user", "content": "git log --oneline v1.2.0..HEAD --no-merges"}, ], ) assert "### Added" in result.text assert "### Fixed" in result.text assert "正确识别changelog输入" in result.trace def test_negative_no_trigger(): result = invoke_agent( skill="changelog-writer", conversation=[ {"role": "user", "content": "帮我写首诗"}, ], ) assert "changelog-writer" not in result.triggered_skills测试的重点有三:
- 断言输出结构里是否出现了关键分段标题(说明技能被正确执行)
- 断言技能是否在正确的场景被触发(decision trace)
- 断言负向场景是否被正确拒绝
把这套测试挂到CI里,每次技能库更新都会跑一遍回归。我强烈建议从第二个技能开始就同步写测试,而不是等技能多了再补。Agent技能的一个特点就是改动一个SKILL.md的措辞,可能连带影响其他技能的选择行为,而人工回归很难发现这种"隔山打牛"的问题。
4.4 多Agent复用时的配置隔离
同一套技能库,给负责客服的Agent和给负责代码生成的Agent用,配置一定不能一刀切。因为manifest里所有技能都会进入Agent的决策候选列表,即使有些技能跟它完全无关,模型在扫描时也会消耗决策注意力。
我的做法是在manifest里增加一个allowed_agents字段,或者在Agent侧维护一个enabled_skills白名单:
skill_config: - name: changelog-writer enabled_agents: [release-bot, dev-assistant] - name: repo-mapper enabled_agents: [dev-assistant]白名单机制让每个Agent的候选技能保持在 5 到 10 个以内,这是我认为兼顾决策效率和能力覆盖的最佳区间。一旦候选列表超过15个,模型在技能选择上开始出现明显的不稳定,误触发率显著上升。这个数字不是某篇论文给的,而是我实测对比多个模型后的直观结论。
5. 实测踩坑记录:技能冲突、上下文膨胀与调用失灵
5.1 两个技能"抢活":description冲突的真实案例
有一次我给Agent配了changelog-writer和release-notes-generator两个技能。单独测试时它们各自表现很好,结果一起上线后,同一个发布相关的请求,模型一会儿用changelog技能、一会儿用release-notes技能,行为极其不稳定。
排查链路是这样的:
- 我先去看两个技能的description,发现都包含"发布说明""版本"这些关键词,语义重叠度非常高。
- 我怀疑是候选列表排序问题,把其中一个技能的description重写,强调自己"完全基于git提交记录",把另一个强调"基于PR描述与release管理平台数据"。
- 我把混淆场景的测试用例加进负向测试集,确保模型在遇到"PR列表和git log同时出现"的情况下,能明确走向正确的那一个。
- 重新跑回归测试,两个技能的选择准确率从约65%提升到了约90%。
这个问题的根因不是"技能不够多",而是"技能边界不够清晰"。同类技能宁缺毋滥——如果你的几个技能描述之间需要用"但是""除了"来区分,那说明它们在模型眼里是同一个技能,你需要的不是拆分,而是合并或者明确划分输入特征。
5.2 上下文膨胀:技能元信息正在悄悄吃掉你的注意力
有些刚入门的工程师,习惯把非常详细的参考文档整篇内联在SKILL.md里。比如在repo-mapper技能里放了两千行的"架构模式知识库",每次都跟着技能一起被加载。这样做的结果是:Agent的上下文里塞满了高密度但当前用不到的信息,反而干扰了对用户直接意图的注意力。
我解决这个问题的思路是"分层加载"。SKILL.md里只保留精炼的"何时用、核心流程、关键规则、示例",把长篇细节放到reference/或knowledge/目录,并在SKILL.md里给模型一句明确的指导:当需要了解具体的忽略规则时,打开reference/common_ignore_patterns.md。
这种"按需读取"的实践在对话中效果很好,因为模型在资源不够时不会主动去"翻文件",但你告诉它"需要时才打开某个文件",它反而知道什么时候该主动查找。和人类的工作方式很像——你手上不会一直摊着整本操作手册,但你知道手册在第几章,需要时就翻。
5.3 同一个技能换了个模型就失灵
我在Claude环境下调试得非常顺滑的技能,切到另一个开源模型上,出现了明显的"调用率下降"和"输出格式漂移"。后来仔细分析发现,问题不在于技能本身,而在于不同的模型对指令的遵循习惯差别很大:
- 有些模型对"负面清单"(不要做什么)更敏感,对"正面指导"反而执行得随意。
- 有些模型在长文档里只能有效抓住前几个章节,后面的大段规则基本被忽略。
- 有些模型不太擅长自己触发"打开reference文件"这一步,加载了主文件就完事了。
我的应对方案是写技能时尽量用"正面的、指令式的语言"描述期望行为,减少依赖负面清单来表达核心流程;同时在任何重要规则前加上标题层级标记,让模型更容易定位关键区块。真正需要区分模型行为的场景,我会在技能里加一个平台适配段,写明"当你在X平台上运行时,注意……"而不是试图写一套浦适的指令。
5.4 调试技能问题的通用排查链路
这里我总结一下踩过多次坑之后沉淀出的排查链路,如果技能表现不对,我会按照这个顺序走:
| 步骤 | 检查项 | 典型结论 |
|---|---|---|
| 1 | 技能是否被触发 | 没触发 → description与输入特征不匹配 |
| 2 | 如果不触发 | 补全description中的触发场景关键词 |
| 3 | 触发后执行流程是否完整 | 流程断裂 → SKILL.md的工作流步骤太模糊 |
| 4 | 输出是否达标 | 输出不达标 → 核心规则或示例不足 |
| 5 | 输出结构是否每次一致 | 不一致 → 输出模板未硬性约束 |
| 6 | 是否与其他技能冲突 | 冲突 → 检查description重叠与Agent候选技能数量 |
只要按这个链路走,大多数问题都能在十分钟内定位到原因。这也是为什么我一直强调技能测试不能只看输出结果,还要看"触发路径"。
最后再说一个我个人的体会。很多人刚开始接触agent-skills时,会倾向于把一个任务拆成很多个细碎的技能,结果技能之间冲突不断,管理成本反而比写提示词还高。技能应该对应"一个有边界、有明确产出标准的工作方法",而不是"一个细小的动作"。你不需要给每个API调用都建一个技能,但你应该给"怎么做一个合格的代码评审""怎么按团队规范发布版本"这类"有方法论含量的任务"建技能。技能的粒度拿捏准了,整套体系才会越用越顺畅。我现在维护着一个二十多个技能的库,日常新增和调试已经非常顺手,这也是我觉得这个方向最值得投入的地方。