Files
analysis_claude_code/s07_skill_loading/README.zh.md
2026-08-11 15:13:13 +08:00

144 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# s07: Skill Loading — 用到的时候才加载
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s18 → s19
> *"用到时再加载, 别全塞 prompt 里"* — 通过 tool_result 注入, 不塞 system prompt。
>
> **Harness 层**: 知识 — 按需加载, 不堆满上下文。
---
## 问题
假设某个项目有一套 React 组件规范、一份 SQL 风格指南和一份 API 设计文档。我们希望 Agent 在开发过程中遵守这些规范。最直接的做法,是把它们全部放进 system prompt
```python
SYSTEM = (
f"You are a coding agent. "
+ open("docs/react-style.md").read()
+ open("docs/sql-style.md").read()
+ open("docs/api-design.md").read()
)
```
这样,每次调用 LLM 都会携带三份完整文档。即使当前任务只涉及其中一份,另外两份仍会占用上下文。
---
## 解决方案
![Skill Overview](images/skill-overview.svg)
保留上一章的最小 hook 结构、`todo_write` 和子 Agent本章重点转向新增的 `load_skill` 工具。启动时把技能目录注入 SYSTEM prompt运行时多注册一个工具加载完整内容用到才花 token。
两层设计:
| 层 | 位置 | 时机 | 代价 |
|---|------|------|------|
| 1. 目录 | system prompt | 启动时注入harness 扫描 skills/ | ~100 tokens/skill每轮都带 |
| 2. 内容 | tool_result | Agent 调用 load_skill 时SKILL.md 可指引后续的 read_file/bash 调用,用于按需访问额外资源 | ~2000 tokens/skill按需 |
dispatch 机制不变load_skill 通过 `TOOL_HANDLERS[block.name]` 分发。
---
## 工作原理
**skills/ 目录**,每个技能一个子目录,包含 `SKILL.md` 文件:
```
skills/
agent-builder/SKILL.md
code-review/SKILL.md
mcp-builder/SKILL.md
pdf/SKILL.md
```
**第一级:启动时注入目录**harness 启动时调用 `_scan_skills()` 扫描 skills/ 目录,解析每个 SKILL.md 的 YAML frontmatter`name``description`),存入 `SKILL_REGISTRY` 字典。`list_skills()` 从注册表生成目录,注入 SYSTEM prompt。Agent 每轮都能看到"我有哪些技能可用",不花额外 API 调用:
```python
SKILL_REGISTRY: dict[str, dict] = {}
def _scan_skills():
if not SKILLS_DIR.exists():
return
for d in sorted(SKILLS_DIR.iterdir()):
if not d.is_dir():
continue
manifest = d / "SKILL.md"
if manifest.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}
_scan_skills() # runs once at startup
def list_skills() -> str:
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
def build_system() -> str:
catalog = list_skills()
return (
f"You are a coding agent at {WORKDIR}. "
f"Skills available:\n{catalog}\n"
"Use load_skill to get full details when needed."
)
SYSTEM = build_system()
```
**第二级load_skill**Agent 决定"我需要 SQL 风格指南",调用 `load_skill("sql-style")`。通过注册表查找不走文件路径没有路径遍历风险。SKILL.md 内容通过 `tool_result` 注入,并可通过现有的 file 和 bash 工具进一步访问引用的 `references/``scripts/``assets/`
```python
def load_skill(name: str) -> str:
skill = SKILL_REGISTRY.get(name)
if not skill:
return f"Skill not found: {name}"
return skill["content"]
```
关键区别:技能内容不是 system prompt 的一部分,它作为一次工具结果进入当前 messages。后续调用会随历史一起携带直到上下文压缩、截断或会话结束。这和 s08 的 compact 自然衔接:按需加载解决了"不该提前带的不要带"compact 解决"该丢的怎么丢"。
---
## 相对 s06 的变更
| 组件 | 之前 (s06) | 之后 (s07) |
|------|-----------|-----------|
| 工具数量 | 7 (bash, read, write, edit, glob, todo_write, task) | 8 (+load_skill) |
| 知识加载 | 无 | 两级:启动时目录注入 SYSTEM + 运行时 load_skillSKILL.md 可指引后续资源访问 |
| SYSTEM 提示 | 静态字符串 | 启动时扫描 skills/ 注入目录 |
| 技能注册表 | 无 | SKILL_REGISTRY启动时填充防路径遍历 |
| 循环 | 不变 | 不变skill 工具自动分发) |
---
## 试一下
```sh
cd learn-claude-code
python s07_skill_loading/code.py
```
试试这些 prompt
1. `What skills are available?`
2. `Load the code-review skill and follow its instructions`
3. `I need to do a code review -- load the relevant skill first`
观察重点Agent 是否直接从 SYSTEM 里的目录知道有哪些技能?需要完整规范时是否出现 `[HOOK] load_skill`?加载后回答是否使用了对应 skill 的说明?
---
## 接下来
按需加载解决了"不该带的不要带"。但另一个问题来了Agent 连续工作 30 分钟后messages 列表塞满了中间过程。旧的 tool_result、过时的文件内容占着上下文但不产生价值。
s08 Context Compact → 四层压缩策略。便宜的先跑,贵的后跑。
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->