Follow up PR #265: refine chapters, diagrams, and add S20 (#283)

* feat: s01-s14 docs quality overhaul — tool pipeline, single-agent, knowledge & resilience

Rewrite code.py and README (zh/en/ja) for s01-s14, each chapter building
incrementally on the previous. Key fixes across chapters:

- s01-s04: agent loop, tool dispatch, permission pipeline, hooks
- s05-s08: todo write, subagent, skill loading, context compact
- s09-s11: memory system, system prompt assembly, error recovery
- s12-s14: task graph, background tasks, cron scheduler

All chapters CC source-verified. Code inherits fixes forward (PROMPT_SECTIONS,
json.dumps cache, real-state context, can_start dep protection, etc.).

* feat: s15-s19 docs quality overhaul — multi-agent platform: teams, protocols, autonomy, worktree, MCP tools

Rewrite code.py and README (zh/en/ja) for s15-s19, the multi-agent platform
chapters. Each chapter inherits all previous fixes and adds one mechanism:

- s15: agent teams (TeamCreate, teammate threads, shared task list)
- s16: team protocols (plan approval, shutdown handshake, consume_inbox)
- s17: autonomous agents (idle polling, auto-claim, consume_lead_inbox)
- s18: worktree isolation (git worktree, bind_task, cwd switching, safety)
- s19: MCP tools (MCPClient, normalize_mcp_name, assemble_tool_pool, no cache)

All appendix source code references verified against CC source. Config priority
corrected: claude.ai < plugin < user < project < local.

* fix: 5 regressions across s05-s19 — glob safety, todo validation, memory extraction, protocol types, dep crash

- s05-s09: glob results now filter with is_relative_to(WORKDIR) (inherited from s02)
- s06-s08: todo_write validates content/status required fields (inherited from s05)
- s09: extract_memories uses pre-compression snapshot instead of compacted messages
- s16: submit_plan docstring clarifies protocol-only (not code-level gate)
- s17-s19: match_response restores type mismatch validation (from s16)
- s17-s19: claim_task deps list handles missing dep files without crashing

* fix: s12 Todo V2 logic reversal, s14/s15 cron range validation, s18/s19 worktree name validation

- s12 README (zh/en/ja): fix Todo V2 direction — interactive defaults to Task,
  non-interactive/SDK defaults to TodoWrite. Fix env var name to
  CLAUDE_CODE_ENABLE_TASKS (not TODO_V2).
- s14/s15: add _validate_cron_field with per-field range checks (minute 0-59,
  hour 0-23, dom 1-31, month 1-12, dow 0-6), step > 0, range lo <= hi.
  Replace old try/except validation that only caught exceptions.
- s18/s19: add validate_worktree_name() to remove_worktree and keep_worktree,
  not just create_worktree.

* fix: align s16-s19 teaching tool consistency

* fix pr265 chapter diagrams

* Add comprehensive s20 harness chapter

* Fix chapter smoke test regressions

* Clarify README tutorial track transition

---------

Co-authored-by: Haoran <bill-billion@outlook.com>
This commit is contained in:
gui-yue
2026-05-20 21:45:38 +08:00
committed by GitHub
parent c354cf7721
commit 1baf1aca5a
174 changed files with 35833 additions and 353 deletions

222
s02_tool_use/README.en.md Normal file
View File

@@ -0,0 +1,222 @@
# s02: Tool Use — Add a Tool, Add Just One Line
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
s01 → `s02` → [s03](../s03_permission/) → s04 → ... → s20
> *"Add a tool, add just one handler"* — The loop stays the same. Register the new tool in the dispatch map and you're done.
>
> **Harness Layer**: Tool Dispatch — Expanding the model's reach.
---
## Only One Tool: Bash
The s01 Agent has only one tool: bash. To read a file, `cat`; to write, `echo "..." > file.py`; to edit, `sed`.
The model thinks "read this file" but has to spell out `cat path/to/file`. An extra layer of translation that wastes tokens and invites errors.
---
## Overview: Tool Dispatch
![Tool Dispatch](images/tool-dispatch.en.svg)
The s01 loop is fully preserved (LLM call, stop_reason check, message append — not a single word changed). The only change is in that one line of tool execution: `run_bash()` is replaced with `TOOL_HANDLERS[block.name]()` dispatch lookup.
Adding a tool to the Agent requires just two things:
1. **Define the tool**: Add one entry to the `TOOLS` array
2. **Register the handler**: Add one mapping in the `TOOL_HANDLERS` dict
---
## From 1 Tool to 5 Tools
s01 had only bash:
```python
TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```
s02 expands to 5 tools, each independently defined:
```python
TOOLS = [
{"name": "bash", "description": "Run a shell command.", ...},
{"name": "read_file", "description": "Read file contents.", ...},
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
]
```
Each tool has its own implementation function:
```python
def run_read(path, limit=None):
lines = safe_path(path).read_text().splitlines()
if limit:
lines = lines[:limit]
return "\n".join(lines)
def run_write(path, content):
safe_path(path).write_text(content)
return f"Wrote {len(content)} bytes to {path}"
def run_edit(path, old_text, new_text):
text = safe_path(path).read_text()
if old_text not in text:
return "Error: text not found"
safe_path(path).write_text(text.replace(old_text, new_text, 1))
return f"Edited {path}"
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))
```
---
## Tool Dispatch
```python
TOOL_HANDLERS = {
"bash": run_bash,
"read_file": run_read,
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
}
# Only one line changed in the loop — from hardcoded run_bash to dispatch lookup:
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS[block.name] # lookup
output = handler(**block.input) # call
results.append(...)
```
Adding a tool = one entry in `TOOLS` array + one line in `TOOL_HANDLERS` dict. The loop stays the same.
---
## Multiple Tool Calls
The model often returns multiple tool_use calls at once — "read a.py and b.py, then list all .py files".
The teaching version executes them one by one in the original `response.content` order. CC's approach is more complex: it slices the original order into consecutive batches, where concurrency-safe tools within a batch run in parallel, and batches are strictly sequential (see appendix).
---
## Quick Reference
| Concept | One-Liner |
|---------|-----------|
| TOOL_HANDLERS | Tool name → handler function dict. Add a tool = add one mapping line |
| Tool Definition | JSON schema telling the model "what I can do" |
| Multiple tool calls | Model may return multiple tool_use at once; teaching version executes them in original order |
| Loop Unchanged | s01's `while True` loop — not a single line changed |
---
## Changes from s01
| Component | Before (s01) | After (s02) |
|-----------|-------------|-------------|
| Tool count | 1 (bash) | 5 (+read, write, edit, glob) |
| Tool execution | Hardcoded `run_bash()` | TOOL_HANDLERS dispatch lookup |
| Path safety | None | safe_path validation (file tools only) |
| Loop | `while True` + `stop_reason` | Identical to s01 |
---
## Try It
```sh
cd learn-claude-code
python s02_tool_use/code.py
```
Try these prompts:
1. `Read the file README.md and tell me what this project is about`
2. `Create a file called test.py that prints "hello", then read it back`
3. `Find all Python files in this directory`
4. `Read both README.md and requirements.txt, then create a summary file`
What to watch for: When does the model call just one tool, and when does it call multiple at once? Are multiple tool calls executed in the correct order?
---
## What's Next
The Agent now has 5 specialized tools. File tools are protected by `safe_path`, but bash is unrestricted — `rm -rf /` still runs.
→ s03 Permission: Add a gate before tool execution — is this operation safe? Does it need user approval?
<details>
<summary>Dive into CC Source Code</summary>
> The following is based on a review of CC source code `Tool.ts`, `tools.ts`, `toolOrchestration.ts`, `toolExecution.ts`, and `StreamingToolExecutor.ts`.
### 1. Tool Definition Approach
**Teaching version**: `TOOLS` array + `TOOL_HANDLERS` dict. Definition and implementation are separate.
**CC**: Each tool is an independent object created by `buildTool()`, containing schema, validation, permissions, and execution. `getAllBaseTools()` aggregates all tools.
The teaching version's separation is clearer for teaching — readers immediately see "add a tool = two definitions".
### 2. Concurrency Safety: isConcurrencySafe()
![Tool Concurrency](images/concurrency-comparison.en.svg)
The teaching version executes tools one by one in original order, without concurrency. CC uses `isConcurrencySafe(input)` to determine concurrency — note this isn't simply "read-only vs write", but judges by specific input:
| | isReadOnly | isConcurrencySafe |
|---|---|---|
| FileRead | true | true |
| Glob | true | true |
| Bash `ls` | true | **true** ← key difference |
| Bash `rm` | false | false |
| TaskCreate | false | **true** ← modifies state but can be concurrent (introduced in s12) |
CC's Bash tool's `isConcurrencySafe` equals `isReadOnly` — read-only commands can be concurrent, write commands cannot. TaskCreate modifies task files, but each writes a different file, so it can be concurrent.
### 3. Partition Algorithm
CC's `partitionToolCalls()` (`toolOrchestration.ts:91-115`) doesn't split into two groups — it batches tool calls **by consecutive blocks**:
```
[read A, read B, glob *.py, bash "rm x", read C]
→ batch1(concurrent): [read A, read B, glob *.py]
→ batch2(serial): [bash "rm x"]
→ batch3(concurrent): [read C]
```
Consecutive concurrency-safe calls are grouped into the same batch for truly concurrent execution (`toolOrchestration.ts:152-176`, with a concurrency limit). When a non-concurrency-safe call is encountered, a new batch starts for serial execution. Batches are strictly sequential.
### 4. Validation Pipeline
Each tool call in CC goes through a strict 5-step validation (`toolExecution.ts`):
1. **Zod schema validation** (`614-680`, teaching version uses JSON Schema): parameter type/structure check
2. **Tool-level validateInput()** (`682-733`): parameter value validation (e.g., is the path within the working directory)
3. **PreToolUse hooks** (`800-862`, covered in s04): hooks can return messages, modify input, or block execution
4. **Permission check** (`921-931`, core topic of s03): canUseTool + checkPermissions → allow/deny/ask
5. **Execute tool.call()** (`1207-1222`)
The teaching version omits Zod (uses JSON Schema), omits validateInput (uses safety functions), but preserves the permission check and hook concepts.
### 5. Streaming Tool Execution
CC's `StreamingToolExecutor` (`StreamingToolExecutor.ts`) starts tools while the model is still generating — no waiting for the model to finish. `read_file` might complete while the model is still outputting "Let me analyze". The teaching version doesn't implement this, consistent with s01's goal — conceptual clarity, not peak performance.
### 6. Tool Result Persistence
Each tool has a `maxResultSizeChars` field. Results exceeding this threshold are persisted to disk, and the model sees a preview + file path. FileRead is special — set to `Infinity`, preventing file read output from being persisted again. Specifically, if FileRead's result exceeds the threshold and gets persisted, the model's next read of that persisted file would trigger another persistence → infinite loop (read file → persist → re-read → re-persist → ...).
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->

