Claude Code Agent Skills 入门指南(下):理解Skill的工作机制

Claude Code Agent Skills 完全指南(下):实战与进阶 ------ 从零实现一个完整的 Skill 系统

摘要

上篇我们理解了 Skills 的原理与规范 ,本篇将动手实现------用核心代码实现一个完整的 SkillsManager,让你在自己的 Agent 中也能使用 Skills 机制。


引言

上篇回顾

Skills 通过渐进式披露解决上下文窗口浪费:

  • 启动时只加载 name + description(~100 tokens)
  • 激活时才加载完整 SKILL.md(~3000 tokens)
  • 详细内容按需加载(references/)

本篇目标

从零实现一个完整的 Skill 系统,理解 Skills 的工作原理。

你将学会:

  • SkillsManager 的核心实现(~200 行)
  • 如何将 Skills 集成到 Agent 中(~150 行)
  • 完整的代码结构和运行效果

一、工作原理总览

在写代码之前,先理解 Skills 系统由哪些组件组成,以及它们如何协作。

1.0 本质理解:Skill 就是"动态内容注入"的工具调用

很多人会问:Skill 和普通工具调用有什么区别?

答案是:本质上没有区别。从代码角度看,Skill 只是一个做了额外封装的工具:

python 复制代码
# 普通工具:固定内容
{
    "name": "get_weather",
    "description": "获取天气信息",
    "parameters": {...}
}
# 调用后返回:天气数据

# Skill 工具:动态内容
{
    "name": "Skill",
    "description": "调用 Skill...\n- code-reviewer: 审查代码...\n- pdf-analyzer: 分析 PDF...",
    "parameters": {"skill": "string", "args": "string"}
}
# 调用后返回:对应 SKILL.md 的完整内容

核心区别只有一个

