当前位置: 首页 > news >正文

Skill的实现方式:让 Agent 学会“开挂“

Skill的实现方式:让 Agent 学会"开挂"

用 Claude Code 写代码时间长了,你会发现一个现象:有时候它表现得像一个经验丰富的老手,对你的项目结构、编码习惯、甚至某些专业领域的知识了如指掌;有时候又像个新手,什么都不懂,需要你从头解释。

区别在哪?在于它有没有加载对应的Skill

Skill 其实就是一份提前写好的提示词,在特定时机注入到 Claude 的上下文中,告诉它"你现在要扮演一个擅长做某某领域的专家"。就像给一个聪明但什么都不知道的新员工一份详细的岗位手册,他看完就知道该怎么干活了。

Skill 长什么样

一个 Skill 本质上就是一个 Markdown 文件,放在特定目录下。Claude Code 在启动时会扫描这些目录,把找到的 Skill 注册到系统中。

最简单的 Skill 结构是这样的:

my-skill/ SKILL.md

SKILL.md就是 Skill 的全部内容。来看一个最基础的例子:

--- name: code-review description: 审查代码变更,检查潜在问题 --- 你是一个严格的代码审查员。审查代码时重点关注: 1. 安全漏洞(SQL 注入、XSS、硬编码密钥) 2. 性能问题(N+1 查询、未优化的循环) 3. 代码风格(命名规范、函数长度) 输出格式: - 用中文回复 - 每个问题标注严重程度: 高 / 中 / 低 - 给出具体的修改建议

开头用---包裹的部分是frontmatter,定义了 Skill 的元信息:名字和描述。描述很重要,Claude Code 靠它来判断什么时候该加载这个 Skill。

下面的内容就是注入到上下文中的指令。你写什么,Claude 就会遵循什么。

Skill 放在哪里

Claude Code 会从多个位置扫描 Skill:

~/.claude/skills/ # 用户级,全局生效 <project>/.claude/skills/ # 项目级,仅当前项目生效

用户级的 Skill 适合放通用能力,比如代码审查、文档生成;项目级的 Skill 适合放项目专属的知识,比如"我们项目用的是 Vue 3 + TypeScript,组件放 src/components 下"。

每个 Skill 就是一个文件夹,里面放SKILL.md

~/.claude/skills/ code-review/ SKILL.md doc-generator/ SKILL.md api-design/ SKILL.md

Skill 是怎么被加载的

这是最关键的部分。回到开头那个问题:如果把所有规范文档全塞进 system prompt,6500 行的 prompt 每次调用都带着,99% 的内容和当前任务无关,白白烧 token。

Skill 的解决方案是两层设计——目录层和内容层分开加载:

注入位置时机代价
目录system prompt启动时扫描 skills/~100 tokens/skill,每轮都带
内容tool_resultAgent 调用 load_skill 时~2000 tokens/skill,按需

第一层:启动时注入目录

Claude Code 启动时,harness 会扫描skills/目录,解析每个SKILL.md的 YAML frontmatter(namedescription),存入一个注册表SKILL_REGISTRY。然后从注册表生成目录,注入 system prompt。

Agent 每轮都能看到"我有哪些技能可用",但只看到名字和描述,不花额外 API 调用。

SKILL_REGISTRY:dict[str,dict]={}def_scan_skills():fordinsorted(SKILLS_DIR.iterdir()):ifnotd.is_dir():continuemanifest=d/"SKILL.md"ifmanifest.exists():raw=manifest.read_text()meta,body=_parse_frontmatter(raw)name=meta.get("name",d.name)desc=meta.get("description",raw.split("\n")[0].lstrip("#").strip())SKILL_REGISTRY[name]={"name":name,"description":desc,"content":raw}defbuild_system()->str:catalog="\n".join(f"- **{s['name']}**:{s['description']}"forsinSKILL_REGISTRY.values())returnf"You are a coding agent. Skills available:\n{catalog}\nUse load_skill to get full details when needed."

这里有个安全细节:注册表是启动时填充的,后续通过名字查找,不走文件路径,没有路径遍历风险。

第二层:load_skill 按需加载内容

Agent 看到目录后,决定"我需要 code-review 这个 Skill",于是调用load_skill("code-review")工具。这时候才把SKILL.md的完整内容读出来,通过 tool_result 注入当前 messages