222
s02_tool_use/README.ja.md Normal file
View File

@@ -0,0 +1,222 @@
# s02: Tool Use — ツール一つ追加、一行追加だけ
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
s01 → `s02` → [s03](../s03_permission/) → s04 → ... → s20
> *"ツールを一つ追加、ハンドラを一つ追加"* — ループはそのまま。新しいツールをディスパッチマップに登録するだけ。
>
> **Harness レイヤー**: ツールディスパッチ — モデルが触れる範囲を拡張。
---
## ツールは bash 一つだけ
s01 の Agent には bash 一つのツールしかない。ファイルを読むには `cat`、書くには `echo "..." > file.py`、編集するには `sed`
モデルは「このファイルを読みたい」と考えながら、`cat path/to/file` と組み立てなければならない。翻訳の層が一つ増え、トークンを無駄にし、エラーも起きやすい。
---
## 概要:ツールディスパッチ
![Tool Dispatch](images/tool-dispatch.ja.svg)
s01 のループは完全に保持されるLLM 呼び出し、stop_reason 判定、メッセージ追加 — 一文字も変更なし)。唯一の変更点はツール実行の 1 行:`run_bash()``TOOL_HANDLERS[block.name]()` の検索ディスパッチに置き換わる。
Agent にツールを追加するには、たった二つ:
1. **ツールを定義**`TOOLS` 配列に一条を追加
2. **ハンドラを登録**`TOOL_HANDLERS` 辞書に一つのマッピングを追加
---
## 1 つのツールから 5 つのツールへ
s01 には bash だけだった:
```python
TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```
s02 では 5 つに増え、各ツールは独立して定義される:
```python
TOOLS = [
{"name": "bash", "description": "Run a shell command.", ...},
{"name": "read_file", "description": "Read file contents.", ...},
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
]
```
各ツールには専用の実装関数がある:
```python
def run_read(path, limit=None):
lines = safe_path(path).read_text().splitlines()
if limit:
lines = lines[:limit]
return "\n".join(lines)
def run_write(path, content):
safe_path(path).write_text(content)
return f"Wrote {len(content)} bytes to {path}"
def run_edit(path, old_text, new_text):
text = safe_path(path).read_text()
if old_text not in text:
return "Error: text not found"
safe_path(path).write_text(text.replace(old_text, new_text, 1))
return f"Edited {path}"
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))
```
---
## ツールディスパッチ
```python
TOOL_HANDLERS = {
"bash": run_bash,
"read_file": run_read,
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
}
# ループ内で変更されたのは一行だけ — ハードコードの run_bash から検索ディスパッチへ:
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS[block.name] # 検索
output = handler(**block.input) # 呼び出し
results.append(...)
```
ツールの追加 = `TOOLS` 配列に一条 + `TOOL_HANDLERS` 辞書に一行。ループは変わらない。
---
## 複数のツール呼び出し
モデルはよく一度に複数の tool_use を返す — 「a.py と b.py を読んで、全 .py ファイルを列挙して」。
教育版は `response.content` の元の順序で一つずつ実行する。CC のやり方はより複雑:元の順序を保ったまま連続バッチに分割し、バッチ内の並列安全なツールを並行実行し、バッチ間は厳密に順次(付録を参照)。
---
## 速查
| 概念 | 一言で |
|------|--------|
| TOOL_HANDLERS | ツール名 → ハンドラ関数の辞書。ツール追加 = マッピング一行追加 |
| ツール定義 | モデルに「何ができるか」を伝える JSON schema |
| 複数ツール呼び出し | モデルは一度に複数の tool_use を返す可能性がある。教育版は元の順序で一つずつ実行 |
| ループ不変 | s01 の `while True` ループ — 一行も変更なし |
---
## s01 からの変更
| コンポーネント | 変更前 (s01) | 変更後 (s02) |
|--------------|-------------|-------------|
| ツール数 | 1 (bash) | 5 (+read, write, edit, glob) |
| ツール実行 | ハードコード `run_bash()` | TOOL_HANDLERS 検索ディスパッチ |
| パス安全性 | なし | safe_path 検証file tools のみ) |
| ループ | `while True` + `stop_reason` | s01 と完全に同一 |
---
## 試してみよう
```sh
cd learn-claude-code
python s02_tool_use/code.py
```
以下のプロンプトを試してみよう:
1. `Read the file README.md and tell me what this project is about`
2. `Create a file called test.py that prints "hello", then read it back`
3. `Find all Python files in this directory`
4. `Read both README.md and requirements.txt, then create a summary file`
観察のポイント:モデルがツールを一つだけ呼び出すときと、複数同時に呼び出すときの違い。複数のツール呼び出しは正しい順序で実行されているか?
---
## 次へ
Agent は 5 つの専用ツールを持つようになった。file tools は `safe_path` で保護されるが、bash は制限なし — `rm -rf /` はまだ実行できる。
→ s03 Permissionツール実行前にゲートを追加 — この操作は安全か? ユーザーの承認が必要か?
<details>
<summary>CC ソースコードを深掘り</summary>
> 以下は CC ソースコード `Tool.ts`、`tools.ts`、`toolOrchestration.ts`、`toolExecution.ts`、`StreamingToolExecutor.ts` の検証に基づく。
### 一、ツール定義方式
**教育版**`TOOLS` 配列 + `TOOL_HANDLERS` 辞書。定義と実装が分離。
**CC**:各ツールは `buildTool()` で作成された独立オブジェクトで、schema、バリデーション、権限、実行を含む。`getAllBaseTools()` が全ツールを集約。
教育版の分離方式は教学に適している — 読者は「ツール追加 = 二つの定義」と一目で分かる。
### 二、並列安全性isConcurrencySafe()
![Tool Concurrency](images/concurrency-comparison.ja.svg)
教育版は元の順序で一つずつ実行し、並列処理は行わない。CC は `isConcurrencySafe(input)` で並列可否を判断する — これは単なる「読み取り専用 vs 書き込み」ではなく、具体的な入力で判断する:
| | isReadOnly | isConcurrencySafe |
|---|---|---|
| FileRead | true | true |
| Glob | true | true |
| Bash `ls` | true | **true** ← 重要な違い |
| Bash `rm` | false | false |
| TaskCreate | false | **true** ← 状態変更するが並列可能s12 で紹介) |
CC の Bash ツールの `isConcurrencySafe``isReadOnly` と同じ — 読み取り専用コマンドは並列可能、書き込みコマンドは不可。TaskCreate はタスクファイルを変更するが、毎回異なるファイルに書き込むため並列可能。
### 三、パーティションアルゴリズム
CC の `partitionToolCalls()``toolOrchestration.ts:91-115`)は二つのグループに分けるのではなく、ツール呼び出しを**連続ブロックごとにバッチ化**する:
```
[read A, read B, glob *.py, bash "rm x", read C]
→ batch1(並列): [read A, read B, glob *.py]
→ batch2(直列): [bash "rm x"]
→ batch3(並列): [read C]
```
連続する並列安全な呼び出しを同じバッチにまとめ、真の並列実行を行う(`toolOrchestration.ts:152-176`、並列数上限あり)。非並列安全な呼び出しに遭遇すると新しいバッチを開始して直列実行。バッチ間は厳密に順次。
### 四、バリデーションパイプライン
CC の各ツール呼び出しは厳格な 5 段階のバリデーションを経る(`toolExecution.ts`
1. **Zod schema バリデーション**`614-680`、教育版は JSON Schema で代替):パラメータの型/構造チェック
2. **ツールレベル validateInput()**`682-733`):パラメータ値の検証(例:パスが作業ディレクトリ内か)
3. **PreToolUse フック**`800-862`、s04 で詳解):フックはメッセージの返却、入力の変更、実行のブロックが可能
4. **権限チェック**`921-931`、s03 の核心canUseTool + checkPermissions → allow/deny/ask
5. **tool.call() の実行**`1207-1222`
教育版は Zod を省略JSON Schema を使用、validateInput を省略(安全関数を使用)、権限チェックとフック概念は保持。
### 五、ストリーミングツール実行
CC の `StreamingToolExecutor``StreamingToolExecutor.ts`)はモデルがまだ生成中にツールを起動する — モデルの完了を待たない。`read_file` はモデルが「分析します」と出力中に完了するかもしれない。教育版はこれを実装しない。s01 と同じ目標 — 概念の明確さ、極限のパフォーマンスではない。
### 六、ツール結果の永続化
各ツールには `maxResultSizeChars` フィールドがある。この閾値を超える結果はディスクに保存され、モデルにはプレビュー + ファイルパスが表示される。FileRead は特殊 — `Infinity` に設定され、ファイル読み出し結果の再永続化を防ぐ。具体的には、FileRead の結果が閾値を超えて永続化されると、モデルがその永続化ファイルを次に読むときにまた永続化がトリガーされ → 無限ループ(ファイル読む → 永続化 → 再読み → 再永続化 → ...)になる。
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->

