mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-03-22 02:15:42 +08:00
- s03: inject reminder into tool_result instead of mutating history (#37) - s05: SkillLoader uses rglob("SKILL.md") + frontmatter name priority, matching Agent Skills standard (#30, PR #34) - CI: upgrade actions/checkout and actions/setup-node to v6 (#36) - docs: update s05 skill directory structure in all 3 languages
This commit is contained in:
parent
4f39ee4512
commit
dbffe7c8d0
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -14,9 +14,9 @@ jobs:
|
|||||||
working-directory: web
|
working-directory: web
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -55,7 +55,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|||||||
@ -163,11 +163,7 @@ TOOLS = [
|
|||||||
def agent_loop(messages: list):
|
def agent_loop(messages: list):
|
||||||
rounds_since_todo = 0
|
rounds_since_todo = 0
|
||||||
while True:
|
while True:
|
||||||
# Nag reminder: if 3+ rounds without a todo update, inject reminder
|
# Nag reminder is injected below, alongside tool results
|
||||||
if rounds_since_todo >= 3 and messages:
|
|
||||||
last = messages[-1]
|
|
||||||
if last["role"] == "user" and isinstance(last.get("content"), list):
|
|
||||||
last["content"].insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
|
|
||||||
response = client.messages.create(
|
response = client.messages.create(
|
||||||
model=MODEL, system=SYSTEM, messages=messages,
|
model=MODEL, system=SYSTEM, messages=messages,
|
||||||
tools=TOOLS, max_tokens=8000,
|
tools=TOOLS, max_tokens=8000,
|
||||||
@ -189,6 +185,8 @@ def agent_loop(messages: list):
|
|||||||
if block.name == "todo":
|
if block.name == "todo":
|
||||||
used_todo = True
|
used_todo = True
|
||||||
rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
|
rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
|
||||||
|
if rounds_since_todo >= 3:
|
||||||
|
results.insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
|
||||||
messages.append({"role": "user", "content": results})
|
messages.append({"role": "user", "content": results})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,19 +7,25 @@ Two-layer skill injection that avoids bloating the system prompt:
|
|||||||
Layer 1 (cheap): skill names in system prompt (~100 tokens/skill)
|
Layer 1 (cheap): skill names in system prompt (~100 tokens/skill)
|
||||||
Layer 2 (on demand): full skill body in tool_result
|
Layer 2 (on demand): full skill body in tool_result
|
||||||
|
|
||||||
|
skills/
|
||||||
|
pdf/
|
||||||
|
SKILL.md <-- frontmatter (name, description) + body
|
||||||
|
code-review/
|
||||||
|
SKILL.md
|
||||||
|
|
||||||
System prompt:
|
System prompt:
|
||||||
+--------------------------------------+
|
+--------------------------------------+
|
||||||
| You are a coding agent. |
|
| You are a coding agent. |
|
||||||
| Skills available: |
|
| Skills available: |
|
||||||
| - git: Git workflow helpers | <-- Layer 1: metadata only
|
| - pdf: Process PDF files... | <-- Layer 1: metadata only
|
||||||
| - test: Testing best practices |
|
| - code-review: Review code... |
|
||||||
+--------------------------------------+
|
+--------------------------------------+
|
||||||
|
|
||||||
When model calls load_skill("git"):
|
When model calls load_skill("pdf"):
|
||||||
+--------------------------------------+
|
+--------------------------------------+
|
||||||
| tool_result: |
|
| tool_result: |
|
||||||
| <skill> |
|
| <skill> |
|
||||||
| Full git workflow instructions... | <-- Layer 2: full body
|
| Full PDF processing instructions | <-- Layer 2: full body
|
||||||
| Step 1: ... |
|
| Step 1: ... |
|
||||||
| Step 2: ... |
|
| Step 2: ... |
|
||||||
| </skill> |
|
| </skill> |
|
||||||
@ -44,10 +50,10 @@ if os.getenv("ANTHROPIC_BASE_URL"):
|
|||||||
WORKDIR = Path.cwd()
|
WORKDIR = Path.cwd()
|
||||||
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
||||||
MODEL = os.environ["MODEL_ID"]
|
MODEL = os.environ["MODEL_ID"]
|
||||||
SKILLS_DIR = WORKDIR / ".skills"
|
SKILLS_DIR = WORKDIR / "skills"
|
||||||
|
|
||||||
|
|
||||||
# -- SkillLoader: parse .skills/*.md files with YAML frontmatter --
|
# -- SkillLoader: scan skills/<name>/SKILL.md with YAML frontmatter --
|
||||||
class SkillLoader:
|
class SkillLoader:
|
||||||
def __init__(self, skills_dir: Path):
|
def __init__(self, skills_dir: Path):
|
||||||
self.skills_dir = skills_dir
|
self.skills_dir = skills_dir
|
||||||
@ -57,10 +63,10 @@ class SkillLoader:
|
|||||||
def _load_all(self):
|
def _load_all(self):
|
||||||
if not self.skills_dir.exists():
|
if not self.skills_dir.exists():
|
||||||
return
|
return
|
||||||
for f in sorted(self.skills_dir.glob("*.md")):
|
for f in sorted(self.skills_dir.rglob("SKILL.md")):
|
||||||
name = f.stem
|
|
||||||
text = f.read_text()
|
text = f.read_text()
|
||||||
meta, body = self._parse_frontmatter(text)
|
meta, body = self._parse_frontmatter(text)
|
||||||
|
name = meta.get("name", f.parent.name)
|
||||||
self.skills[name] = {"meta": meta, "body": body, "path": str(f)}
|
self.skills[name] = {"meta": meta, "body": body, "path": str(f)}
|
||||||
|
|
||||||
def _parse_frontmatter(self, text: str) -> tuple:
|
def _parse_frontmatter(self, text: str) -> tuple:
|
||||||
|
|||||||
@ -199,7 +199,7 @@ class SkillLoader:
|
|||||||
def __init__(self, skills_dir: Path):
|
def __init__(self, skills_dir: Path):
|
||||||
self.skills = {}
|
self.skills = {}
|
||||||
if skills_dir.exists():
|
if skills_dir.exists():
|
||||||
for f in sorted(skills_dir.glob("*.md")):
|
for f in sorted(skills_dir.rglob("SKILL.md")):
|
||||||
text = f.read_text()
|
text = f.read_text()
|
||||||
match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
|
match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
|
||||||
meta, body = {}, text
|
meta, body = {}, text
|
||||||
@ -209,7 +209,8 @@ class SkillLoader:
|
|||||||
k, v = line.split(":", 1)
|
k, v = line.split(":", 1)
|
||||||
meta[k.strip()] = v.strip()
|
meta[k.strip()] = v.strip()
|
||||||
body = match.group(2).strip()
|
body = match.group(2).strip()
|
||||||
self.skills[f.stem] = {"meta": meta, "body": body}
|
name = meta.get("name", f.parent.name)
|
||||||
|
self.skills[name] = {"meta": meta, "body": body}
|
||||||
|
|
||||||
def descriptions(self) -> str:
|
def descriptions(self) -> str:
|
||||||
if not self.skills: return "(no skills)"
|
if not self.skills: return "(no skills)"
|
||||||
|
|||||||
@ -33,24 +33,27 @@ Layer 1: skill *names* in system prompt (cheap). Layer 2: full *body* via tool_r
|
|||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. Skill files live in `.skills/` as Markdown with YAML frontmatter.
|
1. Each skill is a directory containing a `SKILL.md` with YAML frontmatter.
|
||||||
|
|
||||||
```
|
```
|
||||||
.skills/
|
skills/
|
||||||
git.md # ---\n description: Git workflow\n ---\n ...
|
pdf/
|
||||||
test.md # ---\n description: Testing patterns\n ---\n ...
|
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 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
2. SkillLoader parses frontmatter, separates metadata from body.
|
2. SkillLoader scans for `SKILL.md` files, uses the directory name as the skill identifier.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class SkillLoader:
|
class SkillLoader:
|
||||||
def __init__(self, skills_dir: Path):
|
def __init__(self, skills_dir: Path):
|
||||||
self.skills = {}
|
self.skills = {}
|
||||||
for f in sorted(skills_dir.glob("*.md")):
|
for f in sorted(skills_dir.rglob("SKILL.md")):
|
||||||
text = f.read_text()
|
text = f.read_text()
|
||||||
meta, body = self._parse_frontmatter(text)
|
meta, body = self._parse_frontmatter(text)
|
||||||
self.skills[f.stem] = {"meta": meta, "body": body}
|
name = meta.get("name", f.parent.name)
|
||||||
|
self.skills[name] = {"meta": meta, "body": body}
|
||||||
|
|
||||||
def get_descriptions(self) -> str:
|
def get_descriptions(self) -> str:
|
||||||
lines = []
|
lines = []
|
||||||
@ -87,7 +90,7 @@ The model learns what skills exist (cheap) and loads them when relevant (expensi
|
|||||||
|----------------|------------------|----------------------------|
|
|----------------|------------------|----------------------------|
|
||||||
| Tools | 5 (base + task) | 5 (base + load_skill) |
|
| Tools | 5 (base + task) | 5 (base + load_skill) |
|
||||||
| System prompt | Static string | + skill descriptions |
|
| System prompt | Static string | + skill descriptions |
|
||||||
| Knowledge | None | .skills/*.md files |
|
| Knowledge | None | skills/\*/SKILL.md files |
|
||||||
| Injection | None | Two-layer (system + result)|
|
| Injection | None | Two-layer (system + result)|
|
||||||
|
|
||||||
## Try It
|
## Try It
|
||||||
|
|||||||
@ -33,24 +33,27 @@ When model calls load_skill("git"):
|
|||||||
|
|
||||||
## 仕組み
|
## 仕組み
|
||||||
|
|
||||||
1. スキルファイルは`.skills/`にYAMLフロントマター付きMarkdownとして配置される。
|
1. 各スキルは `SKILL.md` ファイルを含むディレクトリとして配置される。
|
||||||
|
|
||||||
```
|
```
|
||||||
.skills/
|
skills/
|
||||||
git.md # ---\n description: Git workflow\n ---\n ...
|
pdf/
|
||||||
test.md # ---\n description: Testing patterns\n ---\n ...
|
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 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
2. SkillLoaderがフロントマターを解析し、メタデータと本体を分離する。
|
2. SkillLoaderが `SKILL.md` を再帰的に探索し、ディレクトリ名をスキル識別子として使用する。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class SkillLoader:
|
class SkillLoader:
|
||||||
def __init__(self, skills_dir: Path):
|
def __init__(self, skills_dir: Path):
|
||||||
self.skills = {}
|
self.skills = {}
|
||||||
for f in sorted(skills_dir.glob("*.md")):
|
for f in sorted(skills_dir.rglob("SKILL.md")):
|
||||||
text = f.read_text()
|
text = f.read_text()
|
||||||
meta, body = self._parse_frontmatter(text)
|
meta, body = self._parse_frontmatter(text)
|
||||||
self.skills[f.stem] = {"meta": meta, "body": body}
|
name = meta.get("name", f.parent.name)
|
||||||
|
self.skills[name] = {"meta": meta, "body": body}
|
||||||
|
|
||||||
def get_descriptions(self) -> str:
|
def get_descriptions(self) -> str:
|
||||||
lines = []
|
lines = []
|
||||||
@ -87,7 +90,7 @@ TOOL_HANDLERS = {
|
|||||||
|----------------|------------------|----------------------------|
|
|----------------|------------------|----------------------------|
|
||||||
| Tools | 5 (base + task) | 5 (base + load_skill) |
|
| Tools | 5 (base + task) | 5 (base + load_skill) |
|
||||||
| System prompt | Static string | + skill descriptions |
|
| System prompt | Static string | + skill descriptions |
|
||||||
| Knowledge | None | .skills/*.md files |
|
| Knowledge | None | skills/\*/SKILL.md files |
|
||||||
| Injection | None | Two-layer (system + result)|
|
| Injection | None | Two-layer (system + result)|
|
||||||
|
|
||||||
## 試してみる
|
## 試してみる
|
||||||
|
|||||||
@ -33,24 +33,27 @@ When model calls load_skill("git"):
|
|||||||
|
|
||||||
## 工作原理
|
## 工作原理
|
||||||
|
|
||||||
1. 技能文件以 Markdown 格式存放在 `.skills/`, 带 YAML frontmatter。
|
1. 每个技能是一个目录, 包含 `SKILL.md` 文件和 YAML frontmatter。
|
||||||
|
|
||||||
```
|
```
|
||||||
.skills/
|
skills/
|
||||||
git.md # ---\n description: Git workflow\n ---\n ...
|
pdf/
|
||||||
test.md # ---\n description: Testing patterns\n ---\n ...
|
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 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
2. SkillLoader 解析 frontmatter, 分离元数据和正文。
|
2. SkillLoader 递归扫描 `SKILL.md` 文件, 用目录名作为技能标识。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class SkillLoader:
|
class SkillLoader:
|
||||||
def __init__(self, skills_dir: Path):
|
def __init__(self, skills_dir: Path):
|
||||||
self.skills = {}
|
self.skills = {}
|
||||||
for f in sorted(skills_dir.glob("*.md")):
|
for f in sorted(skills_dir.rglob("SKILL.md")):
|
||||||
text = f.read_text()
|
text = f.read_text()
|
||||||
meta, body = self._parse_frontmatter(text)
|
meta, body = self._parse_frontmatter(text)
|
||||||
self.skills[f.stem] = {"meta": meta, "body": body}
|
name = meta.get("name", f.parent.name)
|
||||||
|
self.skills[name] = {"meta": meta, "body": body}
|
||||||
|
|
||||||
def get_descriptions(self) -> str:
|
def get_descriptions(self) -> str:
|
||||||
lines = []
|
lines = []
|
||||||
@ -87,7 +90,7 @@ TOOL_HANDLERS = {
|
|||||||
|----------------|------------------|--------------------------------|
|
|----------------|------------------|--------------------------------|
|
||||||
| Tools | 5 (基础 + task) | 5 (基础 + load_skill) |
|
| Tools | 5 (基础 + task) | 5 (基础 + load_skill) |
|
||||||
| 系统提示 | 静态字符串 | + 技能描述列表 |
|
| 系统提示 | 静态字符串 | + 技能描述列表 |
|
||||||
| 知识库 | 无 | .skills/*.md 文件 |
|
| 知识库 | 无 | skills/\*/SKILL.md 文件 |
|
||||||
| 注入方式 | 无 | 两层 (系统提示 + result) |
|
| 注入方式 | 无 | 两层 (系统提示 + result) |
|
||||||
|
|
||||||
## 试一试
|
## 试一试
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
"version": "s05",
|
"version": "s05",
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"title": "s05: Skills",
|
"title": "s05: Skills",
|
||||||
"content": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Load knowledge when you need it, not upfront\"* -- inject via tool_result, not the system prompt.\n\n## Problem\n\nYou want the agent to follow domain-specific workflows: git conventions, testing patterns, code review checklists. Putting everything in the system prompt wastes tokens on unused skills. 10 skills at 2000 tokens each = 20,000 tokens, most of which are irrelevant to any given task.\n\n## Solution\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\nLayer 1: skill *names* in system prompt (cheap). Layer 2: full *body* via tool_result (on demand).\n\n## How It Works\n\n1. Skill files live in `.skills/` as Markdown with YAML frontmatter.\n\n```\n.skills/\n git.md # ---\\n description: Git workflow\\n ---\\n ...\n test.md # ---\\n description: Testing patterns\\n ---\\n ...\n```\n\n2. SkillLoader parses frontmatter, separates metadata from body.\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.glob(\"*.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n self.skills[f.stem] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. Layer 1 goes into the system prompt. Layer 2 is just another tool handler.\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\nThe model learns what skills exist (cheap) and loads them when relevant (expensive).\n\n## What Changed From s04\n\n| Component | Before (s04) | After (s05) |\n|----------------|------------------|----------------------------|\n| Tools | 5 (base + task) | 5 (base + load_skill) |\n| System prompt | Static string | + skill descriptions |\n| Knowledge | None | .skills/*.md files |\n| Injection | None | Two-layer (system + result)|\n\n## Try It\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
"content": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Load knowledge when you need it, not upfront\"* -- inject via tool_result, not the system prompt.\n\n## Problem\n\nYou want the agent to follow domain-specific workflows: git conventions, testing patterns, code review checklists. Putting everything in the system prompt wastes tokens on unused skills. 10 skills at 2000 tokens each = 20,000 tokens, most of which are irrelevant to any given task.\n\n## Solution\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\nLayer 1: skill *names* in system prompt (cheap). Layer 2: full *body* via tool_result (on demand).\n\n## How It Works\n\n1. Each skill is a directory containing a `SKILL.md` with YAML frontmatter.\n\n```\nskills/\n pdf/\n SKILL.md # ---\\n name: pdf\\n description: Process PDF files\\n ---\\n ...\n code-review/\n SKILL.md # ---\\n name: code-review\\n description: Review code\\n ---\\n ...\n```\n\n2. SkillLoader scans for `SKILL.md` files, uses the directory name as the skill identifier.\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.rglob(\"SKILL.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n name = meta.get(\"name\", f.parent.name)\n self.skills[name] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. Layer 1 goes into the system prompt. Layer 2 is just another tool handler.\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\nThe model learns what skills exist (cheap) and loads them when relevant (expensive).\n\n## What Changed From s04\n\n| Component | Before (s04) | After (s05) |\n|----------------|------------------|----------------------------|\n| Tools | 5 (base + task) | 5 (base + load_skill) |\n| System prompt | Static string | + skill descriptions |\n| Knowledge | None | skills/\\*/SKILL.md files |\n| Injection | None | Two-layer (system + result)|\n\n## Try It\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "s06",
|
"version": "s06",
|
||||||
@ -99,7 +99,7 @@
|
|||||||
"version": "s05",
|
"version": "s05",
|
||||||
"locale": "zh",
|
"locale": "zh",
|
||||||
"title": "s05: Skills (技能加载)",
|
"title": "s05: Skills (技能加载)",
|
||||||
"content": "# s05: Skills (技能加载)\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"用到什么知识, 临时加载什么知识\"* -- 通过 tool_result 注入, 不塞 system prompt。\n\n## 问题\n\n你希望智能体遵循特定领域的工作流: git 约定、测试模式、代码审查清单。全塞进系统提示太浪费 -- 10 个技能, 每个 2000 token, 就是 20,000 token, 大部分跟当前任务毫无关系。\n\n## 解决方案\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\n第一层: 系统提示中放技能名称 (低成本)。第二层: tool_result 中按需放完整内容。\n\n## 工作原理\n\n1. 技能文件以 Markdown 格式存放在 `.skills/`, 带 YAML frontmatter。\n\n```\n.skills/\n git.md # ---\\n description: Git workflow\\n ---\\n ...\n test.md # ---\\n description: Testing patterns\\n ---\\n ...\n```\n\n2. SkillLoader 解析 frontmatter, 分离元数据和正文。\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.glob(\"*.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n self.skills[f.stem] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. 第一层写入系统提示。第二层不过是 dispatch map 中的又一个工具。\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\n模型知道有哪些技能 (便宜), 需要时再加载完整内容 (贵)。\n\n## 相对 s04 的变更\n\n| 组件 | 之前 (s04) | 之后 (s05) |\n|----------------|------------------|--------------------------------|\n| Tools | 5 (基础 + task) | 5 (基础 + load_skill) |\n| 系统提示 | 静态字符串 | + 技能描述列表 |\n| 知识库 | 无 | .skills/*.md 文件 |\n| 注入方式 | 无 | 两层 (系统提示 + result) |\n\n## 试一试\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n试试这些 prompt (英文 prompt 对 LLM 效果更好, 也可以用中文):\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
"content": "# s05: Skills (技能加载)\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"用到什么知识, 临时加载什么知识\"* -- 通过 tool_result 注入, 不塞 system prompt。\n\n## 问题\n\n你希望智能体遵循特定领域的工作流: git 约定、测试模式、代码审查清单。全塞进系统提示太浪费 -- 10 个技能, 每个 2000 token, 就是 20,000 token, 大部分跟当前任务毫无关系。\n\n## 解决方案\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\n第一层: 系统提示中放技能名称 (低成本)。第二层: tool_result 中按需放完整内容。\n\n## 工作原理\n\n1. 每个技能是一个目录, 包含 `SKILL.md` 文件和 YAML frontmatter。\n\n```\nskills/\n pdf/\n SKILL.md # ---\\n name: pdf\\n description: Process PDF files\\n ---\\n ...\n code-review/\n SKILL.md # ---\\n name: code-review\\n description: Review code\\n ---\\n ...\n```\n\n2. SkillLoader 递归扫描 `SKILL.md` 文件, 用目录名作为技能标识。\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.rglob(\"SKILL.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n name = meta.get(\"name\", f.parent.name)\n self.skills[name] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. 第一层写入系统提示。第二层不过是 dispatch map 中的又一个工具。\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\n模型知道有哪些技能 (便宜), 需要时再加载完整内容 (贵)。\n\n## 相对 s04 的变更\n\n| 组件 | 之前 (s04) | 之后 (s05) |\n|----------------|------------------|--------------------------------|\n| Tools | 5 (基础 + task) | 5 (基础 + load_skill) |\n| 系统提示 | 静态字符串 | + 技能描述列表 |\n| 知识库 | 无 | skills/\\*/SKILL.md 文件 |\n| 注入方式 | 无 | 两层 (系统提示 + result) |\n\n## 试一试\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n试试这些 prompt (英文 prompt 对 LLM 效果更好, 也可以用中文):\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "s06",
|
"version": "s06",
|
||||||
@ -171,7 +171,7 @@
|
|||||||
"version": "s05",
|
"version": "s05",
|
||||||
"locale": "ja",
|
"locale": "ja",
|
||||||
"title": "s05: Skills",
|
"title": "s05: Skills",
|
||||||
"content": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"必要な知識を、必要な時に読み込む\"* -- system prompt ではなく tool_result で注入。\n\n## 問題\n\nエージェントにドメイン固有のワークフローを遵守させたい: gitの規約、テストパターン、コードレビューチェックリスト。すべてをシステムプロンプトに入れると、使われないスキルにトークンを浪費する。10スキル x 2000トークン = 20,000トークン、ほとんどが任意のタスクに無関係だ。\n\n## 解決策\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\n第1層: スキル*名*をシステムプロンプトに(低コスト)。第2層: スキル*本体*をtool_resultに(オンデマンド)。\n\n## 仕組み\n\n1. スキルファイルは`.skills/`にYAMLフロントマター付きMarkdownとして配置される。\n\n```\n.skills/\n git.md # ---\\n description: Git workflow\\n ---\\n ...\n test.md # ---\\n description: Testing patterns\\n ---\\n ...\n```\n\n2. SkillLoaderがフロントマターを解析し、メタデータと本体を分離する。\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.glob(\"*.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n self.skills[f.stem] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. 第1層はシステムプロンプトに配置。第2層は通常のツールハンドラ。\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\nモデルはどのスキルが存在するかを知り(低コスト)、関連する時にだけ読み込む(高コスト)。\n\n## s04からの変更点\n\n| Component | Before (s04) | After (s05) |\n|----------------|------------------|----------------------------|\n| Tools | 5 (base + task) | 5 (base + load_skill) |\n| System prompt | Static string | + skill descriptions |\n| Knowledge | None | .skills/*.md files |\n| Injection | None | Two-layer (system + result)|\n\n## 試してみる\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
"content": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"必要な知識を、必要な時に読み込む\"* -- system prompt ではなく tool_result で注入。\n\n## 問題\n\nエージェントにドメイン固有のワークフローを遵守させたい: gitの規約、テストパターン、コードレビューチェックリスト。すべてをシステムプロンプトに入れると、使われないスキルにトークンを浪費する。10スキル x 2000トークン = 20,000トークン、ほとんどが任意のタスクに無関係だ。\n\n## 解決策\n\n```\nSystem prompt (Layer 1 -- always present):\n+--------------------------------------+\n| You are a coding agent. |\n| Skills available: |\n| - git: Git workflow helpers | ~100 tokens/skill\n| - test: Testing best practices |\n+--------------------------------------+\n\nWhen model calls load_skill(\"git\"):\n+--------------------------------------+\n| tool_result (Layer 2 -- on demand): |\n| <skill name=\"git\"> |\n| Full git workflow instructions... | ~2000 tokens\n| Step 1: ... |\n| </skill> |\n+--------------------------------------+\n```\n\n第1層: スキル*名*をシステムプロンプトに(低コスト)。第2層: スキル*本体*をtool_resultに(オンデマンド)。\n\n## 仕組み\n\n1. 各スキルは `SKILL.md` ファイルを含むディレクトリとして配置される。\n\n```\nskills/\n pdf/\n SKILL.md # ---\\n name: pdf\\n description: Process PDF files\\n ---\\n ...\n code-review/\n SKILL.md # ---\\n name: code-review\\n description: Review code\\n ---\\n ...\n```\n\n2. SkillLoaderが `SKILL.md` を再帰的に探索し、ディレクトリ名をスキル識別子として使用する。\n\n```python\nclass SkillLoader:\n def __init__(self, skills_dir: Path):\n self.skills = {}\n for f in sorted(skills_dir.rglob(\"SKILL.md\")):\n text = f.read_text()\n meta, body = self._parse_frontmatter(text)\n name = meta.get(\"name\", f.parent.name)\n self.skills[name] = {\"meta\": meta, \"body\": body}\n\n def get_descriptions(self) -> str:\n lines = []\n for name, skill in self.skills.items():\n desc = skill[\"meta\"].get(\"description\", \"\")\n lines.append(f\" - {name}: {desc}\")\n return \"\\n\".join(lines)\n\n def get_content(self, name: str) -> str:\n skill = self.skills.get(name)\n if not skill:\n return f\"Error: Unknown skill '{name}'.\"\n return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n```\n\n3. 第1層はシステムプロンプトに配置。第2層は通常のツールハンドラ。\n\n```python\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\nTOOL_HANDLERS = {\n # ...base tools...\n \"load_skill\": lambda **kw: SKILL_LOADER.get_content(kw[\"name\"]),\n}\n```\n\nモデルはどのスキルが存在するかを知り(低コスト)、関連する時にだけ読み込む(高コスト)。\n\n## s04からの変更点\n\n| Component | Before (s04) | After (s05) |\n|----------------|------------------|----------------------------|\n| Tools | 5 (base + task) | 5 (base + load_skill) |\n| System prompt | Static string | + skill descriptions |\n| Knowledge | None | skills/\\*/SKILL.md files |\n| Injection | None | Two-layer (system + result)|\n\n## 試してみる\n\n```sh\ncd learn-claude-code\npython agents/s05_skill_loading.py\n```\n\n1. `What skills are available?`\n2. `Load the agent-builder skill and follow its instructions`\n3. `I need to do a code review -- load the relevant skill first`\n4. `Build an MCP server using the mcp-builder skill`\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "s06",
|
"version": "s06",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user