普通工具 Skill 工具
调用时返回固定内容 调用时返回动态内容(根据 skill_name 加载不同 SKILL.md

为什么这样做?

为了管理 LLM 的上下文

ini 复制代码
┌─────────────────────────────────────────────────────────┐
│  如果把所有 Skill 内容写进系统提示词:                     │
│  20 个 Skill × 3000 tokens = 60,000 tokens ❌ 爆上下文   │
├─────────────────────────────────────────────────────────┤
│  用 Skill 工具动态加载:                                  │
│  启动时:20 × 100 tokens = 2,000 tokens ✅               │
│  激活时:只加载用户需要的那个(~3000 tokens)             │
└─────────────────────────────────────────────────────────┘

一句话总结

Skill 就是"按需加载提示词"的工具调用,本质是用代码管理 LLM 上下文

1.1 三个核心组件

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      Skills 系统                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐   ┌──────────────────┐   ┌─────────────┐  │
│  │  SKILL.md   │   │  SkillsManager   │   │    Agent    │  │
│  │  (定义层)    │   │    (管理层)       │   │  (执行层)   │  │
│  └─────────────┘   └──────────────────┘   └─────────────┘  │
│        │                   │                     │          │
│        │  ① 扫描解析        │                     │          │
│        └──────────────────►│                     │          │
│                            │                     │          │
│                            │  ② 提供元工具 Schema │          │
│                            ├────────────────────►│          │
│                            │                     │          │
│                            │  ③ 加载完整内容      │          │
│                            │◄────────────────────┤          │
│                            │                     │          │
│                            │  ④ 返回 Skill 提示词 │          │
│                            ├────────────────────►│          │
│                            │                     │          │
│                            │                     │ ⑤ 注入对话│
│                            │                     │    ↓     │
│                            │                     │  LLM 执行 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
组件 职责 对应代码
SKILL.md 定义 Skill 的元数据和指令 Markdown 文件
SkillsManager 扫描、解析、加载 Skills skills_manager.py
Agent 调用 LLM、处理 Skill 激活、注入提示词 agent.py

1.2 完整消息流程

当用户说"帮我审查代码"时,发生了什么?

scss 复制代码
用户                    Agent                 SkillsManager              LLM
 │                        │                         │                      │
 │  "帮我审查代码"         │                         │                      │
 │───────────────────────►│                         │                      │
 │                        │                         │                      │
 │                        │  ① 发送请求(含 Skill 工具描述)                 │
 │                        │─────────────────────────────────────────────────►│
 │                        │                         │                      │
 │                        │  ② LLM 决定调用 Skill("code-reviewer")           │
 │                        │◄────────────────────────────────────────────────│
 │                        │                         │                      │
 │                        │  ③ generate_skill_messages("code-reviewer")    │
 │                        │────────────────────────►│                      │
 │                        │                         │                      │
 │                        │  ④ 返回 (visible_msg, skill_prompt)            │
 │                        │◄────────────────────────│                      │
 │                        │                         │                      │
 │                        │  ⑤ 向用户显示 visible_msg                      │
 │◄───────────────────────│                         │                      │
 │  "code-reviewer 正在加载"                        │                      │
 │                        │                         │                      │
 │                        │  ⑥ 发送 tool_result(含 skill_prompt)          │
 │                        │─────────────────────────────────────────────────►│
 │                        │                         │                      │
 │                        │  ⑦ LLM 根据 Skill 指令执行代码审查               │
 │                        │◄────────────────────────────────────────────────│
 │                        │                         │                      │
 │  ⑧ 返回审查结果        │                         │                      │
 │◄───────────────────────│                         │                      │
 │                        │                         │                      │

关键点

  1. LLM 选择 Skill:不是算法匹配,而是 LLM 根据用户意图和 Skill 描述推理决定
  2. 两消息模式:一条给用户看(状态通知),一条给 LLM 看(执行指令)
  3. 提示词注入:Skill 内容通过 tool_result 注入到对话中

1.3 渐进式披露的三层

objectivec 复制代码
┌─────────────────────────────────────────────────────────────┐
│  第一层:元数据(启动时加载)                                  │
│  ─────────────────────────────                              │
│  • 内容:name + description                                  │
│  • 大小:~100 tokens / Skill                                 │
│  • 用途:LLM 据此选择要激活哪个 Skill                          │
│                                                              │
│  SkillsManager.get_skill_tool_schema()                      │
│  → 生成 Skill 元工具的 JSON Schema                           │
│  → 放入 API 请求的 tools 数组                                │
├─────────────────────────────────────────────────────────────┤
│  第二层:完整指令(激活时加载)                                │
│  ─────────────────────────────                              │
│  • 内容:完整 SKILL.md 文件                                   │
│  • 大小:~500-5000 tokens                                    │
│  • 用途:告诉 LLM 如何执行任务                                │
│                                                              │
│  SkillsManager.generate_skill_messages()                    │
│  → 读取 SKILL.md 文件                                        │
│  → 作为 tool_result 返回给 LLM                               │
├─────────────────────────────────────────────────────────────┤
│  第三层:资源(按需加载)                                      │
│  ─────────────────────────────                              │
│  • 内容:references/、scripts/、assets/                      │
│  • 大小:不定                                                │
│  • 用途:详细的参考资料、检查清单等                            │
│                                                              │
│  SKILL.md 中写:参见 [references/checklist.md]              │
│  → LLM 需要时会调用 Read 工具自行读取                         │
└─────────────────────────────────────────────────────────────┘

第三层的本质区别

层级 谁决定加载? 何时加载?
第一层(元数据) Agent 代码 启动时
第二层(完整指令) Agent 代码 Skill 被激活时
第三层(资源) LLM 自己 LLM 认为需要时

第三层的工作流程

objectivec 复制代码
┌─────────────────────────────────────────────────────────┐
│  用户: 帮我审查这段复杂的微服务代码                        │
├─────────────────────────────────────────────────────────┤
│  1. LLM 激活 Skill,收到 SKILL.md                        │
│     → 看到:"对于详细审查,请按需读取:                    │
│              - 完整检查清单: references/checklist.md"    │
├─────────────────────────────────────────────────────────┤
│  2. LLM 自主判断:代码复杂,需要详细清单                   │
│     → 决定调用 Read("references/checklist.md")          │
├─────────────────────────────────────────────────────────┤
│  3. Agent 的 Read 工具返回 checklist 内容                 │
│     → LLM 按照详细清单执行审查                            │
└─────────────────────────────────────────────────────────┘

对比:如果用户只是"帮我看看这个简单函数"
→ LLM 判断不需要详细清单
→ 不调用 Read,直接用 SKILL.md 中的精简版检查

这意味着你的 Agent 需要额外提供 Read 工具 ,LLM 才能读取 references/ 中的内容:

python 复制代码
# Agent 需要提供 Read 工具
tools: [
    skill_schema,           # Skill 元工具
    read_tool,              # ← 读文件工具(第三层需要)
    write_tool,
    bash_tool
]

为什么这样设计?

假设你有 20 个 Skills:

  • 一次性加载:20 × 3000 = 60,000 tokens(直接爆上下文)
  • 渐进式披露:20 × 100 = 2,000 tokens(节省 97%)

二、深度案例:code-reviewer Skill

理解原理后,来看一个完整的 Skill 案例。

2.1 目录结构

bash 复制代码
skills/
└── code-reviewer/
    ├── SKILL.md              # 核心指令(~50 行)
    └── references/
        └── checklist.md      # 详细检查清单(按需加载)

2.2 SKILL.md 完整内容

yaml 复制代码
---
name: code-reviewer
description: 审查代码的质量、安全性和最佳实践。当用户要求审查代码、检查 bug、建议改进或审计安全时使用。
allowed-tools: "Read Grep Glob"
---

# 代码审查器

## 审查检查清单

复制此清单并跟踪进度:

~~~
代码审查进度:
- [ ] 检查代码结构和组织
- [ ] 识别潜在的 bug 或逻辑错误
- [ ] 审查安全漏洞
- [ ] 提出性能优化建议
- [ ] 验证错误处理
- [ ] 检查命名规范
~~~

## 审查流程

1. **阅读代码** - 理解目的和上下文
2. **识别问题** - 使用上面的检查清单
3. **优先级排序** - 严重 > 重要 > 轻微 > 建议
4. **提供修复** - 展示修正后的代码片段

## 输出格式

~~~
## 审查摘要

**严重问题:** X
**重要问题:** X
**轻微问题:** X

### 严重
- [第X行] 问题描述
  # 当前代码
  # 建议修复
~~~

## 安全关注点

- SQL 注入(查询中的用户输入)
- XSS(未转义的输出)
- 硬编码凭证
- 路径遍历
- 命令注入

## 高级资源

对于详细的代码审查,请按需读取:
- **完整检查清单**: 参见 [references/checklist.md](references/checklist.md)

2.3 设计要点

设计决策 原因
SKILL.md 保持简洁(~50行) 核心指令每次都需要,避免占用过多上下文
检查清单放在 references/ 详细内容按需加载,不用的不占空间
使用"复制此清单"指令 模拟 for 循环,让 Agent 逐步执行
allowed-tools 限制为只读 代码审查不应该修改文件

三、SkillsManager 核心实现

SkillsManager 的核心职责是三层渐进式披露的实现。

3.1 两个数据结构

python 复制代码
@dataclass
class SkillMetadata:
    """第一层:元数据(启动时加载,~100 tokens)"""
    name: str           # Skill 名称
    description: str    # 描述(LLM 选择的依据)
    path: Path          # SKILL.md 路径

@dataclass
class SkillContent:
    """第二层:完整内容(激活时加载,~500-5000 tokens)"""
    metadata: SkillMetadata
    full_content: str   # 完整 SKILL.md

3.2 三个核心方法

python 复制代码
# config.py - 客户端配置
from openai import OpenAI
import os

MODEL = "qwen-plus"  # 或 "deepseek-chat", "gpt-4o" 等
BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"  # 各厂商的兼容端点

def create_client() -> OpenAI:
    return OpenAI(api_key=os.environ.get("DASHSCOPE_API_KEY"), base_url=BASE_URL)
python 复制代码
class SkillsManager:
    def __init__(self, skills_dir: str = "./skills"):
        self.skills: dict[str, SkillMetadata] = {}

    # 方法1:启动时扫描(第一层)
    def discover_skills(self):
        """扫描 skills 目录,只解析 frontmatter"""
        for skill_folder in Path(self.skills_dir).iterdir():
            skill_md = skill_folder / "SKILL.md"
            if skill_md.exists():
                # 解析 YAML frontmatter,提取 name 和 description
                # _parse_metadata() 实现:读取文件 → 找到 --- 之间的内容 → yaml.safe_load()
                metadata = self._parse_metadata(skill_md)
                self.skills[metadata.name] = metadata

    # 方法2:生成元工具 Schema(LLM 选择 Skill 的依据)
    def get_skill_tool_schema(self) -> dict:
        """生成 Skill 元工具的 JSON Schema"""
        skill_list = "\n".join([
            f"- **{name}**: {meta.description}"
            for name, meta in self.skills.items()
        ])
        return {
            "type": "function",
            "function": {
                "name": "Skill",
                "description": f"调用 Skill 以扩展能力。\n\n可用的 Skills:\n{skill_list}",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "skill": {"type": "string", "enum": list(self.skills.keys())},
                        "args": {"type": "string"}
                    },
                    "required": ["skill"]
                }
            }
        }

    # 方法3:激活时加载(第二层 + 两消息模式)
    def generate_skill_messages(self, skill_name: str, args: str = "") -> tuple[str, str]:
        """生成两条消息"""
        meta = self.skills[skill_name]

        # 消息1:用户可见
        visible_msg = f'<command-message>"{skill_name}" 正在加载</command-message>'

        # 消息2:LLM 可见(完整 SKILL.md)
        skill_prompt = meta.path.read_text(encoding='utf-8')

        return visible_msg, skill_prompt