222
s02_tool_use/README.md Normal file
View File

@@ -0,0 +1,222 @@
# s02: Tool Use — 多加一个工具,只加一行
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
s01 → `s02` → [s03](../s03_permission/) → s04 → ... → s20
> *"加一个工具, 只加一个 handler"* — 循环不用动, 新工具注册进 dispatch map 就行。
>
> **Harness 层**: 工具分发 — 扩展模型能触达的边界。
---
## 只有 bash 一个工具
s01 的 Agent 只有一个 bash 工具。读文件要 `cat`,写文件要 `echo "..." > file.py`,改文件要 `sed`
模型想的是"读这个文件",却要拼出 `cat path/to/file`。多了一层翻译,浪费 token还容易拼错。
---
## 全局视角:工具分发
![Tool Dispatch](images/tool-dispatch.svg)
s01 的循环完全保留LLM 调用、stop_reason 判断、消息追加)。唯一的变动在工具执行那 1 行:`run_bash()` 替换为 `TOOL_HANDLERS[block.name]()` 查表分发。
给 Agent 加一个工具只需要做两件事:
1. **定义工具**:在 `TOOLS` 数组里加一条描述
2. **注册处理函数**:在 `TOOL_HANDLERS` 字典里加一个映射
---
## 从 1 个工具到 5 个工具
s01 只有一个 bash
```python
TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```
s02 加到 5 个,每个工具都是独立定义:
```python
TOOLS = [
{"name": "bash", "description": "Run a shell command.", ...},
{"name": "read_file", "description": "Read file contents.", ...},
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
]
```
每个工具有自己的实现函数:
```python
def run_read(path, limit=None):
lines = safe_path(path).read_text().splitlines()
if limit:
lines = lines[:limit]
return "\n".join(lines)
def run_write(path, content):
safe_path(path).write_text(content)
return f"Wrote {len(content)} bytes to {path}"
def run_edit(path, old_text, new_text):
text = safe_path(path).read_text()
if old_text not in text:
return "Error: text not found"
safe_path(path).write_text(text.replace(old_text, new_text, 1))
return f"Edited {path}"
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))
```
---
## 工具分发
```python
TOOL_HANDLERS = {
"bash": run_bash,
"read_file": run_read,
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
}
# 循环里只改了一行——从硬编码 run_bash 变成查表:
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS[block.name] # 查表
output = handler(**block.input) # 调用
results.append(...)
```
加一个工具 = 在 `TOOLS` 数组加一条 + 在 `TOOL_HANDLERS` 字典加一行。循环不变。
---
## 多个工具调用
模型经常一次返回多个 tool_use"读一下 a.py 和 b.py然后列出所有 .py 文件"。
教学版按 `response.content` 原始顺序逐个执行。CC 的做法更复杂:按原始顺序切成连续 batchbatch 内并发安全的工具并行执行batch 间严格顺序(见附录)。
---
## 速查
| 概念 | 一句话 |
|------|--------|
| TOOL_HANDLERS | 工具名 → 处理函数的字典。加工具 = 加一行映射 |
| 工具定义 | 告诉模型"我能做什么"的 JSON schema |
| 多工具调用 | 模型可一次返回多个 tool_use教学版按原始顺序逐个执行 |
| 循环不变 | s01 的 `while True` 循环一行都没改 |
---
## 相对 s01 的变更
| 组件 | 之前 (s01) | 之后 (s02) |
|------|-----------|-----------|
| 工具数量 | 1 (bash) | 5 (+read, write, edit, glob) |
| 工具执行 | 硬编码 `run_bash()` | TOOL_HANDLERS 查表分发 |
| 路径安全 | 无 | safe_path 校验(仅 file tools |
| 循环 | `while True` + `stop_reason` | 与 s01 完全一致 |
---
## 试一下
```sh
cd learn-claude-code
python s02_tool_use/code.py
```
试试这些 prompt
1. `Read the file README.md and tell me what this project is about`
2. `Create a file called test.py that prints "hello", then read it back`
3. `Find all Python files in this directory`
4. `Read both README.md and requirements.txt, then create a summary file`
观察重点:模型什么时候只调一个工具,什么时候一次调多个?多个工具调用的顺序和结果是否正确?
---
## 接下来
现在 Agent 有 5 个专用工具。file tools 受 `safe_path` 保护,但 bash 不受限制,`rm -rf /` 还是能跑。
s03 Permission → 在工具执行之前加一道门:这个操作安全吗?需要用户批准吗?
<details>
<summary>深入 CC 源码</summary>
> 以下基于 CC 源码 `Tool.ts`、`tools.ts`、`toolOrchestration.ts`、`toolExecution.ts`、`StreamingToolExecutor.ts` 的核查。
### 一、工具定义方式
**教学版**`TOOLS` 数组 + `TOOL_HANDLERS` 字典。定义和实现分开。
**CC**:每个工具是 `buildTool()` 创建的独立对象,包含 schema、验证、权限、执行。`getAllBaseTools()` 汇总所有工具。
教学版的分离方式对教学更清晰——读者一眼看到"加一个工具 = 两条定义"。
### 二、并发安全判断isConcurrencySafe()
![Tool Concurrency](images/concurrency-comparison.svg)
教学版按原始顺序逐个执行不做并发。CC 用 `isConcurrencySafe(input)` 判断能否并发——注意这不是简单的"只读 vs 写",而是按具体输入判断:
| | isReadOnly | isConcurrencySafe |
|---|---|---|
| FileRead | true | true |
| Glob | true | true |
| Bash `ls` | true | **true** ← 关键差异 |
| Bash `rm` | false | false |
| TaskCreate | false | **true** ← 改状态但可并发TaskCreate 在 s12 介绍) |
CC 的 Bash tool 的 `isConcurrencySafe` 等于 `isReadOnly`——只读命令可并发写命令不可。TaskCreate 虽然改了任务文件,但每次都写不同的文件,所以可以并发。
### 三、分区算法
CC 的 `partitionToolCalls()``toolOrchestration.ts:91-115`)不是分两组,而是把工具调用**按连续块分批**
```
[read A, read B, glob *.py, bash "rm x", read C]
→ batch1(并发): [read A, read B, glob *.py]
→ batch2(串行): [bash "rm x"]
→ batch3(并发): [read C]
```
并发安全的连续块编入同一个 batchbatch 内真正并发执行(`toolOrchestration.ts:152-176`,有并发上限)。遇到非并发安全的就开新 batch 串行执行。batch 之间严格顺序。
### 四、验证管线
CC 的每个工具调用经过严格的 5 步验证(`toolExecution.ts`
1. **Zod schema 验证**`614-680`,教学版用 JSON Schema 替代):参数类型/结构检查
2. **工具级 validateInput()**`682-733`):参数值验证(如路径是否在工作区内)
3. **PreToolUse hooks**`800-862`s04 详细介绍):钩子可以返回消息、修改输入、阻止执行
4. **权限检查**`921-931`s03 的核心内容canUseTool + checkPermissions → allow/deny/ask
5. **执行 tool.call()**`1207-1222`
教学版省略了 Zod用 JSON Schema、省略了 validateInput用安全函数、保留了权限检查和钩子概念。
### 五、流式工具执行
CC 的 `StreamingToolExecutor``StreamingToolExecutor.ts`)让工具在模型还在生成时就启动——不等模型说完。`read_file` 可能在模型还在输出"我来分析"的时候就跑完了。教学版不实现这个,目标和 s01 一致——概念清晰,不追求性能极致。
### 六、工具结果持久化
每个工具有一个 `maxResultSizeChars` 字段。结果超过这个值就落盘,模型看到的是预览 + 文件路径。FileRead 特殊——设为 `Infinity`,防止读文件的输出又被当成文件落盘。具体来说,如果 FileRead 的结果超过阈值被落盘,模型下次读那个落盘文件时又会触发落盘 → 无限循环(读文件 → 落盘 → 再读 → 再落盘 → ...)。
</details>
<!-- translation-sync: zh@v1, en@v0, ja@v0 -->

