news 2026/9/7 14:47:12

learn-claude-code 技能加载机制详解:用 SKILL.md 与双层注入实现 Agent 知识的按需加载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
learn-claude-code 技能加载机制详解:用 SKILL.md 与双层注入实现 Agent 知识的按需加载

learn-claude-code 技能加载机制详解:用 SKILL.md 与双层注入实现 Agent 知识的按需加载

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

本文围绕 learn-claude-code 课程第 s05 节「Skills(技能加载)」展开,讲解如何用skills/*/SKILL.md目录结构 +load_skill工具,把领域知识从系统提示词中剥离出来、由模型按需注入到tool_result中。读完后你将能够独立搭建一套「目录即注册、元数据进系统提示、正文按需加载」的技能体系,并理解 agents/s05_skill_loading.py 中SkillLoader的完整实现、边界条件与测试验证方式。

一、要解决的问题:系统提示词不是知识库

在编码 Agent 中,我们经常希望模型遵守一套领域工作流:git 操作规范、测试编写模式、代码评审检查清单、PDF 处理流程等等。最直觉的做法是把它们全部写进系统提示词(system prompt),但 docs/ja/s05-skill-loading.md 指出的核心矛盾在于:系统提示词是每一轮对话都会随请求发送的常驻上下文,把所有技能全文放进去,等于为大量当前任务用不到的知识持续支付 token 成本——原文给出的估算相当直接:10 个技能 × 每技能约 2000 token ≈ 20000 token,其中绝大部分对任意给定任务都是无关的。

s05 节给出的设计原则是一句话:

不要把所有东西放进系统提示词,而是在需要时加载(Load on demand)。

这一原则在整个 learn-claude-code 的 harness 分层中处于「知识与观察层」的位置:模型负责决策,harness 负责在正确的时机供给正确的上下文。

二、方案:双层注入架构(Two-layer Injection)

s05 的核心方案是把每个技能拆成「目录信息」和「技能正文」两层,分别放在两个成本等级不同的位置:

System prompt (Layer 1 -- always present): +--------------------------------------+ || You are a coding agent. | || Skills available: | || - git: Git workflow helpers | ~100 tokens/skill || - test: Testing best practices | +--------------------------------------+ When model calls load_skill("git"): +--------------------------------------+ || tool_result (Layer 2 -- on demand): | || <skill name="git"> | || Full git workflow instructions... | ~2000 tokens || Step 1: ... | || </skill> | +--------------------------------------+
位置内容成本生命周期
第 1 层系统提示词技能名称+ 一句话描述每技能约 100 token全程常驻
第 2 层tool_result技能正文(完整工作流指令)按需,约 2000 token/次仅当模型调用load_skill后进入消息历史

这样模型在每一轮都「知道有哪些技能」(低成本),只在判断相关时才「读取全文」(高成本)。第 1 层的描述文本因此承担了类似「路由」的职责——它写得越准确,模型越少误触发或漏触发load_skill

三、机制:每个技能就是一个含 SKILL.md 的目录

3.1 技能目录布局

约定每个技能是一个目录,目录内必须有SKILL.md,目录名即技能标识符:

skills/ pdf/ SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ... code-review/ SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...

SKILL.md由两部分组成:

  1. YAML frontmatter:位于首尾两条---分隔线之间,声明name(技能名)与description(第 1 层使用的描述);
  2. 正文 body:分隔线之后的 Markdown 全文,即load_skill时注入的完整领域知识。

3.2 仓库内置的 4 个真实技能

当前仓库 skills/ 目录下有 4 个可直接运行的示例技能,它们就是 s05 实验的数据源:

技能目录frontmatterdescription(节选)正文要点
skills/pdf/SKILL.mdProcess PDF files - extract text, create PDFs, merge documentspdftotext/PyMuPDF 读取、pandoc/ReportLab/wkhtmltopdf 生成、PyMuPDF 合并与拆分,以及一张「任务 → 库 → 安装命令」对照表
skills/code-review/SKILL.mdPerform thorough code reviews with security, performance, and maintainability analysis五维检查清单(Security/Correctness/Performance/Maintainability/Testing)、固定输出格式模板、Python/JS 反例模式、npm auditradon cc等评审命令与 7 步评审工作流
skills/mcp-builder/SKILL.mdBuild MCP (Model Context Protocol) servers that give Claude new capabilitiesMCP 概念(Tools/Resources/Prompts)、Python MCP server 模板、stdio 接入方式
skills/agent-builder/SKILL.mdDesign and build AI agents for any domain(多行 YAML 块标量)Agent 三要素(Capabilities/Knowledge/Context)、agent loop 心法,并附带references/scripts/子目录

两个值得注意的实现细节:

  • agent-builder是多行 description 的实例。它的 frontmatter 使用 YAML 块标量(description: |后跟多行缩进文本),说明第 1 层描述并不限于单行短语,测试 tests/test_skill_loading.py 也专门覆盖了块标量内含---行、以及 CRLF 换行的解析正确性。
  • 子目录不产生独立技能agent-builder/references/(如 agent-philosophy.md、minimal-agent.py)和agent-builder/scripts/init_agent.py不会被注册成技能——技能名以顶层目录名/frontmattername为准;这些附属文件的设计意图是:模型加载正文后,再用read_file/bash自行深入读取,相当于「正文里引用的二级知识库」。

3.3 SkillLoader:扫描、解析、双接口

s05 源码 agents/s05_skill_loading.py 中,SkillLoader在启动时一次性完成「扫描 + 解析」,之后对外只提供两个廉价接口:

class SkillLoader: def __init__(self, skills_dir: Path): self.skills_dir = skills_dir self.skills = {} self._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)} def _parse_frontmatter(self, text: str) -> tuple: """Parse YAML frontmatter between --- delimiters.""" match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL) if not match: return {}, text try: meta = yaml.safe_load(match.group(1)) or {} except yaml.YAMLError: meta = {} return meta, match.group(2).strip() def get_descriptions(self) -> str: """Layer 1: short descriptions for the system prompt.""" if not self.skills: return "(no skills available)" lines = [] for name, skill in self.skills.items(): desc = skill["meta"].get("description", "No description") tags = skill["meta"].get("tags", "") line = f" - {name}: {desc}" if tags: line += f" [{tags}]" lines.append(line) return "\n".join(lines) def get_content(self, name: str) -> str: """Layer 2: full skill body returned in tool_result.""" skill = self.skills.get(name) if not skill: return f"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}" return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"

实现要点逐条拆解:

  1. rglob("SKILL.md")递归扫描(L68):只要目录名叫SKILL.md就会被发现,因此技能目录可以嵌套;sorted(...)保证加载顺序稳定。技能名优先取 frontmatter 的name字段,缺省时回退为父目录名f.parent.name),这就是「目录名即技能标识」约定的实现落点。
  2. frontmatter 解析的容错(L74-L83):正则要求首行恰好是---、用非贪婪(.*?)匹配到下一条独立---行;没有匹配则返回({}, 原文)——即「无 frontmatter 的文件整体当作正文」;yaml.safe_loadYAMLError时静默降级为空元数据。这套降级路径不是臆测,测试 tests/test_skill_loading.py 显式断言了:---not frontmatter这类不合法开头不触发解析、块标量中夹带的---行不会被误认为结束符。
  3. get_descriptions()就是第 1 层(L85-L97):每技能输出一行- {name}: {desc},源码还支持可选的tags字段追加[tags]后缀。注意 s05 版与 s07 重构版的行为差异:s07 的SkillLoader(s07_skill_loading/code.py)把元数据解析改成了逐行扫描的parse_frontmatter静态方法,并增加了「非 dict 的 YAML(如列表)回退为{}、description 为空时回退取正文首行」等防御逻辑——从源码结构看,这是 s05 之后针对更恶劣输入补强的版本。
  4. get_content()就是第 2 层(L99-L104):命中时把正文包进<skill name="...">XML 风格标签再返回——标签给模型一个明确的知识边界标记,告诉它「从这里开始是某个技能的全部指令,结束于</skill>」。未命中时返回的错误信息会附带全部可用技能名,这让模型可以在一次纠错循环内自修复拼写错误,而不是盲目重试。

3.4 注入点:系统提示词 + 工具注册

两个注入点各只有一行胶水代码。第 1 层在模块加载时拼进SYSTEM常量(agents/s05_skill_loading.py#L107-L114):

SKILL_LOADER = SkillLoader(SKILLS_DIR) # Layer 1: skill metadata injected into system prompt SYSTEM = f"""You are a coding agent at {WORKDIR}. Use load_skill to access specialized knowledge before tackling unfamiliar topics. Skills available: {SKILL_LOADER.get_descriptions()}"""

第 2 层则是一个普通工具处理器,与bash/read_file等基础工具并列注册(L166-L184):

TOOL_HANDLERS = { "bash": lambda **kw: run_bash(kw["command"]), "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")), "write_file": lambda **kw: run_write(kw["path"], kw["content"]), "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]), "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]), } TOOLS = [ # ...base tools... {"name": "load_skill", "description": "Load specialized knowledge by name.", "input_schema": {"type": "object", "properties": {"name": {"type": "string", "description": "Skill name to load"}}, "required": ["name"]}}, ]

load_skillinput_schema只有一个必填的name字符串参数——接口刻意做到极简。agent_loop(L188-L208)中,当模型返回stop_reason == "tool_use"且块为load_skill时,get_content的返回值被原样塞进tool_result并回传——至此,完整技能正文已作为一条消息进入历史,模型随后基于它行动,而系统提示词始终只有那一两行目录。

四、相对 s04 的变化

s05 在 s04(hooks)基础上只做了三处增量,原变更对照表如下:

ComponentBefore (s04)After (s05)
Tools5 (base + task)5 (base + load_skill)
System promptStatic string+ skill descriptions
KnowledgeNoneskills/*/SKILL.mdfiles
InjectionNoneTwo-layer (system + result)

也就是说,s05 的改造面非常克制:工具数量不变(基础工具集 + 一个知识工具),变化集中在「系统提示词从静态字符串变为目录驱动的模板」和「新增了skills/文件作为外部知识源」两点。

五、动手实验

5.1 环境准备

s05 脚本的运行依赖(从 agents/s05_skill_loading.py 的导入与环境变量读取确认):

  • Python 包:anthropicpython-dotenvload_dotenv)、pyyamlyaml.safe_load),仓库根目录 requirements.txt 提供项目统一依赖;
  • 环境变量:MODEL_ID必填,代码中os.environ["MODEL_ID"]缺省即KeyError)、ANTHROPIC_BASE_URL(可选,指向自定义网关;注意代码在设置了ANTHROPIC_BASE_URL时会主动popANTHROPIC_AUTH_TOKEN,避免认证头冲突,见 L49-L50);
  • .env文件会被load_dotenv(override=True)优先加载。

5.2 运行

cd learn-claude-code python agents/s05_skill_loading.py

启动后出现青色s05 >>交互提示符。仓库文档(docs/ja/s05-skill-loading.md「試してみる」小节)给出的 4 条验证提示,正好覆盖双层机制的不同路径:

  1. What skills are available?—— 验证第 1 层:模型不加载任何正文,仅凭系统提示词中的目录作答;
  2. Load the agent-builder skill and follow its instructions—— 直接指令式触发load_skill("agent-builder"),观察终端打印的> load_skill:输出;
  3. I need to do a code review -- load the relevant skill first—— 验证隐式路由:模型需要自行把「code review」意图映射到code-review技能名;
  4. Build an MCP server using the mcp-builder skill—— 技能正文驱动后续多轮工具调用(写文件、执行命令),验证第 2 层知识能否真正改变模型行为。

观察要点:每条工具调用都会打印> {tool}:前缀和输出的前 200 字符(L205-L206),据此可以确认load_skill恰好在预期轮次发生、且全文仅进入消息历史一次。

5.3 测试用例给出的行为边界

tests/test_skill_loading.py 针对技能加载课程代码(s07 与 s15 集成版)断言了 s05 同款语义,可作为自建技能体系时的验收清单:

  • 目录小、正文大(test_catalog_stays_small...):SYSTEM中只出现- code-review: Review code for bugs, regressions, and missing tests.一行,断言完整指令UNIQUE_FULL_INSTRUCTION不在SYSTEM里,而load("code-review")返回的是整个SKILL.md原文;
  • 工具面收敛(L96-L107):TOOLS只暴露bash / read_file / write_file / edit_file / glob / load_skill六个工具,技能不引入额外工具;
  • frontmatter 边界(L110-L128):非独立行的---开头不解析;块标量|中含---内容不被截断,CRLF 换行同样成立;
  • 回退与安全性(L131-L164):name/description为空时回退取正文首行作描述;YAML 解析成非 mapping(如列表)时回退空元数据;SKILL.md是符号链接且指向skills/外部文件时拒绝注册——这是 s07 版在scan()manifest.resolve().is_relative_to(skills_root)检查(s07_skill_loading/code.py#L87-L91)对应的防御,防止目录投毒把任意文件注册为技能。

自建技能时的三条实用建议(均由上述源码直接导出):

  1. description 要写「何时该用」:对照仓库 4 个内置技能,pdfcode-review的 description 都带触发条件("Use when user asks to..."),agent-builder甚至列出了 5 类触发场景 + 关键词,这正是第 1 层路由质量的来源;
  2. 正文开头给结论、中间给命令:内置技能普遍是「工作流 → 可复制命令/代码 → 对照表 → 最佳实践」的顺序,方便模型加载后立即执行;
  3. 不要让正文依赖系统提示词load_skill注入的正文是一段tool_result,技能之间不共享上下文,每个SKILL.md必须自包含。

六、小结:这套机制为什么值得抄进自己的 Agent

s05 的完整实现不超过 250 行(agents/s05_skill_loading.py),其可迁移的设计是:

  • 知识外置skills/*/SKILL.md让领域知识成为可版本化、可 diff、可增量添加的文件,而非散落的提示词;
  • 成本分层:系统提示词只付「目录税」(每技能约 100 token),全文只在命中时付一次;
  • 失败自愈:未知技能名返回错误时附带可用清单,一次纠错即可收敛;
  • 接口最小化load_skill(name)一个参数,正文用<skill name="...">标签包裹,边界清晰。

与后续章节的关系:s07(s07_skill_loading/code.py)把SkillLoader重构得更健壮并接入权限 hooks,s15(s15_integrated_harness/code.py)将其并入完整 harness;而 agents/s_full.py 是全部机制的合体版。理解 s05 的「双层注入」后,再看这些版本只会是同一思想在边界条件上的加固。

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 14:46:48

艾略特波浪理论实战指南:八浪循环、数浪铁律与斐波那契测幅

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:41:29

音乐混音技术:从IRIS OUT REMIX看场景化改编与音频处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:41:22

MCP协议深度实战:让AI真正掌控你的工具链

MCP协议深度实战&#xff1a;让AI真正掌控你的工具链 过去一年我试过不少AI编程助手和Agent框架&#xff0c;最让我难受的场景是&#xff1a;AI聊得头头是道&#xff0c;一旦让它去读文件、改代码、跑测试&#xff0c;它就卡住了。不是模型能力不够&#xff0c;而是它根本够不到…

作者头像 李华
网站建设 2026/9/7 14:40:19

Kafka集群搭建实战:从规划部署到性能调优全指南

搞大数据这行&#xff0c;基本绕不开 Kafka 这关。无论是日志采集、实时数仓&#xff0c;还是 Flink/Spark 的数据入口&#xff0c;Kafka 集群都是整个数据管道里最核心的“运输大脑”。但很多朋友一上来就急着装个单机版&#xff0c;跑了 demo 就觉得会了&#xff0c;结果一到…

作者头像 李华
网站建设 2026/9/7 14:38:28

python的图论工业场景模拟第九十三篇:供应商资质二分图清洗与非法过滤,任务:过滤不在合法列表中的脏记录建合规二分图,图建模说明:二分无向图,边=资质匹配,核心点:数据校验与属性标注。

供应商资质二分图清洗与非法过滤&#xff1a;过滤不在合法列表中的脏记录&#xff0c;建合规二分图"某汽车零部件 Tier1 企业&#xff0c;SRM 系统里存了 200 供应商和 50 种资质证书。每次招标前&#xff0c;采购员要人工核对哪些供应商有合法资质——Excel 里混着过期证…

作者头像 李华