选择一个版本开始学习
每个版本都建立在前一个版本的基础上,逐步添加新的功能和概念。点击下方的版本按钮,查看详细的代码实现和学习笔记。
Bash is All You Need
"ONE tool (bash) + ONE loop = FULL agent capability"
核心特性
代码实现
#!/usr/bin/env python
"""
v0_bash_agent.py - Mini Claude Code: Bash is All You Need (~50 lines core)
Core Philosophy: "Bash is All You Need"
======================================
This is the ULTIMATE simplification of a coding agent.
The answer: ONE tool (bash) + ONE loop = FULL agent capability.
"""
from anthropic import Anthropic
from dotenv import load_dotenv
import subprocess
import sys
import os
load_dotenv(override=True)
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.getenv("MODEL_ID", "claude-sonnet-4-5-20250929")
# The ONE tool that does everything
TOOL = [{
"name": "bash",
"description": """Execute shell command. Common patterns:
- Read: cat/head/tail, grep/find/rg/ls, wc -l
- Write: echo 'content' > file, sed -i 's/old/new/g' file
- Subagent: python v0_bash_agent.py 'task description'""",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"]
}
}]
SYSTEM = f"""You are a CLI agent at {os.getcwd()}. Solve problems using bash commands.
Rules:
- Prefer tools over prose. Act first, explain briefly after.
- Read files: cat, grep, find, rg, ls, head, tail
- Write files: echo '...' > file, sed -i, or cat << 'EOF' > file
- Subagent: For complex subtasks, spawn a subagent to keep context clean.
The subagent runs in isolation and returns only its final summary."""
def chat(prompt, history=None):
"""The complete agent loop in ONE function."""
if history is None:
history = []
history.append({"role": "user", "content": prompt})
while True:
# 1. Call the model with tools
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=history,
tools=TOOL,
max_tokens=8000
)
# 2. Build assistant message content
content = []
for block in response.content:
if hasattr(block, "text"):
content.append({"type": "text", "text": block.text})
elif block.type == "tool_use":
content.append({
"type": "tool_use",
"id": block.id,
"name": block.name,
"input": block.input
})
history.append({"role": "assistant", "content": content})
# 3. If model didn't call tools, we're done
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if hasattr(b, "text"))
# 4. Execute each tool call and collect results
results = []
for block in response.content:
if block.type == "tool_use":
cmd = block.input["command"]
print(f"\033[33m$ {cmd}\033[0m")
try:
out = subprocess.run(
cmd, shell=True, capture_output=True,
text=True, timeout=300, cwd=os.getcwd()
)
output = out.stdout + out.stderr
except subprocess.TimeoutExpired:
output = "(timeout after 300s)"
print(output or "(empty)")
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output[:50000]
})
# 5. Append results and continue the loop
history.append({"role": "user", "content": results})
if __name__ == "__main__":
if len(sys.argv) > 1:
# Subagent mode
print(chat(sys.argv[1]))
else:
# Interactive REPL mode
history = []
while True:
try:
query = input("\033[36m>> \033[0m")
except (EOFError, KeyboardInterrupt):
break
if query in ("q", "exit", ""):
break
print(chat(query, history))
💬 学习过程中的疑问
bash是"元工具"(Meta Tool)。v0只定义了一个bash工具,但bash本身就是可以调用所有其他命令的工具。
工作原理:
- 工具定义中描述了bash可以执行shell命令
- 通过shell=True调用系统shell
- Shell可以访问所有已安装的命令(cat, grep, find, python等)
类比理解:
- ❌ 传统方式:定义read_file、search_file、execute_python等多个工具
- ✅ v0方式:只定义一个bash工具,让Claude学会用cat、grep、python等命令
这就是标题"Bash is All You Need"的含义——bash本身就是通往所有功能的大门。
Model as Agent
"The model IS the agent"
核心特性
代码实现
#!/usr/bin/env python3
"""
v1_basic_agent.py - Mini Claude Code: Model as Agent (~200 lines)
Core Philosophy: "The Model IS the Agent"
=========================================
The secret of Claude Code, Cursor Agent, Codex CLI? There is no secret.
Strip away the CLI polish, progress bars, permission systems. What remains
is surprisingly simple: a LOOP that lets the model call tools until done.
Traditional Assistant:
User -> Model -> Text Response
Agent System:
User -> Model -> [Tool -> Result]* -> Response
^________|
The asterisk (*) matters! The model calls tools REPEATEDLY until it decides
the task is complete. This transforms a chatbot into an autonomous agent.
"""
import os
import subprocess
import sys
from pathlib import Path
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv(override=True)
WORKDIR = Path.cwd()
MODEL = os.getenv("MODEL_ID", "claude-sonnet-4-5-20250929")
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Loop: think briefly -> use tools -> report results.
Rules:
- Prefer tools over prose. Act, don't just explain.
- Never invent file paths. Use bash ls/find first if unsure.
- Make minimal changes. Don't over-engineer.
- After finishing, summarize what changed."""
# The Four Essential Tools
TOOLS = [
{
"name": "bash",
"description": "Run a shell command. Use for: ls, find, grep, git, npm, python, etc.",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
},
{
"name": "read_file",
"description": "Read file contents. Returns UTF-8 text.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"limit": {"type": "integer"}
},
"required": ["path"],
},
},
{
"name": "write_file",
"description": "Write content to a file. Creates parent directories if needed.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"],
},
},
{
"name": "edit_file",
"description": "Replace exact text in a file. Use for surgical edits.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"old_text": {"type": "string"},
"new_text": {"type": "string"},
},
"required": ["path", "old_text", "new_text"],
},
},
]
def safe_path(p: str) -> Path:
"""Ensure path stays within workspace (security measure)."""
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path
def run_bash(command: str) -> str:
"""Execute shell command with safety checks."""
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:
result = subprocess.run(
command, shell=True, cwd=WORKDIR,
capture_output=True, text=True, timeout=60
)
output = (result.stdout + result.stderr).strip()
return output[:50000] if output else "(no output)"
except subprocess.TimeoutExpired:
return "Error: Command timed out (60s)"
except Exception as e:
return f"Error: {e}"
def run_read(path: str, limit: int = None) -> str:
"""Read file contents with optional line limit."""
try:
text = safe_path(path).read_text()
lines = text.splitlines()
if limit and limit < len(lines):
lines = lines[:limit]
lines.append(f"... ({len(text.splitlines()) - limit} more lines)")
return "\n".join(lines)[:50000]
except Exception as e:
return f"Error: {e}"
def run_write(path: str, content: str) -> str:
"""Write content to file, creating parent directories if needed."""
try:
fp = safe_path(path)
fp.parent.mkdir(parents=True, exist_ok=True)
fp.write_text(content)
return f"Wrote {len(content)} bytes to {path}"
except Exception as e:
return f"Error: {e}"
def run_edit(path: str, old_text: str, new_text: str) -> str:
"""Replace exact text in a file (surgical edit)."""
try:
fp = safe_path(path)
content = fp.read_text()
if old_text not in content:
return f"Error: Text not found in {path}"
new_content = content.replace(old_text, new_text, 1)
fp.write_text(new_content)
return f"Edited {path}"
except Exception as e:
return f"Error: {e}"
def execute_tool(name: str, args: dict) -> str:
"""Dispatch tool call to the appropriate implementation."""
if name == "bash":
return run_bash(args["command"])
if name == "read_file":
return run_read(args["path"], args.get("limit"))
if name == "write_file":
return run_write(args["path"], args["content"])
if name == "edit_file":
return run_edit(args["path"], args["old_text"], args["new_text"])
return f"Unknown tool: {name}"
def agent_loop(messages: list) -> list:
"""
The complete agent in one function.
This is the pattern that ALL coding agents share:
while True:
response = model(messages, tools)
if no tool calls: return
execute tools, append results, continue
"""
while True:
# Step 1: Call the model
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
# Step 2: Collect any tool calls and print text output
tool_calls = []
for block in response.content:
if hasattr(block, "text"):
print(block.text)
if block.type == "tool_use":
tool_calls.append(block)
# Step 3: If no tool calls, task is complete
if response.stop_reason != "tool_use":
messages.append({"role": "assistant", "content": response.content})
return messages
# Step 4: Execute each tool and collect results
results = []
for tc in tool_calls:
print(f"\n> {tc.name}: {tc.input}")
output = execute_tool(tc.name, tc.input)
preview = output[:200] + "..." if len(output) > 200 else output
print(f" {preview}")
results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": output,
})
# Step 5: Append to conversation and continue
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results})
def main():
print(f"Mini Claude Code v1 - {WORKDIR}")
print("Type 'exit' to quit.\n")
history = []
while True:
try:
user_input = input("You: ").strip()
except (EOFError, KeyboardInterrupt):
break
if not user_input or user_input.lower() in ("exit", "quit", "q"):
break
history.append({"role": "user", "content": user_input})
try:
agent_loop(history)
except Exception as e:
print(f"Error: {e}")
print()
if __name__ == "__main__":
main()
💬 学习过程中的疑问
这涉及到理论上的可能性和工程上的实用性之间的权衡。
v0的哲学:极简主义证明
- "证明概念"——bash理论上可以做一切
- 但实践中容易出错
v1的哲学:生产可用性
- 更好的错误处理
- 安全性控制(safe_path、危险命令拦截)
- 更清晰的意图表达
- 更好的输出控制
- 编辑文件的精确性
| 维度 | v0 (bash only) | v1 (4 tools) |
|---|---|---|
| 理论能力 | 100% | 100% |
| 实际可用性 | 60% | 95% |
| 安全性 | 低 | 高 |
| 错误率 | 高 | 低 |
v0的价值:证明"一个工具就够了"的理论可行性
v1的价值:提供"生产环境可用"的实际系统
1. 安全机制 🔒
- 路径安全检查:safe_path()确保不越界
- bash命令黑名单:拦截rm -rf /等危险命令
- 超时保护:60秒限制
2. 错误处理 🛡️
- 每个工具都有专门的错误处理
- 友好的错误信息
- 特定的错误检查(如edit_file检查文本是否存在)
3. 输出格式化 📊
- 工具执行时的可视化显示
- 智能截断(显示"... (N more lines)")
- 结果预览
4. System Prompt改进 📝
- "Never invent file paths"——防止幻觉
- "Make minimal changes"——指导原则
- "After finishing, summarize"——要求总结
5. 代码组织 🏗️
- 清晰的模块化
- 每个工具独立函数
- 易于扩展和维护
Structured Planning
"Make Plans Visible"
核心特性
代码实现
# v2: TodoManager - The core addition
class TodoManager:
"""
Manages a structured task list with enforced constraints.
Key Design Decisions:
1. Max 20 items: Prevents endless lists
2. One in_progress: Forces focus
3. Required fields: content, status, activeForm
"""
def __init__(self):
self.items = []
def update(self, items: list) -> str:
"""Validate and update the todo list."""
validated = []
in_progress_count = 0
for i, item in enumerate(items):
content = str(item.get("content", "")).strip()
status = str(item.get("status", "pending")).lower()
active_form = str(item.get("activeForm", "")).strip()
# Validation checks
if not content:
raise ValueError(f"Item {i}: content required")
if status not in ("pending", "in_progress", "completed"):
raise ValueError(f"Item {i}: invalid status")
if not active_form:
raise ValueError(f"Item {i}: activeForm required")
if status == "in_progress":
in_progress_count += 1
validated.append({
"content": content,
"status": status,
"activeForm": active_form
})
# Enforce constraints
if len(validated) > 20:
raise ValueError("Max 20 todos allowed")
if in_progress_count > 1:
raise ValueError("Only one task can be in_progress")
self.items = validated
return self.render()
def render(self) -> str:
"""Render the todo list as human-readable text."""
if not self.items:
return "No todos."
lines = []
for item in self.items:
if item["status"] == "completed":
lines.append(f"[x] {item['content']}")
elif item["status"] == "in_progress":
lines.append(f"[>] {item['content']} <- {item['activeForm']}")
else:
lines.append(f"[ ] {item['content']}")
completed = sum(1 for t in self.items if t["status"] == "completed")
lines.append(f"\n({completed}/{len(self.items)} completed)")
return "\n".join(lines)
# NEW in v2: TodoWrite Tool
{
"name": "TodoWrite",
"description": "Update the task list. Use to plan and track progress.",
"input_schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string"},
"status": {
"type": "string",
"enum": ["pending", "in_progress", "completed"]
},
"activeForm": {"type": "string"},
},
"required": ["content", "status", "activeForm"],
},
}
},
"required": ["items"],
},
}
# Agent Loop with todo tracking
rounds_without_todo = 0
def agent_loop(messages: list) -> list:
global rounds_without_todo
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
tool_calls = []
for block in response.content:
if hasattr(block, "text"):
print(block.text)
if block.type == "tool_use":
tool_calls.append(block)
if response.stop_reason != "tool_use":
messages.append({"role": "assistant", "content": response.content})
return messages
results = []
used_todo = False
for tc in tool_calls:
output = execute_tool(tc.name, tc.input)
results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": output,
})
if tc.name == "TodoWrite":
used_todo = True
# Update counter
if used_todo:
rounds_without_todo = 0
else:
rounds_without_todo += 1
messages.append({"role": "assistant", "content": response.content})
# Inject reminder if model hasn't used todos
if rounds_without_todo > 10:
results.insert(0, {"type": "text", "text": NAG_REMINDER})
messages.append({"role": "user", "content": results})
💬 学习过程中的疑问
问题:Context Fade(上下文消退)
在v1中,计划只存在于模型的"头脑"中:
"我会先做A,再做B,最后做C"(看不见)
10个工具调用后:"等等,我在做什么?"
解决方案:TodoWrite工具
v2添加了一个新工具,让计划可见:
[ ] 重构认证模块 [>] 添加单元测试 <- 正在做 [ ] 更新文档
关键约束(guardrails):
| 规则 | 原因 |
|---|---|
| 最多20项 | 防止无限任务列表 |
| 仅一项in_progress | 强制聚焦 |
| 必填字段 | 确保结构化输出 |
深层洞察:
"Structure constrains AND enables."
约束(最多20项、仅一项进行中)ENABLE(可见计划、可追踪进度)。
好的约束不是限制,而是脚手架。
Divide and Conquer
"Divide and Conquer with Context Isolation"
核心特性
代码实现
# v3: Agent Type Registry - The core of subagent mechanism
AGENT_TYPES = {
"explore": {
"description": "Read-only agent for exploring code, finding files, searching",
"tools": ["bash", "read_file"], # No write access
"prompt": "You are an exploration agent. Search and analyze, but never modify files. Return a concise summary.",
},
"code": {
"description": "Full agent for implementing features and fixing bugs",
"tools": "*", # All tools
"prompt": "You are a coding agent. Implement the requested changes efficiently.",
},
"plan": {
"description": "Planning agent for designing implementation strategies",
"tools": ["bash", "read_file"], # Read-only
"prompt": "You are a planning agent. Analyze the codebase and output a numbered implementation plan. Do NOT make changes.",
},
}
# NEW in v3: Task Tool
TASK_TOOL = {
"name": "Task",
"description": """Spawn a subagent for a focused subtask.
Subagents run in ISOLATED context - they don't see parent's history.
Use this to keep the main conversation clean.
Agent types:
- explore: Read-only exploration
- code: Full implementation
- plan: Design strategies
""",
"input_schema": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Short task name (3-5 words)"
},
"prompt": {
"type": "string",
"description": "Detailed instructions for the subagent"
},
"agent_type": {
"type": "string",
"enum": list(AGENT_TYPES.keys()),
},
},
"required": ["description", "prompt", "agent_type"],
},
}
def run_task(description: str, prompt: str, agent_type: str) -> str:
"""
Execute a subagent task with isolated context.
This is the core of the subagent mechanism:
1. Create isolated message history (KEY: no parent context!)
2. Use agent-specific system prompt
3. Filter available tools based on agent type
4. Run the same agent loop
5. Return ONLY the final text
"""
if agent_type not in AGENT_TYPES:
return f"Error: Unknown agent type '{agent_type}'"
config = AGENT_TYPES[agent_type]
# Agent-specific system prompt
sub_system = f"""You are a {agent_type} subagent at {WORKDIR}.
{config["prompt"]}
Complete the task and return a clear, concise summary."""
# Filtered tools for this agent type
sub_tools = get_tools_for_agent(agent_type)
# ISOLATED message history - this is the key!
sub_messages = [{"role": "user", "content": prompt}]
# Progress tracking
print(f" [{agent_type}] {description}")
start = time.time()
tool_count = 0
# Run the agent loop (silently)
while True:
response = client.messages.create(
model=MODEL,
system=sub_system,
messages=sub_messages,
tools=sub_tools,
max_tokens=8000,
)
if response.stop_reason != "tool_use":
break
tool_calls = [b for b in response.content if b.type == "tool_use"]
results = []
for tc in tool_calls:
tool_count += 1
output = execute_tool(tc.name, tc.input)
results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": output
})
# Update progress line
elapsed = time.time() - start
sys.stdout.write(
f"\r [{agent_type}] {description} ... {tool_count} tools, {elapsed:.1f}s"
)
sys.stdout.flush()
sub_messages.append({"role": "assistant", "content": response.content})
sub_messages.append({"role": "user", "content": results})
# Final progress update
elapsed = time.time() - start
sys.stdout.write(
f"\r [{agent_type}] {description} - done ({tool_count} tools, {elapsed:.1f}s)\n"
)
# Extract and return only the final text
for block in response.content:
if hasattr(block, "text"):
return block.text
return "(subagent returned no text)"
def get_tools_for_agent(agent_type: str) -> list:
"""Filter tools based on agent type."""
allowed = AGENT_TYPES.get(agent_type, {}).get("tools", "*")
if allowed == "*":
return BASE_TOOLS # All base tools, but NOT Task
return [t for t in BASE_TOOLS if t["name"] in allowed]
💬 学习过程中的疑问
问题:Context Pollution(上下文污染)
单Agent历史记录:
[探索中...] cat file1.py -> 500行 [探索中...] cat file2.py -> 300行 ... 15个文件后 ... [现在重构...] "等等,file1里有什么来着?"
模型的上下文被探索细节填满,留给实际任务的空间很少。
解决方案:子agent + 上下文隔离
主Agent历史记录:
[任务:探索代码库]
-> 子agent探索20个文件(在自己的上下文中)
-> 只返回:"认证在src/auth/,数据库在src/models/"
[现在用干净上下文重构]
每个子agent拥有:
- 自己全新的message history
- 过滤后的工具(explore不能write)
- 专门的system prompt
- 只返回最终摘要给父agent
核心洞察:
Process isolation = Context isolation
通过生成子任务,我们获得:
- 主agent的干净上下文
- 并行探索可能
- 自然任务分解
Knowledge Externalization
"Knowledge Externalization"
核心特性
代码实现
# v4: SkillLoader - The core addition
class SkillLoader:
"""
Loads and manages skills from SKILL.md files.
A skill is a FOLDER containing:
- SKILL.md (required): YAML frontmatter + markdown instructions
- scripts/ (optional): Helper scripts
- references/ (optional): Additional documentation
- assets/ (optional): Templates, files for output
"""
def __init__(self, skills_dir: Path):
self.skills_dir = skills_dir
self.skills = {}
self.load_skills()
def parse_skill_md(self, path: Path) -> dict:
"""Parse a SKILL.md file into metadata and body."""
content = path.read_text()
# Match YAML frontmatter between --- markers
match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)$", content, re.DOTALL)
if not match:
return None
frontmatter, body = match.groups()
# Parse YAML-like frontmatter
metadata = {}
for line in frontmatter.strip().split("\n"):
if ":" in line:
key, value = line.split(":", 1)
metadata[key.strip()] = value.strip().strip("\"'")
if "name" not in metadata or "description" not in metadata:
return None
return {
"name": metadata["name"],
"description": metadata["description"],
"body": body.strip(),
"path": path,
"dir": path.parent,
}
def load_skills(self):
"""Scan skills directory and load all valid SKILL.md files."""
if not self.skills_dir.exists():
return
for skill_dir in self.skills_dir.iterdir():
if not skill_dir.is_dir():
continue
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
continue
skill = self.parse_skill_md(skill_md)
if skill:
self.skills[skill["name"]] = skill
def get_descriptions(self) -> str:
"""
Generate skill descriptions for system prompt.
This is Layer 1 - only name and description, ~100 tokens per skill.
"""
if not self.skills:
return "(no skills available)"
return "\n".join(
f"- {name}: {skill['description']}"
for name, skill in self.skills.items()
)
def get_skill_content(self, name: str) -> str:
"""
Get full skill content for injection.
This is Layer 2 - the complete SKILL.md body.
"""
if name not in self.skills:
return None
skill = self.skills[name]
content = f"# Skill: {skill['name']}\n\n{skill['body']}"
# List available resources (Layer 3 hints)
resources = []
for folder, label in [
("scripts", "Scripts"),
("references", "References"),
("assets", "Assets")
]:
folder_path = skill["dir"] / folder
if folder_path.exists():
files = list(folder_path.glob("*"))
if files:
resources.append(f"{label}: {', '.join(f.name for f in files)}")
if resources:
content += f"\n\n**Available resources:**\n"
content += "\n".join(f"- {r}" for r in resources)
return content
# NEW in v4: Skill Tool
SKILL_TOOL = {
"name": "Skill",
"description": """Load a skill to gain specialized knowledge for a task.
When to use:
- IMMEDIATELY when user task matches a skill description
- Before attempting domain-specific work (PDF, MCP, etc.)
The skill content will be injected into the conversation.""",
"input_schema": {
"type": "object",
"properties": {
"skill": {
"type": "string",
"description": "Name of the skill to load"
}
},
"required": ["skill"],
},
}
def run_skill(skill_name: str) -> str:
"""
Load a skill and inject it into the conversation.
Why tool_result instead of system prompt?
- System prompt changes invalidate cache (20-50x cost increase)
- Tool results append to end (prefix unchanged, cache hit)
This is how production systems stay cost-efficient.
"""
content = SKILLS.get_skill_content(skill_name)
if content is None:
available = ", ".join(SKILLS.list_skills()) or "none"
return f"Error: Unknown skill '{skill_name}'. Available: {available}"
return f"""<skill-loaded name="{skill_name}">
{content}
</skill-loaded>
Follow the instructions in the skill above to complete the user's task."""
# Progressive Disclosure:
# Layer 1: Metadata (always loaded) ~100 tokens/skill
# Layer 2: SKILL.md body (on trigger) ~2000 tokens
# Layer 3: Resources (as needed) Unlimited
💬 学习过程中的疑问
核心区别:知识的"消化程度"
微调(Fine-tuning) → 知识"长"在神经网络里 RAG → 知识"存"在外部库,用时检索 Skills → 知识"写"在提示词里,直接给AI看
详细对比:
| 维度 | 微调 | RAG | Skills |
|---|---|---|---|
| 知识存储 | 模型参数 | 向量数据库 | Markdown文件 |
| 知识类型 | 深层模式 | 事实性知识 | 流程化操作知识 |
| 访问方式 | 自动激活 | 语义检索 | 精确加载 |
| 成本 | $10K-$1M+ | 向量化+检索 | 几乎为0 |
| 更新速度 | 周/月 | 分钟 | 秒 |
| 准确性 | 最高 | 中等 | 高 |
适用场景:
- 微调:改变模型的"底层行为"(如医疗诊断流程)
- RAG:海量、动态更新的知识库(如企业内部文档)
- Skills:流程化、需要精确执行的专业技能(如MCP开发、PDF处理)
关键洞察:Cache-Preserving Injection(缓存保持注入)
# ❌ 错误方式:修改system prompt
SYSTEM = f"""You are a coding agent...
{skill_content} # ← 每次不同,缓存失效!
"""
# 结果:缓存失效,成本增加20-50倍
# ✅ 正确方式:通过tool_result追加
messages.append({
"role": "user",
"content": [{"type": "tool_result", "content": skill_content}]
})
# 结果:system prompt不变,缓存命中!
为什么这样有效?
Claude的提示词缓存基于前缀匹配:
- System prompt是前缀
- 如果前缀不变,缓存有效
- Skill内容追加到messages末尾,不影响前缀
渐进式披露(Progressive Disclosure):
Layer 1: 元数据(始终加载) ~100 tokens/skill
↓ 放入system prompt
Layer 2: SKILL.md正文(触发时) ~2000 tokens
↓ 通过tool_result追加
Layer 3: 资源文件(按需使用) 无限制
↓ 可以包含代码、模板等
这保持了上下文精简,同时允许任意深度。
核心区别:能力 vs 知识
| 概念 | 它是什么 | 例子 |
|---|---|---|
| Tool | 模型能做什么 | bash, read_file, write_file |
| Skill | 模型知道如何做 | PDF处理、MCP开发、代码审查 |
类比理解:
- Tools = 工具箱里的工具(锤子、螺丝刀、扳手)——告诉你"有什么"
- Skills = 使用说明书("如何正确锤钉子"、"什么时候用螺丝刀而不是锤子")——告诉你"怎么做"
实际例子:
# Tool告诉AI它能执行bash bash_command = "pdftotext input.pdf -" # Skill告诉AI什么时候用pdftotext,什么时候用PyMuPDF """ ## Reading PDFs Use pdftotext for quick extraction: pdftotext input.pdf - Use PyMuPDF for complex layouts: python -c "import fitz; ..." """
Tools是能力,Skills是知识。两者结合,AI才能真正完成复杂任务。
版本对比
从代码行数到核心功能的演进一览
| 版本 | 代码行数 | 工具数量 | 核心新增 | 关键洞察 |
|---|---|---|---|---|
| v0 | ~50 | 1 | 递归子agent | One tool is enough |
| v1 | ~200 | 4 | 核心循环 | Model as Agent |
| v2 | ~300 | 5 | TodoManager | Constraints enable complexity |
| v3 | ~450 | 6 | Task tool | Clean context = better results |
| v4 | ~550 | 7 | Skill tool | Expertise without retraining |
学习路径建议
理解核心循环
从 v0 开始,理解 "while True" 循环是所有 Agent 的基础模式。
掌握工具设计
学习 v1 的 4 个核心工具,理解如何给 AI 提供与真实世界交互的能力。
显式规划
v2 的 TodoManager 教你如何让 AI 的行为更可预测、更可控。
上下文隔离
v3 的子 agent 机制展示了如何通过任务分解保持上下文干净。
知识外化
v4 的 Skills 系统让你理解如何在不重新训练的情况下注入专业知识。
动手实践
运行代码,修改参数,观察 AI 的行为变化。实践是最好的老师。