189
s02_tool_use/code.py Normal file
View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""
s02: Tool Use — 在 s01 基础上新增 4 个工具 + 分发映射。
运行: python s02_tool_use/code.py
需要: pip install anthropic python-dotenv + .env 中配置 ANTHROPIC_API_KEY
本文件 = s01 的全部代码 + 以下新增:
+ run_read / run_write / run_edit / run_glob 四个工具实现
+ TOOL_HANDLERS 分发映射(替代 s01 中硬编码的 run_bash 调用)
+ safe_path 路径安全校验
循环本身agent_loop与 s01 完全一致。
"""
import os, subprocess
from pathlib import Path
try:
import readline
readline.parse_and_bind('set bind-tty-special-chars off')
readline.parse_and_bind('set input-meta on')
readline.parse_and_bind('set output-meta on')
readline.parse_and_bind('set convert-meta off')
except ImportError:
pass
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."
# ═══════════════════════════════════════════════════════════
# FROM s01 (unchanged)
# ═══════════════════════════════════════════════════════════
def run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:
r = subprocess.run(command, shell=True, cwd=WORKDIR,
capture_output=True, text=True, timeout=120)
out = (r.stdout + r.stderr).strip()
return out[:50000] if out else "(no output)"
except subprocess.TimeoutExpired:
return "Error: Timeout (120s)"
except (FileNotFoundError, OSError) as e:
return f"Error: {e}"
# ═══════════════════════════════════════════════════════════
# NEW in s02: 4 个新工具
# ═══════════════════════════════════════════════════════════
def safe_path(p: str) -> Path:
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path
def run_read(path: str, limit: int | None = None) -> str:
try:
lines = safe_path(path).read_text().splitlines()
if limit and limit < len(lines):
lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
return "\n".join(lines)
except Exception as e:
return f"Error: {e}"
def run_write(path: str, content: str) -> str:
try:
file_path = safe_path(path)
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.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:
try:
file_path = safe_path(path)
text = file_path.read_text()
if old_text not in text:
return f"Error: text not found in {path}"
file_path.write_text(text.replace(old_text, new_text, 1))
return f"Edited {path}"
except Exception as e:
return f"Error: {e}"
def run_glob(pattern: str) -> str:
import glob as g
try:
results = []
for match in g.glob(pattern, root_dir=WORKDIR):
if (WORKDIR / match).resolve().is_relative_to(WORKDIR):
results.append(match)
return "\n".join(results) if results else "(no matches)"
except Exception as e:
return f"Error: {e}"
# ═══════════════════════════════════════════════════════════
# NEW in s02: 工具定义s01 只有一个 bash现在扩展到 5 个)
# ═══════════════════════════════════════════════════════════
TOOLS = [
{"name": "bash", "description": "Run a shell command.",
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
{"name": "read_file", "description": "Read file contents.",
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
{"name": "write_file", "description": "Write content to a file.",
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
{"name": "edit_file", "description": "Replace exact text in a file once.",
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
{"name": "glob", "description": "Find files matching a glob pattern.",
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
]
# ═══════════════════════════════════════════════════════════
# NEW in s02: 工具分发映射s01 是硬编码 run_bash现在改为查表
# ═══════════════════════════════════════════════════════════
TOOL_HANDLERS = {
"bash": run_bash, "read_file": run_read, "write_file": run_write,
"edit_file": run_edit, "glob": run_glob,
}
# ═══════════════════════════════════════════════════════════
# agent_loop — 与 s01 结构完全一致,只改了工具执行那部分
# s01: output = run_bash(block.input["command"])
# s02: output = TOOL_HANDLERS[block.name](**block.input)
# ═══════════════════════════════════════════════════════════
def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
results = []
for block in response.content:
if block.type == "tool_use":
print(f"\033[33m> {block.name}\033[0m")
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown: {block.name}"
print(str(output)[:200])
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
messages.append({"role": "user", "content": results})
if __name__ == "__main__":
print("s02: Tool Use — 在 s01 基础上加了 4 个工具")
print("输入问题,回车发送。输入 q 退出。\n")
history = []
while True:
try:
query = input("\033[36ms02 >> \033[0m")
except (EOFError, KeyboardInterrupt):
break
if query.strip().lower() in ("q", "exit", ""):
break
history.append({"role": "user", "content": query})
agent_loop(history)
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
print()

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 500" font-family="system-ui, -apple-system, sans-serif">
<defs>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
<linearGradient id="teach" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fef3c7"/><stop offset="100%" stop-color="#fde68a"/>
</linearGradient>
<linearGradient id="cc" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dcfce7"/><stop offset="100%" stop-color="#bbf7d0"/>
</linearGradient>
<marker id="arrow-g" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
</defs>
<rect width="760" height="500" fill="#fafbfc" rx="8"/>
<rect x="0" y="0" width="760" height="36" fill="url(#header)" rx="8"/>
<rect x="0" y="28" width="760" height="8" fill="url(#header)"/>
<text x="380" y="24" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Tool Concurrency — Teaching Version vs Claude Code</text>
<!-- Input tool blocks -->
<rect x="180" y="52" width="400" height="28" rx="14" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1"/>
<text x="380" y="71" fill="#475569" font-size="11" font-weight="600" text-anchor="middle">Model returns 5 tool calls at once</text>
<rect x="38" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="88" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read A.py</text>
<rect x="148" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="198" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">glob *.py</text>
<rect x="258" y="92" width="110" height="36" rx="4" fill="#fef3c7" stroke="#fbbf24" stroke-width="1"/>
<text x="313" y="114" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">bash "ls -la"</text>
<rect x="378" y="92" width="100" height="36" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="428" y="114" fill="#991b1b" font-size="10" font-weight="600" text-anchor="middle">write B.py</text>
<rect x="488" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="538" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read C.py</text>
<!-- LEFT: Teaching Version -->
<rect x="20" y="156" width="350" height="230" rx="8" fill="url(#teach)" stroke="#d97706" stroke-width="1.5"/>
<text x="195" y="180" fill="#92400e" font-size="13" font-weight="700" text-anchor="middle">Teaching: Original Order, One by One</text>
<rect x="35" y="192" width="320" height="46" rx="4" fill="#fff" stroke="#fbbf24" stroke-width="0.5"/>
<text x="46" y="209" fill="#92400e" font-size="9" font-family="monospace">for block in response.content:</text>
<text x="46" y="224" fill="#92400e" font-size="9" font-family="monospace"> TOOL_HANDLERS[name](**input)</text>
<text x="195" y="258" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">Result: 5 serial calls, no batches</text>
<rect x="45" y="270" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="284" fill="#92400e" font-size="8" font-weight="600">1. read A.py</text>
<rect x="45" y="294" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="308" fill="#92400e" font-size="8" font-weight="600">2. glob *.py</text>
<rect x="45" y="318" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="332" fill="#92400e" font-size="8" font-weight="600">3. bash "ls -la"</text>
<rect x="45" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="356" fill="#92400e" font-size="8" font-weight="600">4. write B.py</text>
<rect x="200" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="215" y="356" fill="#92400e" font-size="8" font-weight="600">5. read C.py</text>
<text x="195" y="378" fill="#dc2626" font-size="8" font-weight="600" text-anchor="middle">Teaching focus: tool dispatch first; concurrency omitted</text>
<!-- RIGHT: Claude Code -->
<rect x="390" y="156" width="350" height="230" rx="8" fill="url(#cc)" stroke="#16a34a" stroke-width="1.5"/>
<text x="565" y="180" fill="#166534" font-size="13" font-weight="700" text-anchor="middle">Claude Code: isConcurrencySafe(input)</text>
<rect x="405" y="192" width="320" height="38" rx="4" fill="#fff" stroke="#86efac" stroke-width="0.5"/>
<text x="416" y="207" fill="#166534" font-size="9" font-family="monospace">Each tool call judged individually:</text>
<text x="416" y="222" fill="#166534" font-size="9" font-family="monospace">tool.isConcurrencySafe(parsedInput) → bool</text>
<text x="565" y="250" fill="#166534" font-size="10" font-weight="600" text-anchor="middle">Result: 3 batches (by consecutive blocks)</text>
<rect x="400" y="258" width="155" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="477" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 1</text>
<text x="477" y="289" fill="#166534" font-size="8" text-anchor="middle">Concurrent</text>
<text x="477" y="302" fill="#166534" font-size="7" text-anchor="middle">read A · glob · bash "ls"</text>
<line x1="560" y1="283" x2="575" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="580" y="258" width="65" height="50" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="612" y="276" fill="#991b1b" font-size="8" font-weight="600" text-anchor="middle">Batch 2</text>
<text x="612" y="289" fill="#991b1b" font-size="8" text-anchor="middle">Serial</text>
<text x="612" y="302" fill="#991b1b" font-size="7" text-anchor="middle">write B</text>
<line x1="650" y1="283" x2="665" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="670" y="258" width="55" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="697" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 3</text>
<text x="697" y="289" fill="#166534" font-size="8" text-anchor="middle">Concurrent</text>
<text x="697" y="302" fill="#166534" font-size="7" text-anchor="middle">read C</text>
<text x="565" y="332" fill="#16a34a" font-size="8" font-weight="600" text-anchor="middle">bash "ls" is safe and consecutive, so it stays in Batch 1</text>
<text x="565" y="366" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ Input-dependent safety, not tool-name hardcoding</text>
<text x="565" y="380" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ Original order preserved; only safe consecutive calls run together</text>
<!-- Bottom Summary -->
<rect x="20" y="402" width="720" height="82" rx="6" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1"/>
<text x="40" y="424" fill="#1e3a5f" font-size="12" font-weight="600">Key Difference</text>
<text x="40" y="444" fill="#475569" font-size="10">• Teaching: executes response.content in original order, one tool call at a time; no concurrency or batching</text>
<text x="40" y="460" fill="#475569" font-size="10">• CC: checks isConcurrencySafe(input), then groups consecutive safe calls into one batch</text>
<text x="40" y="476" fill="#475569" font-size="10">• Key difference: teaching focuses on dispatch; CC optimizes safe concurrency while preserving order semantics</text>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 500" font-family="system-ui, -apple-system, sans-serif">
<defs>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
<linearGradient id="teach" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fef3c7"/><stop offset="100%" stop-color="#fde68a"/>
</linearGradient>
<linearGradient id="cc" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dcfce7"/><stop offset="100%" stop-color="#bbf7d0"/>
</linearGradient>
<marker id="arrow-g" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
</defs>
<rect width="760" height="500" fill="#fafbfc" rx="8"/>
<rect x="0" y="0" width="760" height="36" fill="url(#header)" rx="8"/>
<rect x="0" y="28" width="760" height="8" fill="url(#header)"/>
<text x="380" y="24" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">ツール並列実行 — 教育版 vs Claude Code</text>
<!-- 入力ツールブロック -->
<rect x="180" y="52" width="400" height="28" rx="14" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1"/>
<text x="380" y="71" fill="#475569" font-size="11" font-weight="600" text-anchor="middle">モデルが一度に 5 つのツール呼び出しを返す</text>
<rect x="38" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="88" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read A.py</text>
<rect x="148" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="198" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">glob *.py</text>
<rect x="258" y="92" width="110" height="36" rx="4" fill="#fef3c7" stroke="#fbbf24" stroke-width="1"/>
<text x="313" y="114" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">bash "ls -la"</text>
<rect x="378" y="92" width="100" height="36" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="428" y="114" fill="#991b1b" font-size="10" font-weight="600" text-anchor="middle">write B.py</text>
<rect x="488" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="538" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read C.py</text>
<!-- 左:教育版 -->
<rect x="20" y="156" width="350" height="230" rx="8" fill="url(#teach)" stroke="#d97706" stroke-width="1.5"/>
<text x="195" y="180" fill="#92400e" font-size="13" font-weight="700" text-anchor="middle">教育版:元の順序で一つずつ実行</text>
<rect x="35" y="192" width="320" height="46" rx="4" fill="#fff" stroke="#fbbf24" stroke-width="0.5"/>
<text x="46" y="209" fill="#92400e" font-size="9" font-family="monospace">for block in response.content:</text>
<text x="46" y="224" fill="#92400e" font-size="9" font-family="monospace"> TOOL_HANDLERS[name](**input)</text>
<text x="195" y="258" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">結果5 回の直列呼び出し、batch なし</text>
<rect x="45" y="270" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="284" fill="#92400e" font-size="8" font-weight="600">1. read A.py</text>
<rect x="45" y="294" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="308" fill="#92400e" font-size="8" font-weight="600">2. glob *.py</text>
<rect x="45" y="318" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="332" fill="#92400e" font-size="8" font-weight="600">3. bash "ls -la"</text>
<rect x="45" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="356" fill="#92400e" font-size="8" font-weight="600">4. write B.py</text>
<rect x="200" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="215" y="356" fill="#92400e" font-size="8" font-weight="600">5. read C.py</text>
<text x="195" y="378" fill="#dc2626" font-size="8" font-weight="600" text-anchor="middle">教育の焦点:まず tool_use 分配を理解し、並列は省略</text>
<!--Claude Code -->
<rect x="390" y="156" width="350" height="230" rx="8" fill="url(#cc)" stroke="#16a34a" stroke-width="1.5"/>
<text x="565" y="180" fill="#166534" font-size="13" font-weight="700" text-anchor="middle">Claude CodeisConcurrencySafe(input)</text>
<rect x="405" y="192" width="320" height="38" rx="4" fill="#fff" stroke="#86efac" stroke-width="0.5"/>
<text x="416" y="207" fill="#166534" font-size="9" font-family="monospace">各ツール呼び出しを個別に判定:</text>
<text x="416" y="222" fill="#166534" font-size="9" font-family="monospace">tool.isConcurrencySafe(parsedInput) → bool</text>
<text x="565" y="250" fill="#166534" font-size="10" font-weight="600" text-anchor="middle">結果3 バッチ(連続ブロックごと)</text>
<rect x="400" y="258" width="155" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="477" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 1</text>
<text x="477" y="289" fill="#166534" font-size="8" text-anchor="middle">並列</text>
<text x="477" y="302" fill="#166534" font-size="7" text-anchor="middle">read A · glob · bash "ls"</text>
<line x1="560" y1="283" x2="575" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="580" y="258" width="65" height="50" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="612" y="276" fill="#991b1b" font-size="8" font-weight="600" text-anchor="middle">Batch 2</text>
<text x="612" y="289" fill="#991b1b" font-size="8" text-anchor="middle">直列</text>
<text x="612" y="302" fill="#991b1b" font-size="7" text-anchor="middle">write B</text>
<line x1="650" y1="283" x2="665" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="670" y="258" width="55" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="697" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 3</text>
<text x="697" y="289" fill="#166534" font-size="8" text-anchor="middle">並列</text>
<text x="697" y="302" fill="#166534" font-size="7" text-anchor="middle">read C</text>
<text x="565" y="332" fill="#16a34a" font-size="8" font-weight="600" text-anchor="middle">bash "ls" は安全かつ連続しているため Batch 1 に入る</text>
<text x="565" y="366" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ 入力に基づく安全判定、ツール名ハードコードではない</text>
<text x="565" y="380" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ 元の順序を保ち、連続する安全呼び出しだけ並列化</text>
<!-- 下部まとめ -->
<rect x="20" y="402" width="720" height="82" rx="6" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1"/>
<text x="40" y="424" fill="#1e3a5f" font-size="12" font-weight="600">核心的な違い</text>
<text x="40" y="444" fill="#475569" font-size="10">• 教育版response.content の元の順序で一つずつ実行し、並列処理も batch 化もしない</text>
<text x="40" y="460" fill="#475569" font-size="10">• CCisConcurrencySafe(input) で判定し、連続する安全呼び出しを同じ batch にまとめる</text>
<text x="40" y="476" fill="#475569" font-size="10">• 差分の要点教育版は分配に集中し、CC は順序意味を保ったまま安全な並列を最適化する</text>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 500" font-family="system-ui, -apple-system, sans-serif">
<defs>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
<linearGradient id="teach" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fef3c7"/><stop offset="100%" stop-color="#fde68a"/>
</linearGradient>
<linearGradient id="cc" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dcfce7"/><stop offset="100%" stop-color="#bbf7d0"/>
</linearGradient>
<marker id="arrow-g" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
</defs>
<rect width="760" height="500" fill="#fafbfc" rx="8"/>
<rect x="0" y="0" width="760" height="36" fill="url(#header)" rx="8"/>
<rect x="0" y="28" width="760" height="8" fill="url(#header)"/>
<text x="380" y="24" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Tool Concurrency — 教学版 vs Claude Code</text>
<!-- Input tool blocks -->
<rect x="180" y="52" width="400" height="28" rx="14" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1"/>
<text x="380" y="71" fill="#475569" font-size="11" font-weight="600" text-anchor="middle">模型一次返回 5 个工具调用</text>
<rect x="38" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="88" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read A.py</text>
<rect x="148" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="198" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">glob *.py</text>
<rect x="258" y="92" width="110" height="36" rx="4" fill="#fef3c7" stroke="#fbbf24" stroke-width="1"/>
<text x="313" y="114" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">bash "ls -la"</text>
<rect x="378" y="92" width="100" height="36" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="428" y="114" fill="#991b1b" font-size="10" font-weight="600" text-anchor="middle">write B.py</text>
<rect x="488" y="92" width="100" height="36" rx="4" fill="#dbeafe" stroke="#93c5fd" stroke-width="1"/>
<text x="538" y="114" fill="#1e40af" font-size="10" font-weight="600" text-anchor="middle">read C.py</text>
<!-- LEFT: Teaching Version -->
<rect x="20" y="156" width="350" height="230" rx="8" fill="url(#teach)" stroke="#d97706" stroke-width="1.5"/>
<text x="195" y="180" fill="#92400e" font-size="13" font-weight="700" text-anchor="middle">教学版:按原始顺序逐个执行</text>
<rect x="35" y="192" width="320" height="46" rx="4" fill="#fff" stroke="#fbbf24" stroke-width="0.5"/>
<text x="46" y="209" fill="#92400e" font-size="9" font-family="monospace">for block in response.content:</text>
<text x="46" y="224" fill="#92400e" font-size="9" font-family="monospace"> TOOL_HANDLERS[name](**input)</text>
<text x="195" y="258" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">结果5 次串行调用,不做 batch</text>
<rect x="45" y="270" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="284" fill="#92400e" font-size="8" font-weight="600">1. read A.py</text>
<rect x="45" y="294" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="308" fill="#92400e" font-size="8" font-weight="600">2. glob *.py</text>
<rect x="45" y="318" width="300" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="332" fill="#92400e" font-size="8" font-weight="600">3. bash "ls -la"</text>
<rect x="45" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="60" y="356" fill="#92400e" font-size="8" font-weight="600">4. write B.py</text>
<rect x="200" y="342" width="145" height="20" rx="4" fill="#fff7ed" stroke="#fbbf24" stroke-width="0.7"/>
<text x="215" y="356" fill="#92400e" font-size="8" font-weight="600">5. read C.py</text>
<text x="195" y="378" fill="#dc2626" font-size="8" font-weight="600" text-anchor="middle">教学重点:先理解 tool_use 分发,暂不引入并发执行</text>
<!-- RIGHT: Claude Code -->
<rect x="390" y="156" width="350" height="230" rx="8" fill="url(#cc)" stroke="#16a34a" stroke-width="1.5"/>
<text x="565" y="180" fill="#166534" font-size="13" font-weight="700" text-anchor="middle">Claude CodeisConcurrencySafe(input)</text>
<rect x="405" y="192" width="320" height="38" rx="4" fill="#fff" stroke="#86efac" stroke-width="0.5"/>
<text x="416" y="207" fill="#166534" font-size="9" font-family="monospace">每个工具调用单独判断:</text>
<text x="416" y="222" fill="#166534" font-size="9" font-family="monospace">tool.isConcurrencySafe(parsedInput) → bool</text>
<text x="565" y="250" fill="#166534" font-size="10" font-weight="600" text-anchor="middle">结果3 个 batch按连续块分批</text>
<rect x="400" y="258" width="155" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="477" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 1</text>
<text x="477" y="289" fill="#166534" font-size="8" text-anchor="middle">并发</text>
<text x="477" y="302" fill="#166534" font-size="7" text-anchor="middle">read A · glob · bash "ls"</text>
<line x1="560" y1="283" x2="575" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="580" y="258" width="65" height="50" rx="4" fill="#fee2e2" stroke="#fca5a5" stroke-width="1"/>
<text x="612" y="276" fill="#991b1b" font-size="8" font-weight="600" text-anchor="middle">Batch 2</text>
<text x="612" y="289" fill="#991b1b" font-size="8" text-anchor="middle">串行</text>
<text x="612" y="302" fill="#991b1b" font-size="7" text-anchor="middle">write B</text>
<line x1="650" y1="283" x2="665" y2="283" stroke="#16a34a" stroke-width="1" marker-end="url(#arrow-g)"/>
<rect x="670" y="258" width="55" height="50" rx="4" fill="#dcfce7" stroke="#86efac" stroke-width="1"/>
<text x="697" y="276" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">Batch 3</text>
<text x="697" y="289" fill="#166534" font-size="8" text-anchor="middle">并发</text>
<text x="697" y="302" fill="#166534" font-size="7" text-anchor="middle">read C</text>
<text x="565" y="332" fill="#16a34a" font-size="8" font-weight="600" text-anchor="middle">bash "ls" 是并发安全调用,且和 read/glob 连续,所以留在 Batch 1</text>
<text x="565" y="366" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ 按输入判断并发安全,不按工具名硬编码</text>
<text x="565" y="380" fill="#16a34a" font-size="9" font-weight="600" text-anchor="middle">✓ 保留原始顺序,只在连续安全块内部并发</text>
<!-- Bottom Summary -->
<rect x="20" y="402" width="720" height="82" rx="6" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1"/>
<text x="40" y="424" fill="#1e3a5f" font-size="12" font-weight="600">核心差异</text>
<text x="40" y="444" fill="#475569" font-size="10">• 教学版:按 response.content 原始顺序逐个执行,不做并发,也不分 batch</text>
<text x="40" y="460" fill="#475569" font-size="10">• CC按 isConcurrencySafe(input) 判断,并把连续的并发安全调用合成同一个 batch</text>
<text x="40" y="476" fill="#475569" font-size="10">• 差异重点教学版聚焦工具分发CC 在保持顺序语义的同时优化安全并发</text>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 560" font-family="system-ui, -apple-system, sans-serif">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</marker>
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
</marker>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/>
<stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="720" height="560" fill="#fafbfc" rx="8"/>
<!-- Title -->
<rect x="0" y="0" width="720" height="48" fill="url(#header)" rx="8"/>
<rect x="0" y="40" width="720" height="8" fill="url(#header)"/>
<text x="360" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Tool Use — Loop Unchanged, Just Add Dispatch Mapping</text>
<!-- ===== s01 (gray, preserved) ===== -->
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s01 Preserved</text>
<!-- User Input -->
<rect x="60" y="86" width="140" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="130" y="105" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">User Query</text>
<text x="130" y="121" fill="#64748b" font-size="10" text-anchor="middle">messages[]</text>
<!-- Arrow: User → LLM -->
<line x1="200" y1="108" x2="268" y2="108" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<!-- LLM -->
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason check</text>
<!-- Arrow: LLM → Decision -->
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<!-- Decision Diamond -->
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
<!-- No → Return -->
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="445" y="189" fill="#16a34a" font-size="9" font-weight="600">No</text>
<rect x="477" y="178" width="100" height="36" rx="18" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<text x="527" y="200" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">Return Result</text>
<!-- Yes → Next Step -->
<line x1="345" y1="226" x2="345" y2="260" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<text x="356" y="248" fill="#d97706" font-size="9" font-weight="600">Yes</text>
<!-- ===== s02 New: TOOL_HANDLERS Dispatch Mapping ===== -->
<text x="505" y="282" fill="#d97706" font-size="11" font-weight="600">s02 New</text>
<!-- Dispatch Mapping Outer Box -->
<rect x="195" y="268" width="300" height="200" rx="10" fill="#fff7ed" stroke="#d97706" stroke-width="2" stroke-dasharray="6,3"/>
<text x="345" y="290" fill="#92400e" font-size="12" font-weight="700" text-anchor="middle">TOOL_HANDLERS Dispatch Mapping</text>
<!-- Arrow into dispatch mapping -->
<line x1="345" y1="260" x2="345" y2="268" stroke="#d97706" stroke-width="1.5"/>
<!-- bash handler -->
<rect x="220" y="300" width="120" height="34" rx="6" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.2"/>
<text x="280" y="316" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">bash</text>
<text x="280" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_bash()</text>
<!-- read_file handler -->
<rect x="360" y="300" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="316" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">read_file</text>
<text x="420" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_read()</text>
<!-- write_file handler -->
<rect x="220" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="280" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">write_file</text>
<text x="280" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_write()</text>
<!-- edit_file handler -->
<rect x="360" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">edit_file</text>
<text x="420" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_edit()</text>
<!-- glob handler -->
<rect x="290" y="392" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="350" y="408" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">glob</text>
<text x="350" y="420" fill="#64748b" font-size="9" text-anchor="middle">→ run_glob()</text>
<!-- Arrow: Dispatch Mapping → Back to Messages -->
<path d="M 195 368 L 50 368 L 50 108 L 58 108" fill="none" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)" stroke-dasharray="6,3"/>
<text x="28" y="300" fill="#92400e" font-size="9" font-weight="500" transform="rotate(-90, 28, 300)">Append tool_result to messages</text>
<!-- ===== Legend ===== -->
<rect x="60" y="492" width="600" height="52" rx="6" fill="#f1f5f9"/>
<rect x="80" y="508" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="100" y="518" fill="#334155" font-size="10">s01 Preserved (loop, LLM, decision — completely unchanged)</text>
<rect x="380" y="508" width="12" height="12" rx="2" fill="#ecfdf5" stroke="#16a34a" stroke-width="1"/>
<text x="400" y="518" fill="#334155" font-size="10">s02 New (5 tools + dispatch mapping)</text>
<text x="80" y="536" fill="#64748b" font-size="10">Only 1 line changed in the loop: run_bash() → TOOL_HANDLERS[block.name]()</text>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 560" font-family="system-ui, -apple-system, sans-serif">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</marker>
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
</marker>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/>
<stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
</defs>
<!-- 背景 -->
<rect width="720" height="560" fill="#fafbfc" rx="8"/>
<!-- タイトル -->
<rect x="0" y="0" width="720" height="48" fill="url(#header)" rx="8"/>
<rect x="0" y="40" width="720" height="8" fill="url(#header)"/>
<text x="360" y="31" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Tool Use — ループ不変、ディスパッチマッピングを追加</text>
<!-- ===== s01 (灰色、保持部分) ===== -->
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s01 保持</text>
<!-- ユーザー入力 -->
<rect x="60" y="86" width="140" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="130" y="105" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">ユーザーの質問</text>
<text x="130" y="121" fill="#64748b" font-size="10" text-anchor="middle">messages[]</text>
<!-- 矢印:ユーザー → LLM -->
<line x1="200" y1="108" x2="268" y2="108" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<!-- LLM -->
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判定</text>
<!-- 矢印LLM → 判定 -->
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<!-- 判定ダイヤモンド -->
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
<!-- いいえ → 返却 -->
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="445" y="189" fill="#16a34a" font-size="9" font-weight="600">No</text>
<rect x="477" y="178" width="100" height="36" rx="18" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<text x="527" y="200" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">結果を返す</text>
<!-- はい → 次のステップ -->
<line x1="345" y1="226" x2="345" y2="260" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<text x="356" y="248" fill="#d97706" font-size="9" font-weight="600">Yes</text>
<!-- ===== s02 新規TOOL_HANDLERS ディスパッチマッピング ===== -->
<text x="505" y="282" fill="#d97706" font-size="11" font-weight="600">s02 新規</text>
<!-- ディスパッチマッピング外枠 -->
<rect x="195" y="268" width="300" height="200" rx="10" fill="#fff7ed" stroke="#d97706" stroke-width="2" stroke-dasharray="6,3"/>
<text x="345" y="290" fill="#92400e" font-size="12" font-weight="700" text-anchor="middle">TOOL_HANDLERS ディスパッチマッピング</text>
<!-- ディスパッチマッピングへの矢印 -->
<line x1="345" y1="260" x2="345" y2="268" stroke="#d97706" stroke-width="1.5"/>
<!-- bash handler -->
<rect x="220" y="300" width="120" height="34" rx="6" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.2"/>
<text x="280" y="316" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">bash</text>
<text x="280" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_bash()</text>
<!-- read_file handler -->
<rect x="360" y="300" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="316" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">read_file</text>
<text x="420" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_read()</text>
<!-- write_file handler -->
<rect x="220" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="280" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">write_file</text>
<text x="280" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_write()</text>
<!-- edit_file handler -->
<rect x="360" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">edit_file</text>
<text x="420" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_edit()</text>
<!-- glob handler -->
<rect x="290" y="392" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="350" y="408" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">glob</text>
<text x="350" y="420" fill="#64748b" font-size="9" text-anchor="middle">→ run_glob()</text>
<!-- 矢印:ディスパッチマッピング → メッセージリストに戻る -->
<path d="M 195 368 L 50 368 L 50 108 L 58 108" fill="none" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)" stroke-dasharray="6,3"/>
<text x="28" y="300" fill="#92400e" font-size="9" font-weight="500" transform="rotate(-90, 28, 300)">tool_result を messages に追加</text>
<!-- ===== 凡例 ===== -->
<rect x="60" y="492" width="600" height="52" rx="6" fill="#f1f5f9"/>
<rect x="80" y="508" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="100" y="518" fill="#334155" font-size="10">s01 保持ループ、LLM、判定 — 完全に不変)</text>
<rect x="380" y="508" width="12" height="12" rx="2" fill="#ecfdf5" stroke="#16a34a" stroke-width="1"/>
<text x="400" y="518" fill="#334155" font-size="10">s02 新規5 つのツール + ディスパッチマッピング)</text>
<text x="80" y="536" fill="#64748b" font-size="10">ループ内で変更されたのは 1 行だけrun_bash() → TOOL_HANDLERS[block.name]()</text>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -0,0 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 560" font-family="system-ui, -apple-system, sans-serif">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</marker>
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
</marker>
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3a5f"/>
<stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
</defs>
<!-- 背景 -->
<rect width="720" height="560" fill="#fafbfc" rx="8"/>
<!-- 标题 -->
<rect x="0" y="0" width="720" height="48" fill="url(#header)" rx="8"/>
<rect x="0" y="40" width="720" height="8" fill="url(#header)"/>
<text x="360" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Tool Use — 循环不变,只加分发映射</text>
<!-- ===== s01 (灰色,保留部分) ===== -->
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s01 保留</text>
<!-- 用户输入 -->
<rect x="60" y="86" width="140" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="130" y="105" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">用户提问</text>
<text x="130" y="121" fill="#64748b" font-size="10" text-anchor="middle">messages[]</text>
<!-- 箭头:用户 → LLM -->
<line x1="200" y1="108" x2="268" y2="108" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<!-- LLM -->
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">大模型 (LLM)</text>
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判断</text>
<!-- 箭头LLM → 判断 -->
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<!-- 判断菱形 -->
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
<!-- 否 → 返回 -->
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="445" y="189" fill="#16a34a" font-size="9" font-weight="600"></text>
<rect x="477" y="178" width="100" height="36" rx="18" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<text x="527" y="200" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">返回结果</text>
<!-- 是 → 下一步 -->
<line x1="345" y1="226" x2="345" y2="260" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<text x="356" y="248" fill="#d97706" font-size="9" font-weight="600"></text>
<!-- ===== s02 新增TOOL_HANDLERS 分发映射 ===== -->
<text x="505" y="282" fill="#d97706" font-size="11" font-weight="600">s02 新增</text>
<!-- 分发映射外框 -->
<rect x="195" y="268" width="300" height="200" rx="10" fill="#fff7ed" stroke="#d97706" stroke-width="2" stroke-dasharray="6,3"/>
<text x="345" y="290" fill="#92400e" font-size="12" font-weight="700" text-anchor="middle">TOOL_HANDLERS 分发映射</text>
<!-- 箭头进入分发映射 -->
<line x1="345" y1="260" x2="345" y2="268" stroke="#d97706" stroke-width="1.5"/>
<!-- bash handler -->
<rect x="220" y="300" width="120" height="34" rx="6" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.2"/>
<text x="280" y="316" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">bash</text>
<text x="280" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_bash()</text>
<!-- read_file handler -->
<rect x="360" y="300" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="316" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">read_file</text>
<text x="420" y="328" fill="#64748b" font-size="9" text-anchor="middle">→ run_read()</text>
<!-- write_file handler -->
<rect x="220" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="280" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">write_file</text>
<text x="280" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_write()</text>
<!-- edit_file handler -->
<rect x="360" y="346" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="420" y="362" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">edit_file</text>
<text x="420" y="374" fill="#64748b" font-size="9" text-anchor="middle">→ run_edit()</text>
<!-- glob handler -->
<rect x="290" y="392" width="120" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a" stroke-width="1.2"/>
<text x="350" y="408" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">glob</text>
<text x="350" y="420" fill="#64748b" font-size="9" text-anchor="middle">→ run_glob()</text>
<!-- 箭头:分发映射 → 回到消息列表 -->
<path d="M 195 368 L 50 368 L 50 108 L 58 108" fill="none" stroke="#d97706" stroke-width="1.5" marker-end="url(#arrow-orange)" stroke-dasharray="6,3"/>
<text x="28" y="300" fill="#92400e" font-size="9" font-weight="500" transform="rotate(-90, 28, 300)">tool_result 追加到 messages</text>
<!-- ===== 图例 ===== -->
<rect x="60" y="492" width="600" height="52" rx="6" fill="#f1f5f9"/>
<rect x="80" y="508" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="100" y="518" fill="#334155" font-size="10">s01 保留循环、LLM、判断——完全不变</text>
<rect x="380" y="508" width="12" height="12" rx="2" fill="#ecfdf5" stroke="#16a34a" stroke-width="1"/>
<text x="400" y="518" fill="#334155" font-size="10">s02 新增5 个工具 + 分发映射)</text>
<text x="80" y="536" fill="#64748b" font-size="10">循环里只改了 1 行run_bash() → TOOL_HANDLERS[block.name]()</text>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB