Claude代码技能加载攻略:新手必看榜单

2026-05-31阅读 0热度 0
其他

原址

learn-claude-code-s05_skill_loading.py

s05_skill_loading.py 的核心使命很明确:给 AI Coding Agent 增加“按需加载技能”的能力

换句话说,不要一股脑把所有专业知识都塞进 system prompt。取而代之的策略是:一开始只放技能的名称和简介,等到模型真需要的时候,再通过 load_skill 这个工具去加载完整的内容。文件开头的注释把这个“两层注入”设计讲得很清楚——第一层把技能名和简介放进 system prompt,第二层在模型调用 load_skill("pdf") 时返回完整技能正文。

这个文件要解决什么问题

回头想想,如果咱们的 Agent 把所有的规则、工具说明、PDF 处理方法、代码审查规范、MCP 构建指南……统统写进 system prompt,会怎样?

  • prompt 变得巨长,白白浪费 token
  • 模型的注意力被稀释,容易抓不住重点
  • 那些与当前任务不相关的知识,反而会干扰判断
  • 技能越多,系统提示词就越臃肿

所以,这个文件实现了一个更聪明的机制——类似于 Claude Code、Cursor 或 Agent Skill 的做法。注释里有一句话特别到位:“Don’t put everything in the system prompt. Load on demand.”

整体流程,六步走完

整个过程可以这样理解:

启动程序
  ↓
扫描 skills/ 目录下所有 SKILL.md
  ↓
解析每个 SKILL.md 的 YAML frontmatter
  ↓
把技能名称 + 简介塞进 system prompt
  ↓
用户提出任务
  ↓
模型判断是否需要某个技能,需要就调用 load_skill(name)
  ↓
load_skill 返回完整技能说明
  ↓
模型根据完整技能继续完成任务

目录结构怎么设计

文件注释里规定的技能目录大概长这样:

skills/
  pdf/
    SKILL.md
  code-review/
    SKILL.md

每个技能一个目录,每个目录里放一个 SKILL.md。从仓库的实际布局来看,确实存在 agent-buildercode-reviewmcp-builderpdf 这些技能目录。

SKILL.md 的典型结构是:

---
name: pdf
description: Process PDF files...
---
# PDF Processing Skill
具体操作说明……

skills/pdf/SKILL.md 来说,里面就包含了 name: pdfdescription: Process PDF files... 这样的元信息,以及后面完整的 PDF 处理说明。

核心代码逐段解析

1. 初始化环境

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic()
MODEL = os.environ["MODEL_ID"]
SKILLS_DIR = WORKDIR / "skills"

这段代码干了几件事:

代码 作用
load_dotenv(override=True) 读取 .env 文件里的环境变量
ANTHROPIC_BASE_URL 判断 如果使用自定义 base url,就移除 ANTHROPIC_AUTH_TOKEN
WORKDIR = Path.cwd() 当前运行目录作为工作区
client = Anthropic() 初始化 Anthropic 客户端
MODEL = os.environ["MODEL_ID"] 从环境变量读取模型名
SKILLS_DIR = WORKDIR / "skills" 默认从当前目录下的 skills/ 加载技能

这些初始化代码位于文件中部,负责准备模型客户端、模型 ID 和技能目录。

2. SkillLoader:技能加载器的核心

这是本文件最关键的类。

class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills_dir = skills_dir
        self.skills = {}
        self._load_all()

它的作用简单来说就是:启动时扫描 skills/ 目录,把所有技能读进内存。 self.skills 是一个字典,用来保存所有技能。每个技能大概会被存成这样:

{
  "pdf": {
    "meta": {...},
    "body": "...完整技能内容...",
    "path": "skills/pdf/SKILL.md"
  }
}

SkillLoader 会在初始化时调用 _load_all(),扫描 skills_dir 下面所有 SKILL.md 文件。

3. _load_all():扫描并加载所有技能文件

def _load_all(self):
    if not self.skills_dir.exists():
        return

    for f in sorted(self.skills_dir.rglob("SKILL.md")):
        text = f.read_text()
        meta, body = self._parse_frontmatter(text)
        name = meta.get("name", f.parent.name)
        self.skills[name] = {"meta": meta, "body": body, "path": str(f)}

这段逻辑很关键:

步骤 含义
if not self.skills_dir.exists() 如果没有 skills/ 目录,就直接返回
rglob("SKILL.md") 递归查找所有叫 SKILL.md 的文件
f.read_text() 读取技能文件内容
_parse_frontmatter(text) 拆分 YAML 元信息和正文
meta.get("name", f.parent.name) 优先用 frontmatter 里的 name,没有就用目录名
self.skills[name] = ... 存入技能字典

也就是说,技能名可以来自两种地方:

name: pdf

或者直接取自目录名:

skills/pdf/SKILL.md
      ↑
     pdf

相关的扫描和存储逻辑都在 SkillLoader._load_all() 里。

4. _parse_frontmatter():解析 YAML 头部

match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)

这行正则的意思是:

从文件开头开始匹配:

---
这里是 YAML 元信息
---
这里是正文

如果匹配不到 frontmatter:

return {}, text

那就说明没有元信息,整个文件都当成正文处理。

如果匹配到了:

meta = yaml.safe_load(match.group(1)) or {}
return meta, match.group(2).strip()

拆解一下:

部分 作用
match.group(1) YAML frontmatter
match.group(2) 技能正文
yaml.safe_load(...) 把 YAML 字符串转成 Python 字典
.strip() 去掉正文首尾空白

所以 SKILL.md 实际被拆成了两部分:

meta:name、description、tags 等信息息
body:完整技能说明

这就是后面“两层加载”的基础。

5. get_descriptions():生成第 1 层技能简介

def get_descriptions(self) -> str:
    """Layer 1: short descriptions for the system prompt."""

这个方法用来生成放进 system prompt 的内容。它不会返回完整的技能正文,而只返回类似这样的短描述:

 - pdf: Process PDF files...
 - code-review: Perform thorough code reviews...

代码里会读取每个技能的:

desc = skill["meta"].get("description", "No description")
tags = skill["meta"].get("tags", "")

然后拼成一行文本。这就是第 1 层:轻量索引,只告诉模型“我有这些技能”。

6. get_content():生成第 2 层完整技能内容

def get_content(self, name: str) -> str:
    """Layer 2: full skill body returned in tool_result."""

这个方法就是 load_skill 背后的真正逻辑。

如果技能不存在:

return f"Error: Unknown skill '{name}'. A vailable: ..."

如果技能存在:

return f""

也就是说,当模型调用:

load_skill("pdf")

工具会返回:


完整 PDF 处理说明……

这就是第 2 层:按需加载的完整知识。相关逻辑在 get_content() 中。

System Prompt 是怎么组装的?

SKILL_LOADER = SkillLoader(SKILLS_DIR)

SYSTEM = f"""You are a coding agent at {WORKDIR}.

Use load_skill to access specialized knowledge before tackling unfamiliar topics.

Skills a vailable:

{SKILL_LOADER.get_descriptions()}"""

这段就是把技能简介注入系统提示词。注意:注入的是 get_descriptions(),不是 get_content()

所以 system prompt 里只会有:

你是一个 coding agent
遇到陌生任务时可以用 load_skill 加载专业知识
当前可用技能:
- pdf: ...
- code-review: ...

不会一开始就把 PDF 的详细命令、代码审查清单、MCP 构建流程全部塞进去。

工具系统:比前面章节多了 load_skill

这个文件仍然保留了普通 Agent 工具:

工具 作用
bash 执行 shell 命令
read_file 读取文件
write_file 写文件
edit_file 替换文件中的指定文本
load_skill 按名称加载技能正文

工具处理函数在 TOOL_HANDLERS 里注册,其中 load_skill 对应的是:

"load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"])

也就是说,模型调用 load_skill,实际执行的就是 SKILL_LOADER.get_content(name)

load_skill 和普通工具有什么区别?

这是这个文件最值得琢磨的点。普通工具是“做事”的:

bash        → 执行命令
read_file   → 读文件
write_file  → 写文件
edit_file   → 改文件

load_skill 不是直接去做某件事,而是“给模型补知识”的:

load_skill → 返回一段专业操作说明,让模型变得更会做某类任务

所以它更像是一个:

知识工具 / 上下文注入工具 / 按需说明书加载器

举个例子:用户说“帮我处理这个 PDF”。模型看到 system prompt 里有:

- pdf: Process PDF files...

于是模型可以先调用:

{
  "name": "pdf"
}

然后 load_skill 返回完整的 PDF 技能,比如如何用 pdftotextPyMuPDFreportlabpandoc 等处理 PDF。skills/pdf/SKILL.md 里确实包含了读取、创建、合并、拆分 PDF 的具体操作说明。

safe_path():限制文件路径不能逃出工作区

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

这个函数用于保护 read_filewrite_fileedit_file 这些文件操作。它会把用户传入的路径拼到当前工作区,然后解析成绝对路径。如果最终路径不在 WORKDIR 里面,就抛出错误。

比如:

正常:src/main.py
危险:../../etc/passwd

../../etc/passwd 解析后会逃出工作区,所以会被拦截。相关路径保护逻辑在 safe_path() 中。

run_bash():执行命令,但做了简单危险命令拦截

dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
    return "Error: Dangerous command blocked"

它会拦截一些明显危险命令,比如:

rm -rf /
sudo
shutdown
reboot
> /dev/

然后用:

subprocess.run(..., shell=True, cwd=WORKDIR, timeout=120)

执行命令,最多跑 120 秒。输出会截断到 50000 字符。

不过这里得说清楚:这是教学版,不是生产级沙箱。原因有几个:

  • 它用了 shell=True
  • 危险命令只靠字符串黑名单拦截
  • safe_path() 只保护文件读写工具,并不保护 bash 命令本身
  • bash 理论上仍然能执行很多未被黑名单覆盖的危险操作

所以这个文件更适合用来学习 Agent 架构,不适合直接拿来跑不可信输入。

agent_loop():核心 Agent 循环

def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )

这里和前面的 Agent Loop 思路一致:

  • 把历史消息 messages 发给模型
  • 带上 system
  • 带上 tools
  • 等模型回复
  • 如果模型要调用工具,就执行工具
  • 把工具结果塞回 messages
  • 继续循环
  • 直到模型不再调用工具

判断是否继续的关键是:

if response.stop_reason != "tool_use":
    return

如果模型不是因为调用工具而停止,就说明它已经给出最终回答了,循环结束。

工具调用结果怎么回传给模型?

results.append(
    {
        "type": "tool_result",
        "tool_use_id": block.id,
        "content": str(output),
    }
)

messages.append({"role": "user", "content": results})

这段会把工具执行结果包装成 Anthropic API 需要的 tool_result 格式,再作为一条新的 user message 追加到历史消息里。

所以完整交互像这样:

用户:帮我处理 PDF
模型:我要调用 load_skill("pdf")
程序:执行 load_skill,拿到 PDF 技能说明
程序:把技能说明作为 tool_result 返回给模型
模型:读完技能说明后,再继续完成 PDF 任务

命令行入口

if __name__ == "__main__":
    history = []
    while True:
        query = input("\033[36ms05 >> \033[0m")

这部分让脚本变成一个命令行聊天程序。执行后会看到类似:

s05 >>

然后你输入问题,程序把你的输入追加到 history

history.append({"role": "user", "content": query})
agent_loop(history)

最后从 history[-1]["content"] 里拿出模型回复并打印。

和前面几个文件的关系

前面看过的文件可以这样串起来理解:

文件 重点
s01_agent_loop.py 最小 Agent Loop:用户输入 → 模型回复
s02_tool_use.py 加入工具调用:模型可以调用 bash / read / write 等工具
s03_todo_write.py 加入任务规划 / Todo 管理
s04_subagent.py 加入子 Agent:主 Agent 可以把任务委托出去
s05_skill_loading.py 加入技能加载:模型可以按需加载专业知识

s05 的本质升级是:

从“能调用工具”
升级到
“能根据任务加载专业操作手册”

换句话说:

Agent = LLM + Tools + Memory/Context + Planning + Skills

这里的 Skills 不是模型本身的能力,而是外部维护的一组专业说明书。

这个文件最核心的设计思想

可以总结成一句话:

System Prompt 只放索引,完整知识按需加载。

更具体一点:

skills/ 目录 = 技能库
SKILL.md frontmatter = 技能索引
SKILL.md body = 完整技能说明
get_descriptions() = 注入 system prompt 的轻量索引
load_skill() = 按需读取完整技能
tool_result = 把技能正文塞回模型上下文

这个设计很像:

书架目录 + 按需取书

不是一开始把所有书都摊在桌子上,而是先告诉模型:

我有 PDF 技能、代码审查技能、MCP 构建技能……

等模型真的要处理 PDF 时,再把 PDF 那本“说明书”拿出来。

最后用一句大白话理解

s05_skill_loading.py 就是在教你做一个更聪明的 AI Coding Agent:让它学会“用到什么学什么”,而不是“什么都先背下来”。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策