Skip to content

05 — 技能加载:按需注入领域知识

"用到什么知识,临时加载什么知识" — 通过 tool_result 注入,不塞 system prompt。

Harness 层:按需知识 — 模型开口要时才给的领域专长。

课程路径:[[01-agent-loop|s01 智能体循环]] → [[02-tool-use|s02 工具使用]] → [[03-todo-write|s03 待办写入]] → [[04-subagent|s04 子智能体]] → ⭐ s05 技能加载


为什么需要技能加载?

[[01-agent-loop|s01]] 中我们建立了最小 agent loop,[[02-tool-use|s02]] 添加了工具系统,[[03-todo-write|s03]] 引入了待办清单,[[04-subagent|s04]] 实现了子智能体调度。但还有一个根本问题没解决:模型怎么知道特定领域的最佳实践?

如果希望模型遵循 git 提交规范、走代码审查清单、按测试驱动开发的流程工作,最简单的做法是把所有规则塞进系统提示:

"Git 规范:……测试规范:……代码审查规范:……MCP 规范:……"

假设 10 个技能,每个 2000 token,就是 20,000 token。大部分技能跟当前任务毫无关系。更糟的是,系统提示的每个 token 都会占用模型的有效上下文窗口 — 模型真正用来reasoning 的空间被压缩了。

这就是技能加载解决的问题。

核心思想:两层知识注入

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>                             |
+--------------------------------------+

第一层(系统提示):只放技能名称和一句话描述,成本极低(~100 tokens/技能)。第二层(tool_result):模型主动请求时,才注入完整的技能内容(~2000 tokens/技能)。

关键洞察:由模型决定什么时候加载什么知识。Harness 只需提供"技能目录"和"技能加载工具"。模型会自主判断是否需要特定领域的指导。

技能文件结构

每个技能是一个目录,包含 SKILL.md 文件:

skills/
  git/
    SKILL.md
  code-review/
    SKILL.md
  agent-builder/
    SKILL.md
  mcp-builder/
    SKILL.md

SKILL.md 使用 YAML frontmatter 定义元数据:

markdown
---
name: git
description: Git workflow helpers — commit conventions, branch management, rebase workflow
---
# Git Workflow

## Commit Convention
- Use `type(scope): description` format
- Types: feat, fix, refactor, chore, docs, test

## Branch Management
1. Create feature branch from main
2. Rebase onto main before merge
...

SkillLoader 实现

扫描文件系统、解析 frontmatter、提供两个关键方法:

python
class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills = {}
        for f in sorted(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}

    def get_descriptions(self) -> str:
        """Layer 1: 返回精简短描述(放入系统提示)"""
        lines = []
        for name, skill in self.skills.items():
            desc = skill["meta"].get("description", "")
            lines.append(f"  - {name}: {desc}")
        return "\n".join(lines)

    def get_content(self, name: str) -> str:
        """Layer 2: 返回完整技能内容(通过 load_skill 工具注入)"""
        skill = self.skills.get(name)
        if not skill:
            return f"Error: Unknown skill '{name}'."
        return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"

集成到 Harness

与 [[02-tool-use|s02 的工具系统]] 一样,load_skill 不过是 dispatch map 中的又一个工具:

python
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Skills available:
{SKILL_LOADER.get_descriptions()}"""

TOOL_HANDLERS = {
    "read":          handle_read,
    "write":         handle_write,
    "bash":          handle_bash,
    "load_skill":    lambda **kw: SKILL_LOADER.get_content(kw["name"]),
}

模型在 agent loop 中读到"Skills available"列表,需要某个技能时就调用 load_skill(name)

完整的 Agent Loop(含技能加载)

User --> messages[] --> LLM
                         |
                   stop_reason == "tool_use"?
                     /                    \
                   yes                     no
                    |                       |
             dispatch tools             return text
               - read                     ↑
               - write                    |
               - bash              交互中模型可能:
               - load_skill          1. 浏览技能目录
                 ↓                   2. 按需加载技能
             append results          3. 遵循技能指令
               loop back ----→        执行任务

相对 s04 的变更

组件之前 (s04)之后 (s05)
Tools5 (基础 + task)5 (基础 + load_skill)
系统提示静态字符串+ 技能描述列表
知识库skills/*/SKILL.md 文件
注入方式两层(系统提示 + result)

技能 vs 系统提示:为什么两层更好?

方面全量系统提示按需技能加载
初始 token20,000+(10 技能)~1,000(技能目录)
峰值 token20,000+3,000 (加载 1-2 技能)
灵活性静态,更新换版本动态,随时添加技能
模型自主性被动接收主动选择加载时机
扩展性每加一个技能都增加成本加再多也只消耗目录 token

⭐ 核心要点

  1. 模型决定加载什么 — Harness 不预判模型需要什么知识,而是提供"技能目录"让模型自己选择
  2. 低成本目录,高成本内容 — 技能名称和描述便宜,完整指令仅在需要时加载
  3. 技能即文件 — 每个技能是一个包含 frontmatter + 内容的 SKILL.md,易于版本管理和协作
  4. load_skill 就是工具 — 没有任何特殊机制,和 read、write 一样是 agent loop 中的普通工具
  5. 技能扩展与模型无关 — 添加新技能不需要修改模型或系统提示的核心逻辑

试一试

sh
cd learn-claude-code
python3 agents/s05_skill_loading.py

试试这些 prompt(英文 prompt 对 LLM 效果更好,也可以用中文):

  1. What skills are available?
  2. Load the agent-builder skill and follow its instructions
  3. I need to do a code review -- load the relevant skill first
  4. Build an MCP server using the mcp-builder skill

思考题

  1. 技能描述的粒度应该多细?一个"git"技能够用,还是应该拆成"git-commit"、"git-rebase"、"git-branch"?
  2. 如果有两个技能的内容互相矛盾,模型会怎么处理?Harness 应该做什么?
  3. 什么时候应该把知识放进系统提示(总是存在),什么时候应该做成技能(按需加载)?

[[04-subagent|← 上一课:子智能体]] | [[06-context-compact|下一课:上下文压缩 →]] | [[术语表|术语表]]

基于 Learn Claude Code 项目改编