2026/10/11 19:35:17

Agent Skills (Claude Skills) 详细解释一下:从 SKILL.md 到可复用工作流

Agent Skills (Claude Skills) 详细解释一下:从 SKILL.md 到可复用工作流 1. 从一段重复提示词说起Agent Skills 到底解决什么问题如果你最近在折腾 Claude Code、Codex 或者 Cline 这类编码代理大概率遇到过这种场景每次让它帮你写单元测试你都要把同一段话再打一遍——用 Vitest、别用 Jest、mock 放在__mocks__目录、断言风格用expect().toEqual()。第一次写还挺新鲜写到第十次就开始烦了。更麻烦的是团队里每个人写的提示词还不一样产出的测试风格五花八门。Agent Skills在 Claude 生态里常被叫做 Claude Skills就是冲着这个痛点来的。简单说它把「一段固定的指令 可选的脚本和资源文件」打包成一个带元数据的目录代理在需要的时候自动加载。你不用再手动粘贴提示词代理会根据当前任务判断该不该调用这个技能。它和传统的 function calling 有个关键区别function calling 是你在 API 请求里显式声明一堆工具模型从中挑一个调用而 Skills 更像是「按需加载的知识包」代理先看到技能的名字和描述觉得相关才去读完整的SKILL.md正文。这个机制叫渐进式加载progressive disclosure也是它比一次性把所有工具塞进上下文更省 token 的原因。适合谁看这篇刚接触 Agent Skills、想搞明白目录结构和SKILL.md元数据怎么写、并且希望把日常重复提示词沉淀成可复用工作流的开发者。下面我会用一个真实任务——「统一团队的 Vitest 测试生成规范」——从头到尾演示一遍包括目录怎么建、SKILL.md怎么写、本地怎么验证技能真的被加载和触发。先给一个最小心智模型一个 Skill 就是一个文件夹里面必须有一个SKILL.md开头是 YAML frontmatter元数据下面是 Markdown 正文给代理看的指令。文件夹里还可以放脚本、模板、参考文档代理按需读取。就这么简单。2. TaoToken 前置准备让代理真正跑起来的环境在写 Skill 之前得先有一个能加载 Skill 的代理运行环境。我这边用 Claude Code 做演示因为它对 Skills 的支持比较完整而且配置过程不复杂。如果你用的是别的客户端思路是一样的核心就是三件套——Base URL、API Key、Model ID。先说接入点。TaoToken 的 API 地址是https://taotoken.net/api官网在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。你需要先去控制台创建一个 API Key这个 Key 后面会写进配置文件。Claude Code 的配置走的是环境变量或者settings.json。我习惯用settings.json因为团队里可以共享一份模板。文件路径在 macOS/Linux 下是~/.claude/settings.jsonWindows 下是%USERPROFILE%\.claude\settings.json。内容大概长这样{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }这里三个字段缺一不可。ANTHROPIC_BASE_URL指向接入点ANTHROPIC_AUTH_TOKEN放你的 KeyANTHROPIC_MODEL指定模型 ID。很多人只填了前两个结果代理启动后报模型找不到就是因为漏了 Model ID。如果你用的是 Codex配置在~/.codex/auth.json结构不太一样{ OPENAI_API_KEY: sk-你的Key, OPENAI_BASE_URL: https://taotoken.net/api }Codex 的模型 ID 通常在启动参数或者config.toml里指定。Cline 这类 VS Code 插件则是在设置面板里填 Base URL、Key、Model 三项填完点保存就能用。配好之后先别急着写 Skill跑一个最小验证在终端里执行claude或者你的客户端启动命令然后随便问一句「你现在用的是哪个模型」。如果它能正常回复并且模型名对得上说明接入层通了。这一步很重要因为后面 Skill 加载失败时你得先排除是接入问题还是 Skill 本身的问题。有个坑提前说如果你之前配过别的接入点环境变量可能残留。检查一下echo $ANTHROPIC_BASE_URL确保输出的是你刚配的地址。环境变量的优先级通常高于配置文件残留的旧值会覆盖新配置。3. 可复制配置SKILL.md 模板与目录结构现在进入正题。Skills 的目录结构有个约定所有技能放在一个skills根目录下每个技能一个子文件夹文件夹名就是技能名建议用 kebab-case比如vitest-test-writer。每个文件夹里必须有SKILL.md。先看整体目录.claude/ └── skills/ └── vitest-test-writer/ ├── SKILL.md ├── templates/ │ └── component-test.template.ts └── reference/ └── assertion-style.mdSKILL.md是入口templates和reference是可选的辅助资源。代理在需要时会去读这些文件不需要时它们不占上下文。SKILL.md的结构分两部分。上半部分是 YAML frontmatter用---包起来至少要有name和description两个字段--- name: vitest-test-writer description: 当用户要求为 Vue 组件或 TypeScript 工具函数编写单元测试时使用。生成符合团队规范的 Vitest 测试文件包含 describe/it 结构、mock 放置约定和断言风格。不适用于端到端测试或 Jest 项目。 --- # Vitest 测试生成规范 ## 何时使用 - 用户说「给这个组件写测试」「补一下单测」「生成 spec 文件」 - 目标文件是 .vue 或 .ts且项目使用 Vitest ## 生成规则 1. 测试文件与被测文件同目录命名为 xxx.spec.ts 2. 使用 describe 包裹被测单元it 描述具体行为不用 test 3. mock 统一放在文件顶部的 vi.mock() 调用中不散落在各用例里 4. 断言优先用 toEqual 做深比较避免 toBe 比较对象 5. 每个 it 只断言一个行为超过三个断言就拆分 ## 模板 参考 templates/component-test.template.ts替换占位符即可。 ## 断言风格细节 见 reference/assertion-style.md。frontmatter 里的description是最关键的字段。代理就是靠它来判断「当前任务要不要加载这个技能」。写得太窄该触发时不触发写得太宽不该触发时乱触发。我的经验是把「什么时候用」和「什么时候不用」都写进去用否定句划清边界。正文部分就是给代理的指令。你可以把它当成一份写给新同事的规范文档越具体越好。模糊的「写好的测试」没用代理需要的是「文件命名用.spec.ts」「断言用toEqual」这种可执行的规则。templates/component-test.template.ts可以放一个带占位符的骨架import { describe, it, expect, vi } from vitest import { mount } from vue/test-utils import {{ComponentName}} from ./{{ComponentName}}.vue describe({{ComponentName}}, () { it(renders without error, () { const wrapper mount({{ComponentName}}) expect(wrapper.exists()).toEqual(true) }) })reference/assertion-style.md则放更细的对照表比如什么场景用toEqual、什么场景用toMatchObject。这些内容不放进主SKILL.md是为了保持主文件精简——代理只在需要深挖断言细节时才去读它这就是渐进式加载的实际体现。4. 验证请求确认技能被正确加载与触发写完文件不代表技能就生效了。得验证两件事一是代理能不能发现这个技能二是它在合适的任务里会不会真的加载。第一步检查目录位置。Claude Code 默认从项目根目录的.claude/skills/和用户目录的~/.claude/skills/两个地方找技能。项目级的优先级更高适合放团队共享的技能用户级的适合放个人习惯。如果你把技能放错地方代理根本看不到。第二步启动代理后问一个「元问题」「你现在有哪些可用的 skills」正常情况下它会列出vitest-test-writer以及它的 description。如果列表是空的说明目录结构或 frontmatter 有问题。第三步触发测试。找一个真实的.vue文件对代理说「帮我给ScheduleCard.vue写单元测试。」观察它的行为如果它先读取了SKILL.md然后按里面的规则生成.spec.ts文件说明触发成功。如果它直接凭自己的理解写测试完全没提技能说明 description 没匹配上需要调整措辞。如果它报错说找不到技能检查 frontmatter 的 YAML 语法常见问题是缩进用了 tab 或者冒号后面没空格。我实测下来触发失败最常见的原因是 description 写得太抽象。比如只写「帮助写测试」代理不确定是单元测试还是集成测试就可能不加载。改成「为 Vue 组件编写 Vitest 单元测试」之后命中率明显提高。还有一个验证技巧在SKILL.md正文里放一句独特的标记比如「生成的文件头部必须包含注释// generated-by: vitest-test-writer」。然后看代理产出的文件里有没有这行注释。有就证明它确实读了技能内容而不是碰巧写对了。如果技能加载了但行为不对比如它没按模板生成那问题在正文指令的清晰度。把规则拆成编号列表每条只讲一件事比一大段散文有效得多。5. 本篇常见错排查401、local proxy failed 与技能不触发配置和验证过程中有几类报错特别高频我按实际遇到的顺序列一下。401 Unauthorized。这个基本是 Key 的问题。先确认ANTHROPIC_AUTH_TOKEN里的 Key 没有多余空格然后去控制台看这个 Key 是否被禁用或过期。还有一种情况是 Key 复制时漏了尾部字符肉眼很难发现建议重新复制一次。如果用的是settings.json注意 JSON 里不能有注释多一个逗号都会导致整个文件解析失败表现出的症状可能也是 401。local proxy failed / connection refused。这个通常出现在你本地跑了代理或者客户端配置了本地转发的情况。检查ANTHROPIC_BASE_URL是不是被改成了http://localhost:xxxx之类的地址。正确的值应该是https://taotoken.net/api。另外如果你在公司网络里某些端口可能被限制换个网络环境试试能快速定位。reading choices of undefined。这个报错说明返回体结构和你客户端预期的不一致。常见原因是 Base URL 少写了/api或者多写了/v1。不同客户端对路径的拼接方式不同Claude Code 用https://taotoken.net/api有些 OpenAI 兼容客户端需要https://taotoken.net/api/v1。对照你客户端的文档确认一下。OAuth 相关报错。如果你之前登录过官方账号本地可能残留了 OAuth token它会和 API Key 冲突。清理一下~/.claude/下的凭据缓存文件或者干脆用一个干净的配置目录启动。技能不触发。排除接入问题后重点看三处frontmatter 的name是否和文件夹名一致有些客户端要求一致description是否包含任务关键词技能目录是否在客户端扫描范围内。可以临时把 description 改得非常直白比如「写 Vitest 测试时使用」测试能否触发再逐步调回正常措辞。技能触发了但读不到模板文件。检查SKILL.md里引用的相对路径是否正确。路径是相对于SKILL.md所在目录的不是相对于项目根目录。写成./templates/xxx比templates/xxx更稳妥。把这几类排掉基本就能稳定运行了。如果还不行去接入文档里对照一遍配置项或者直接在模型对话里把报错原文贴进去问通常能给出方向。6. 把技能用起来从单文件到团队工作流单个技能跑通之后真正的价值在于复用和组合。我现在的做法是项目根目录放一个.claude/skills/里面按职责分几个技能比如vitest-test-writer、api-mock-builder、changelog-generator。每个技能的SKILL.md保持精简细节丢到reference/里。团队协作时把这个目录提交到 Git新同事拉下来就能用同一套规范。比写一份 Wiki 文档强的地方在于文档没人看但代理每次都会读。规范从「写在纸上」变成了「长在工具里」。如果你想让代理在更长的任务链里自动调用这些技能比如「重构这个模块并补齐测试」可以考虑用 Coding Plan 这类支持多步代理的接入方式它能让技能在连续任务中被反复触发而不是一问一答就结束。配置入口在控制台的 Coding Plan 页面思路和前面一样还是 Base URL、Key、Model 三件套只是模型选择上更偏向长上下文和工具调用能力强的型号。最后留一个实用技巧技能不是写完就一劳永逸的。每次代理产出不符合预期先别改提示词去看看是不是SKILL.md里的某条规则有歧义。把那条规则改具体比在对话里反复纠正高效得多。技能文件本身就是你团队规范的活文档改它就是在改规范。