Skip to content

Commit 9262ccd

Browse files
Bill-Billionclaude
andcommitted
refine: incremental code pattern — each code.py visibly builds on previous
Rewrite all 19 code.py files to show explicit "在 sXX 基础上新增" lineage: - Docstring states which chapter this builds on and what was added - Visual markers (═══ FROM sXX / NEW in sYY ═══) separate inherited from new code - agent_loop comments show exact line changes from previous chapter - Print message at startup tells reader which chapter they're extending This restores the "incremental optimization" soul that made the original agents/s01-s12 version feel like a coherent step-by-step build, rather than 19 disconnected standalone chapters. s01-s04: Full cumulative code with markers s05-s08: Cumulative code with markers (compact for later chapters) s09-s19: Incremental docstrings + agent_loop with change annotations Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent f6746f7 commit 9262ccd

18 files changed

Lines changed: 786 additions & 1032 deletions

File tree

s02_tool_use/code.py

Lines changed: 68 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
#!/usr/bin/env python3
22
"""
3-
s02: Tool Use — 工具分发,循环不变
3+
s02: Tool Use — 在 s01 基础上新增 4 个工具 + 分发映射 + 并发分区
44
5-
运行方式:
6-
python s02_tool_use/code.py
5+
运行: python s02_tool_use/code.py
6+
需要: pip install anthropic python-dotenv + .env 中配置 ANTHROPIC_API_KEY
77
8-
需要:
9-
- pip install anthropic python-dotenv
10-
- .env 文件中配置 ANTHROPIC_API_KEY 或 ANTHROPIC_BASE_URL + MODEL_ID
8+
本文件 = s01 的全部代码 + 以下新增:
9+
+ run_read / run_write / run_edit / run_glob 四个工具实现
10+
+ TOOL_HANDLERS 分发映射(替代 s01 中硬编码的 run_bash 调用)
11+
+ partition_tool_calls 并发分区
12+
+ safe_path 路径安全校验
1113
12-
代码对应 README 章节:
13-
TOOLS / TOOL_HANDLERS → 工具定义与分发
14-
partition_tool_calls() → 并发 vs 顺序执行
15-
agent_loop() → 循环本身(与 s01 完全相同)
14+
循环本身(agent_loop)与 s01 完全一致。
1615
"""
1716

18-
import os
19-
import subprocess
17+
import os, subprocess
2018
from pathlib import Path
2119

2220
try:
@@ -32,7 +30,6 @@
3230
from dotenv import load_dotenv
3331

3432
load_dotenv(override=True)
35-
3633
if os.getenv("ANTHROPIC_BASE_URL"):
3734
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
3835

@@ -43,14 +40,9 @@
4340
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."
4441

4542

46-
def safe_path(p: str) -> Path:
47-
path = (WORKDIR / p).resolve()
48-
if not path.is_relative_to(WORKDIR):
49-
raise ValueError(f"Path escapes workspace: {p}")
50-
return path
51-
52-
53-
# ── 工具实现 ──────────────────────────────────────────────
43+
# ═══════════════════════════════════════════════════════════
44+
# FROM s01 (unchanged)
45+
# ═══════════════════════════════════════════════════════════
5446

5547
def run_bash(command: str) -> str:
5648
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
@@ -63,6 +55,19 @@ def run_bash(command: str) -> str:
6355
return out[:50000] if out else "(no output)"
6456
except subprocess.TimeoutExpired:
6557
return "Error: Timeout (120s)"
58+
except (FileNotFoundError, OSError) as e:
59+
return f"Error: {e}"
60+
61+
62+
# ═══════════════════════════════════════════════════════════
63+
# NEW in s02: 4 个新工具
64+
# ═══════════════════════════════════════════════════════════
65+
66+
def safe_path(p: str) -> Path:
67+
path = (WORKDIR / p).resolve()
68+
if not path.is_relative_to(WORKDIR):
69+
raise ValueError(f"Path escapes workspace: {p}")
70+
return path
6671

6772

6873
def run_read(path: str, limit: int | None = None) -> str:
@@ -106,79 +111,38 @@ def run_glob(pattern: str) -> str:
106111
return f"Error: {e}"
107112

108113

109-
# ── 工具定义 ──────────────────────────────────────────────
114+
# ═══════════════════════════════════════════════════════════
115+
# NEW in s02: 工具定义(s01 只有一个 bash,现在扩展到 5 个)
116+
# ═══════════════════════════════════════════════════════════
110117

111118
TOOLS = [
112-
{
113-
"name": "bash",
114-
"description": "Run a shell command.",
115-
"input_schema": {
116-
"type": "object",
117-
"properties": {"command": {"type": "string"}},
118-
"required": ["command"],
119-
},
120-
},
121-
{
122-
"name": "read_file",
123-
"description": "Read file contents.",
124-
"input_schema": {
125-
"type": "object",
126-
"properties": {
127-
"path": {"type": "string"},
128-
"limit": {"type": "integer"},
129-
},
130-
"required": ["path"],
131-
},
132-
},
133-
{
134-
"name": "write_file",
135-
"description": "Write content to a file.",
136-
"input_schema": {
137-
"type": "object",
138-
"properties": {
139-
"path": {"type": "string"},
140-
"content": {"type": "string"},
141-
},
142-
"required": ["path", "content"],
143-
},
144-
},
145-
{
146-
"name": "edit_file",
147-
"description": "Replace exact text in a file once.",
148-
"input_schema": {
149-
"type": "object",
150-
"properties": {
151-
"path": {"type": "string"},
152-
"old_text": {"type": "string"},
153-
"new_text": {"type": "string"},
154-
},
155-
"required": ["path", "old_text", "new_text"],
156-
},
157-
},
158-
{
159-
"name": "glob",
160-
"description": "Find files matching a glob pattern.",
161-
"input_schema": {
162-
"type": "object",
163-
"properties": {"pattern": {"type": "string"}},
164-
"required": ["pattern"],
165-
},
166-
},
119+
{"name": "bash", "description": "Run a shell command.",
120+
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
121+
{"name": "read_file", "description": "Read file contents.",
122+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
123+
{"name": "write_file", "description": "Write content to a file.",
124+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
125+
{"name": "edit_file", "description": "Replace exact text in a file once.",
126+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
127+
{"name": "glob", "description": "Find files matching a glob pattern.",
128+
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
167129
]
168130

131+
# ═══════════════════════════════════════════════════════════
132+
# NEW in s02: 工具分发映射(s01 是硬编码 run_bash,现在改为查表)
133+
# ═══════════════════════════════════════════════════════════
134+
169135
TOOL_HANDLERS = {
170-
"bash": run_bash,
171-
"read_file": run_read,
172-
"write_file": run_write,
173-
"edit_file": run_edit,
174-
"glob": run_glob,
136+
"bash": run_bash, "read_file": run_read, "write_file": run_write,
137+
"edit_file": run_edit, "glob": run_glob,
175138
}
176139

177140

178-
# ── 工具分区 ──────────────────────────────────────────────
141+
# ═══════════════════════════════════════════════════════════
142+
# NEW in s02: 并发分区(s01 只有 bash 单个工具,不需要分区)
143+
# ═══════════════════════════════════════════════════════════
179144

180145
def partition_tool_calls(blocks):
181-
"""Split tool calls into sequential (may modify filesystem) and concurrent (read-only)."""
182146
concurrent, sequential = [], []
183147
for block in blocks:
184148
if block.type != "tool_use":
@@ -190,58 +154,46 @@ def partition_tool_calls(blocks):
190154
return concurrent, sequential
191155

192156

193-
# ── 核心:Agent Loop(与 s01 完全相同)────────────────────
194-
195-
def execute_tool(block) -> str:
196-
handler = TOOL_HANDLERS.get(block.name)
197-
if not handler:
198-
return f"Unknown tool: {block.name}"
199-
return handler(**block.input)
200-
157+
# ═══════════════════════════════════════════════════════════
158+
# agent_loop — 与 s01 结构完全一致,只改了工具执行那一行
159+
# s01: output = run_bash(block.input["command"])
160+
# s02: output = TOOL_HANDLERS[block.name](**block.input)
161+
# ═══════════════════════════════════════════════════════════
201162

202163
def agent_loop(messages: list):
203164
while True:
204165
response = client.messages.create(
205166
model=MODEL, system=SYSTEM, messages=messages,
206167
tools=TOOLS, max_tokens=8000,
207168
)
208-
209169
messages.append({"role": "assistant", "content": response.content})
210170

211171
if response.stop_reason != "tool_use":
212172
return
213173

214-
# 分区执行:先跑可能改文件系统的,再并发跑只读的
174+
# s02 改动: 分区执行(s01 是直接循环执行)
215175
concurrent, sequential = partition_tool_calls(response.content)
216176
results = []
217177

218178
for block in sequential:
219-
print(f"\033[33m> {block.name}({list(block.input.values())[:1]})\033[0m")
220-
output = execute_tool(block)
221-
print(output[:200])
222-
results.append({
223-
"type": "tool_result",
224-
"tool_use_id": block.id,
225-
"content": output,
226-
})
179+
print(f"\033[33m> {block.name}\033[0m")
180+
handler = TOOL_HANDLERS.get(block.name) # s02: 查表替代硬编码
181+
output = handler(**block.input) if handler else f"Unknown: {block.name}"
182+
print(str(output)[:200])
183+
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
227184

228185
for block in concurrent:
229-
print(f"\033[36m> {block.name}({list(block.input.values())[:1]})\033[0m")
230-
output = execute_tool(block)
231-
print(output[:200])
232-
results.append({
233-
"type": "tool_result",
234-
"tool_use_id": block.id,
235-
"content": output,
236-
})
186+
print(f"\033[36m> {block.name}\033[0m")
187+
handler = TOOL_HANDLERS.get(block.name)
188+
output = handler(**block.input) if handler else f"Unknown: {block.name}"
189+
print(str(output)[:200])
190+
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
237191

238192
messages.append({"role": "user", "content": results})
239193

240194

241-
# ── 入口 ─────────────────────────────────────────────────
242-
243195
if __name__ == "__main__":
244-
print("s02: Tool Use")
196+
print("s02: Tool Use — 在 s01 基础上加了 4 个工具")
245197
print("输入问题,回车发送。输入 q 退出。\n")
246198

247199
history = []
@@ -254,9 +206,7 @@ def agent_loop(messages: list):
254206
break
255207
history.append({"role": "user", "content": query})
256208
agent_loop(history)
257-
response_content = history[-1]["content"]
258-
if isinstance(response_content, list):
259-
for block in response_content:
260-
if getattr(block, "type", None) == "text":
261-
print(block.text)
209+
for block in history[-1]["content"]:
210+
if getattr(block, "type", None) == "text":
211+
print(block.text)
262212
print()

0 commit comments

Comments
 (0)