TOOLS.append({"name":"load_skill","description":"Load a skill by name to get full instructions","input_schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}})TOOL_HANDLERS["load_skill"]=load_skilldefload_skill(name:str)->str:skill=SKILL_REGISTRY.get(name)ifnotskill:returnf"Skill not found:{name}"returnskill["content"]

注意一个关键区别:Skill 内容不是 system prompt 的一部分,它作为一次工具调用的结果进入 messages。后续轮次会随对话历史一起携带,直到上下文被压缩或会话结束。

用伪代码来描述整个流程:

启动时: 扫描 skills/ → 解析 frontmatter → 存入 SKILL_REGISTRY → 生成目录 → 注入 system prompt 运行时: 用户: "帮我 review 代码" ↓ Agent 看到目录里有 code-review Skill ↓ Agent 调用 load_skill("code-review") ↓ tool_result 返回 SKILL.md 完整内容 ↓ Agent 基于 Skill 指令执行任务

这个设计和上下文压缩(compact)自然衔接:按需加载解决了"不该提前带的不要带",compact 解决"该丢的怎么丢"。两层配合,让 Agent 的上下文始终保持在最精简的状态。

Skill 和普通 Prompt 有什么区别

你可能会想:这不就是把一段 prompt 塞进上下文吗?我自己复制粘贴一段指令进去不也一样?

从效果上看确实类似,但 Skill 有几个关键优势:

1. 按需加载,不浪费 token

你有一百个 Skill,但每次对话只用到一两个。两层设计确保目录层始终轻量(~100 tokens/skill),内容层只在需要时才通过工具调用加载(~2000 tokens/skill)。如果全塞进 system prompt,上下文窗口直接就被吃掉了。

2. 声明式的,不需要写代码

Skill 不是插件系统,不需要实现接口、注册回调。你只需要写 Markdown,描述你想要的行为就行。门槛低到任何会写字的人都能创建 Skill。

3. 可以调用工具

Skill 里可以声明它需要使用哪些工具,以及如何使用。比如一个"数据库迁移" Skill 可以指定使用bash工具执行prisma migrate命令。

4. 版本管理和共享

Skill 就是文件,可以放在 Git 仓库里做版本管理。团队共享一套 Skill,每个人的行为就统一了。

来看一个真实场景

假设你团队有一套代码规范,每次 review 代码都要重复说一遍:

我们用 TypeScript strict 模式 函数命名用 camelCase,组件命名用 PascalCase API 返回值统一用 { code, data, message } 格式 错误处理统一用 try-catch,不用 .catch()

每次都复制粘贴这些规则,烦不烦?

写成 Skill 就一劳永逸了:

--- name: team-code-review description: 按照团队规范审查代码变更 --- 你是一个严格的代码审查员,审查时遵循以下团队规范: ## TypeScript 规范 - 使用 strict 模式 - 禁止使用 any,必须定义具体类型 - 接口以 I 开头,如 IUserData ## 命名规范 - 函数/变量:camelCase - 组件/类:PascalCase - 常量:UPPER_SNAKE_CASE - 文件名:kebab-case ## API 规范 - 返回值统一格式:{ code: number, data: T, message: string } - 错误处理统一使用 try-catch - 不使用 .catch() 链式调用 ## 审查输出格式 1. 列出所有不符合规范的地方 2. 每条标注:文件名 + 行号 + 问题描述 + 修改建议 3. 最后给一个总体评价

把这个文件放到~/.claude/skills/team-code-review/SKILL.md,以后说"帮我 review 代码",Claude 就会自动按照你们团队的规范来审查,不用你再重复那些规则了。

Skill 的触发方式

Skill 的触发有两种方式:

自动触发:Agent 在推理过程中看到 system prompt 里的 Skill 目录,根据用户消息和 Skill 描述自行判断是否需要加载。如果匹配上了,就调用load_skill工具获取完整内容。比如用户说"帮我 review 一下这个 PR",Agent 看到目录里有一个 description 是"审查代码变更"的 Skill,就会主动调用load_skill("code-review")

手动触发:在 Claude Code 中输入/skill-name可以强制加载某个 Skill。比如/code-review会直接加载代码审查 Skill,不管当前消息是什么。

手动触发适合那些不容易被自动匹配的场景。比如你有一个 Skill 是"按照公司模板生成周报",自动匹配很难判断什么时候该触发,但手动/weekly-report就很明确。

一个更复杂的例子

Skill 不只能写审查规则,还能定义完整的工作流程。来看一个生成 API 文档的 Skill:

--- name: api-doc-generator description: 根据代码自动生成 API 接口文档 --- 你是一个 API 文档生成专家。当用户要求生成 API 文档时,按以下流程执行: ## 第一步:扫描项目 - 使用 Glob 工具查找所有路由文件(routes/、api/、controllers/) - 使用 Read 工具读取每个路由文件的内容 ## 第二步:提取接口信息 对每个接口提取: - HTTP 方法和路径 - 请求参数(query、body、path params) - 响应格式 - 认证要求(是否需要 token) ## 第三步:生成文档 使用 Markdown 格式输出,每个接口包含: - 接口名称和描述 - 请求示例(curl 格式) - 响应示例(JSON 格式) - 错误码说明 ## 第四步:保存文件 将生成的文档保存到 docs/api.md

这个 Skill 不只定义了"输出什么",还定义了"怎么做"——先扫描、再提取、再生成、再保存。Claude 会严格按照这个流程执行,就像一个有标准作业流程的员工。

Skill 和 CLAUDE.md 的关系

你可能还听说过CLAUDE.md,它和 Skill 有什么区别?

CLAUDE.md是项目级的全局指令,每次对话都会加载。适合放项目的基本信息:技术栈、目录结构、构建命令这些。

Skill是按需加载的能力包,只在需要的时候才注入。适合放专业领域的工作流和规则。

两者可以配合使用。CLAUDE.md告诉 Claude “这个项目是什么”,Skill 告诉 Claude “在特定场景下该怎么做”。

CLAUDE.md(始终加载) "这是一个 Vue 3 + TypeScript 项目,使用 pnpm 管理依赖" Skill(按需加载) "当用户要求 review 代码时,按照团队规范审查" "当用户要求生成文档时,按照标准模板输出"

小结

Skill 的核心思想就一句话:用到的时候才加载,别全塞 prompt 里

传统的做法是把所有规范文档塞进 system prompt,每次调用都带着,不管有没有用。Skill 的两层设计解决了这个问题:目录层始终轻量,内容层按需通过工具调用注入。从软件工程的角度看,这就是懒加载思想——和前端的路由懒加载、后端的依赖注入是同一个思路。

Claude Code 的 Skill 机制没有用什么复杂的技术,没有编译器、没有运行时、没有沙箱。它就是一个 Markdown 文件加一套两层加载策略。但正是这种简洁,让它变得极其灵活——你不需要会写代码,只需要会写字,就能给 Agent 扩展能力。

注:本文参考了 GitHub 开源学习项目:learn-claude-code。这是一个非常优秀的学习资源,推荐读者结合项目内容一同学习。

http://www.gsyq.cn/news/1488832.html

相关文章:

  • Confluence介绍
  • 力扣刷题#11:LeetCode128最长连续序列_刷题笔记
  • 氛围感满分!在厦门,拍一套治愈一辈子的海景婚纱照 - 奔跑123
  • 国产PCB厂家综合实力排行,这5家值得关注
  • 系统架构设计师-计算机系统组成与层次化存储体系深度解析
  • 如何免费使用Duplicity存档编辑器:缺氧游戏存档修改完整指南
  • Markdown 阅读器全平台精选(只看.md 文件 / 兼顾读写分开推荐)
  • 广州番禺上门回收黄金奢侈品,价格公道服务好速度快 - 花生花生1
  • 2026年 3-(1,4-丁炔二醇)-磺丙基醚单钠盐(丁醚嗡盐)厂家推荐:电镀镍中间体核心原料,高纯度与稳定性深度解析 - 品牌发掘
  • Java数据结构——二叉树(Binary Tree)详解
  • 蓝桥杯Java组B类选手,我是如何用‘笨办法’刷题拿到省一的?
  • 如何用ComfyUI-MimicMotionWrapper快速实现视频动作迁移:3步完成AI动作复刻
  • 国产PCB厂家综合实力排行,这5家真值得看
  • 2026年东莞波珠螺丝/定位珠螺丝/弹簧碰珠螺丝厂家推荐:高精度与耐用性并存的优质品牌深度评测 - 品牌发掘
  • CAN-FD比特率切换与发射延迟补偿实战:基于LPC5500的配置详解
  • 别再只盯着准确率了!用sklearn的Brier Score和Log Loss,手把手教你评估分类模型的预测概率到底靠不靠谱
  • 3步解锁AMD GPU大模型部署:Ollama-for-amd终极配置指南
  • 跨语言手写检索的轻量级双编码器框架设计与优化
  • 5分钟掌握SPT-AKI Profile Editor:逃离塔科夫离线版终极存档修改器
  • NXP Kinetis触摸库实战:从环境搭建到FreeMASTER高级调试
  • 轻量级跨语言手写检索技术解析与应用实践
  • Origin 2018保姆级安装教程:从下载到配置,手把手教你搞定科研绘图第一步
  • 深入解析 Leaflet 地图精度与高德地图集成实践
  • Verilog新手避坑指南:从4位全加器到8位乘法器,手把手教你搞定仿真和RTL视图
  • LiteEmbed:CLIP模型的轻量级适配框架优化罕见类别识别
  • HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三十):【社区分享】本地社区功能——让菜谱从“独享”走向“共享”
  • 炉石传说HsMod:解锁55项隐藏功能的游戏体验革命
  • 3步解锁AMD Ryzen处理器隐藏性能:SMU Debug Tool新手完全指南
  • 从原理看 Arthas 为何比 IDEA Profiler 更“懂”你的代码
  • Vue i18n动态加载进阶:结合Pinia/Vuex管理多语言状态与接口缓存策略