核心要点

  • discover_skills() 只解析 frontmatter,不读取完整文件
  • get_skill_tool_schema() 的 description 是 LLM 选择 Skill 的唯一依据
  • generate_skill_messages() 返回两条消息,分别给用户和 LLM 看

3.3 LLM 如何选择 Skill?

当调用 chat.completions.create() 时,tools 参数会发送给 LLM:

python 复制代码
# LLM 实际收到的 tools 参数
tools: [{
    "name": "Skill",
    "description": "调用 Skill 以扩展能力。\n\n可用的 Skills:\n- **code-reviewer**: 审查代码质量、安全...\n- **pdf-analyzer**: 分析 PDF 文档...",
    "parameters": {"skill": {"enum": ["code-reviewer", "pdf-analyzer"]}}
}]

LLM 会根据:

  1. 用户的输入("帮我审查代码")
  2. tools 中的 description("- code-reviewer: 审查代码...")

自行推理决定调用哪个 Skill。

💡 你不需要写任何匹配算法,只需要写好 description。LLM 的理解能力会自动完成"意图 → Skill"的映射。


四、Agent 集成

💡 说明:本文示例使用 OpenAI 兼容接口风格,可对接阿里千问、DeepSeek 等模型。

4.1 核心流程

Agent 的核心是处理 LLM 决定调用 Skill 的情况:

python 复制代码
class SkillEnabledAgent:
    def __init__(self):
        # Phase 1: 加载 Skills 元数据
        self.skills_manager = SkillsManager("./skills")
        self.skills_manager.discover_skills()

        self.client = create_client()
        self.messages = []

    def chat(self, user_message: str):
        self.messages.append({"role": "user", "content": user_message})

        # 调用 LLM,传入 Skill 元工具
        response = self.client.chat.completions.create(
            model=MODEL,
            messages=self.messages,
            tools=[self.skills_manager.get_skill_tool_schema()],
            tool_choice="auto"
        )

        return self._handle_response(response)

    def _handle_response(self, response):
        message = response.choices[0].message

        # LLM 决定调用 Skill
        if message.tool_calls:
            # ⚠️ 关键:先添加 assistant 消息(包含 tool_calls)
            # 这是 OpenAI Tool Calling 的规范要求
            self.messages.append({
                "role": "assistant",
                "content": message.content,
                "tool_calls": [{
                    "id": tc.id,
                    "type": "function",
                    "function": {
                        "name": tc.function.name,
                        "arguments": tc.function.arguments
                    }
                } for tc in message.tool_calls]
            })

            for tool_call in message.tool_calls:
                if tool_call.function.name == "Skill":
                    return self._activate_skill(tool_call)

        return message.content

    def _activate_skill(self, tool_call):
        """两消息模式的核心"""
        args = json.loads(tool_call.function.arguments)
        skill_name = args["skill"]

        # 生成两消息
        visible_msg, skill_prompt = self.skills_manager.generate_skill_messages(skill_name)

        # 消息1:向用户显示(通过 stdout)
        print(visible_msg)

        # 消息2:作为 tool_result 发送给 LLM
        # ⚠️ 关键:tool 角色的消息会被 LLM 看到
        self.messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": f"Skill 已加载。请按以下指令执行:\n\n{skill_prompt}"
        })

        # 继续调用 LLM(此时 LLM 已获得 Skill 指令)
        # 传入空字符串是因为不需要新的用户输入
        # 循环会在 LLM 返回文本响应(而非 tool_calls)时自动结束
        return self.chat("")

4.2 运行效果

text 复制代码
用户: 帮我审查这段代码: def get_user(id):
    query = f"SELECT * FROM users WHERE id = {id}"
    return db.execute(query)

<command-message>"code-reviewer" 正在加载</command-message>

助手: ## 审查摘要
**严重问题:** 1

### 严重
- [第2行] 🔴 SQL 注入漏洞
  # 修复后: cursor.execute("SELECT * FROM users WHERE id = ?", (id,))

五、思考:从"自己写好代码"到"让 AI 写好代码"

在结束之前,我想分享一个最近的感悟。

5.1 一个内置 Skill 带来的启发

Claude Code 最新版内置了一个 simplify Skill,用于代码审查和清理。它的设计非常精妙:

yaml 复制代码
┌─────────────────────────────────────────────────────────┐
│                    Simplify Skill                        │
├─────────────────────────────────────────────────────────┤
│  Phase 1: git diff → 获取变更内容                        │
├─────────────────────────────────────────────────────────┤
│  Phase 2: 并行启动 3 个 Agent(同时进行)                 │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐        │
│  │ Agent 1     │ │ Agent 2     │ │ Agent 3     │        │
│  │ 代码复用    │ │ 代码质量    │ │ 效率审查    │        │
│  └─────────────┘ └─────────────┘ └─────────────┘        │
├─────────────────────────────────────────────────────────┤
│  Phase 3: 汇总发现 → 直接修复                            │
└─────────────────────────────────────────────────────────┘

三个维度覆盖了代码审查的核心关注点:

维度 核心问题 典型检查项
代码复用 是否重复造轮子? 现有工具函数、重复功能、内联逻辑
代码质量 是否有反模式? 冗余状态、参数膨胀、复制粘贴变体、抽象泄露
效率审查 性能是否达标? 不必要的工作、错失并发、热路径膨胀、内存泄漏

5.2 一个感悟

看完 simplify Skill 的设计,我突然意识到:

过去我用 AI 写代码时,经常要反复拉扯。

makefile 复制代码
我: 帮我重构这段代码
AI: 好的,重构完成
我: 这里有个重复的函数,应该用现有的工具类
AI: 好的,我改一下
我: 这里的状态管理有问题
AI: 好的,再改
我: 这里性能不太好,应该加缓存
AI: 好的...
(重复 N 次)

然后我看到 simplify Skill 把这些维度都写进去了:

  • 代码复用:是否重复造轮子?
  • 代码质量:是否有反模式?
  • 效率审查:性能是否达标?

我突然明白------

这些维度,其实就是我每次都会说的话

如果把它们写进 Skill,AI 调用时就会自动按这个标准执行。

这就是 Skill 的本质:把"你反复说的那些话",写成一份文档,让 AI 每次都按这个标准来。

5.3 写在最后

当你发现自己和 AI 反复说同一类话时,就是该创建 Skill 的时机:

  1. 记录你每次的反馈
  2. 提取可复用的模式
  3. 写成 SKILL.md
  4. 下次直接调用

把"反复说的话"变成"一次编码,永久复用"。


关键资源

相关推荐
crossoverJie2 小时前
DeepWiki 优化实战:代码行号与确定性目录生成
后端·ai编程
带刺的坐椅4 小时前
Snack4 Json 流式解析与自动结构修复深度指南
java·llm·json·jsonpath
小虎AI生活4 小时前
全网爆火的 OpenClaw 迎来最强对手?腾讯“龙虾战略”的杀招在这
ai编程
智算菩萨5 小时前
ChatGPT 5.4 API深度解析:从Transformer架构到企业级应用实践
人工智能·深度学习·ai·chatgpt·ai编程
星辰引路-Lefan5 小时前
全平台 Docker 部署 CPA(CLIProxyAPI Plus) 灵活定制指南 (Linux/Windows)——接入Codex
linux·windows·docker·ai·ai编程
程序员七平5 小时前
本地大模型部署笔记:Ollama+Qwen2.5+Win11环境配置实录
llm·ollama
进击的野人5 小时前
新手入门:如何接入AI大模型?从零开始的实用指南
人工智能·agent·ai编程
恋猫de小郭6 小时前
让你的 OpenClaw 带你学习,清华开源 AI 私人导师 OpenMAIC
前端·人工智能·ai编程
何以解忧,唯有..6 小时前
AI编程相关概念
ai编程