Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ MODEL_ID=claude-sonnet-4-6
# MODEL_ID=MiniMax-M3
# MODEL_ID=MiniMax-M2.7
# MODEL_ID=MiniMax-M2.7-highspeed
# Image generation tool (optional; s02)
# MINIMAX_API_KEY=sk-xxx
# MINIMAX_API_HOST=https://api.minimax.io

# GLM (Zhipu) https://z.ai
# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
Expand All @@ -53,6 +56,9 @@ MODEL_ID=claude-sonnet-4-6
# MODEL_ID=MiniMax-M3
# MODEL_ID=MiniMax-M2.7
# MODEL_ID=MiniMax-M2.7-highspeed
# Image generation tool (optional; s02)
# MINIMAX_API_KEY=sk-xxx
# MINIMAX_API_HOST=https://api.minimaxi.com

# GLM (Zhipu) https://open.bigmodel.cn
# ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic
Expand Down
21 changes: 17 additions & 4 deletions s02_tool_use/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Adding a tool to the Agent requires just two things:

---

## From 1 Tool to 5 Tools
## From 1 Tool to 6 Tools

s01 had only bash:

Expand All @@ -40,7 +40,7 @@ TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```

s02 expands to 5 tools, each independently defined:
s02 expands to 6 tools, each independently defined:

```python
TOOLS = [
Expand All @@ -49,6 +49,7 @@ TOOLS = [
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
{"name": "generate_image", "description": "Generate an image from a text prompt.", ...},
]
```

Expand All @@ -75,6 +76,9 @@ def run_edit(path, old_text, new_text):
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))

def run_generate_image(prompt, aspect_ratio="1:1"):
...
```

---
Expand All @@ -88,6 +92,7 @@ TOOL_HANDLERS = {
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
"generate_image": run_generate_image,
}

# Only one line changed in the loop — from hardcoded run_bash to dispatch lookup:
Expand Down Expand Up @@ -125,7 +130,7 @@ The teaching version executes them one by one in the original `response.content`

| Component | Before (s01) | After (s02) |
|-----------|-------------|-------------|
| Tool count | 1 (bash) | 5 (+read, write, edit, glob) |
| Tool count | 1 (bash) | 6 (+read, write, edit, glob, generate_image) |
| 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 |
Expand All @@ -139,20 +144,28 @@ cd learn-claude-code
python s02_tool_use/code.py
```

The optional `generate_image` tool also needs these values in `.env`. Use `https://api.minimaxi.com` as the host for China mainland.

```sh
MINIMAX_API_KEY=sk-xxx
MINIMAX_API_HOST=https://api.minimax.io
```

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`
5. `Generate a 16:9 image of a coding workspace at sunrise`

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.
The Agent now has 6 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?

Expand Down
21 changes: 17 additions & 4 deletions s02_tool_use/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Agent にツールを追加するには、たった二つ:

---

## 1 つのツールから 5 つのツールへ
## From 1 Tool to 6 Tools

s01 には bash だけだった:

Expand All @@ -40,7 +40,7 @@ TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```

s02 では 5 つに増え、各ツールは独立して定義される:
s02 expands to 6 tools, each independently defined:

```python
TOOLS = [
Expand All @@ -49,6 +49,7 @@ TOOLS = [
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
{"name": "generate_image", "description": "Generate an image from a text prompt.", ...},
]
```

Expand All @@ -75,6 +76,9 @@ def run_edit(path, old_text, new_text):
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))

def run_generate_image(prompt, aspect_ratio="1:1"):
...
```

---
Expand All @@ -88,6 +92,7 @@ TOOL_HANDLERS = {
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
"generate_image": run_generate_image,
}

# ループ内で変更されたのは一行だけ — ハードコードの run_bash から検索ディスパッチへ:
Expand Down Expand Up @@ -125,7 +130,7 @@ for block in response.content:

| コンポーネント | 変更前 (s01) | 変更後 (s02) |
|--------------|-------------|-------------|
| ツール数 | 1 (bash) | 5 (+read, write, edit, glob) |
| Tool count | 1 (bash) | 6 (+read, write, edit, glob, generate_image) |
| ツール実行 | ハードコード `run_bash()` | TOOL_HANDLERS 検索ディスパッチ |
| パス安全性 | なし | safe_path 検証(file tools のみ) |
| ループ | `while True` + `stop_reason` | s01 と完全に同一 |
Expand All @@ -139,20 +144,28 @@ cd learn-claude-code
python s02_tool_use/code.py
```

The optional `generate_image` tool also needs these values in `.env`. Use `https://api.minimaxi.com` as the host for China mainland.

```sh
MINIMAX_API_KEY=sk-xxx
MINIMAX_API_HOST=https://api.minimax.io
```

以下のプロンプトを試してみよう:

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`
5. `Generate a 16:9 image of a coding workspace at sunrise`

観察のポイント:モデルがツールを一つだけ呼び出すときと、複数同時に呼び出すときの違い。複数のツール呼び出しは正しい順序で実行されているか?

---

## 次へ

Agent は 5 つの専用ツールを持つようになった。file tools`safe_path` で保護されるが、bash は制限なし — `rm -rf /` はまだ実行できる。
The Agent now has 6 specialized tools. File tools are protected by `safe_path`, but bash remains unrestricted: `rm -rf /` still runs.

→ s03 Permission:ツール実行前にゲートを追加 — この操作は安全か? ユーザーの承認が必要か?

Expand Down
21 changes: 17 additions & 4 deletions s02_tool_use/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ s01 的循环完全保留(LLM 调用、stop_reason 判断、消息追加)。

---

## 1 个工具到 5 个工具
## From 1 Tool to 6 Tools

s01 只有一个 bash:

Expand All @@ -40,7 +40,7 @@ TOOLS = [{"name": "bash", ...}]
def run_bash(command): ...
```

s02 加到 5 个,每个工具都是独立定义:
s02 expands to 6 tools, each independently defined:

```python
TOOLS = [
Expand All @@ -49,6 +49,7 @@ TOOLS = [
{"name": "write_file", "description": "Write content to file.", ...},
{"name": "edit_file", "description": "Replace text in file once.", ...},
{"name": "glob", "description": "Find files by pattern.", ...},
{"name": "generate_image", "description": "Generate an image from a text prompt.", ...},
]
```

Expand All @@ -75,6 +76,9 @@ def run_edit(path, old_text, new_text):
def run_glob(pattern):
import glob as g
return "\n".join(g.glob(pattern, root_dir=WORKDIR))

def run_generate_image(prompt, aspect_ratio="1:1"):
...
```

---
Expand All @@ -88,6 +92,7 @@ TOOL_HANDLERS = {
"write_file": run_write,
"edit_file": run_edit,
"glob": run_glob,
"generate_image": run_generate_image,
}

# 循环里只改了一行——从硬编码 run_bash 变成查表:
Expand Down Expand Up @@ -125,7 +130,7 @@ for block in response.content:

| 组件 | 之前 (s01) | 之后 (s02) |
|------|-----------|-----------|
| 工具数量 | 1 (bash) | 5 (+read, write, edit, glob) |
| Tool count | 1 (bash) | 6 (+read, write, edit, glob, generate_image) |
| 工具执行 | 硬编码 `run_bash()` | TOOL_HANDLERS 查表分发 |
| 路径安全 | 无 | safe_path 校验(仅 file tools) |
| 循环 | `while True` + `stop_reason` | 与 s01 完全一致 |
Expand All @@ -139,20 +144,28 @@ cd learn-claude-code
python s02_tool_use/code.py
```

The optional `generate_image` tool also needs these values in `.env`. Use `https://api.minimaxi.com` as the host for China mainland.

```sh
MINIMAX_API_KEY=sk-xxx
MINIMAX_API_HOST=https://api.minimax.io
```

试试这些 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`
5. `Generate a 16:9 image of a coding workspace at sunrise`

观察重点:模型什么时候只调一个工具,什么时候一次调多个?多个工具调用的顺序和结果是否正确?

---

## 接下来

现在 Agent 有 5 个专用工具。file tools`safe_path` 保护,但 bash 不受限制,`rm -rf /` 还是能跑。
The Agent now has 6 specialized tools. File tools are protected by `safe_path`, but bash remains unrestricted: `rm -rf /` still runs.

s03 Permission → 在工具执行之前加一道门:这个操作安全吗?需要用户批准吗?

Expand Down
52 changes: 45 additions & 7 deletions s02_tool_use/code.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
#!/usr/bin/env python3
"""
s02: Tool Use — 在 s01 基础上新增 4 个工具 + 分发映射。
s02: Tool Use - adds five tools and a dispatch map on top of s01.

运行: python s02_tool_use/code.py
需要: pip install anthropic python-dotenv + .env 中配置 ANTHROPIC_API_KEY

本文件 = s01 的全部代码 + 以下新增:
+ run_read / run_write / run_edit / run_glob 四个工具实现
+ Five tool implementations: run_read / run_write / run_edit / run_glob / run_generate_image
+ TOOL_HANDLERS 分发映射(替代 s01 中硬编码的 run_bash 调用)
+ safe_path 路径安全校验

循环本身(agent_loop)与 s01 完全一致。
"""

import os, subprocess
import os, subprocess, json, urllib.request
from pathlib import Path

try:
Expand Down Expand Up @@ -60,7 +60,7 @@ def run_bash(command: str) -> str:


# ═══════════════════════════════════════════════════════════
# NEW in s02: 4 个新工具
# NEW in s02: 5 tools
# ═══════════════════════════════════════════════════════════

def safe_path(p: str) -> Path:
Expand Down Expand Up @@ -114,8 +114,44 @@ def run_glob(pattern: str) -> str:
return f"Error: {e}"


def run_generate_image(prompt: str, aspect_ratio: str = "1:1") -> str:
"""Generate an image with MiniMax and return the image URL."""
api_key = os.getenv("MINIMAX_API_KEY")
if not api_key:
return "Error: set MINIMAX_API_KEY to use image generation"
api_host = (os.getenv("MINIMAX_API_HOST") or "https://api.minimax.io").rstrip("/")

payload = {
"model": "image-01",
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"n": 1,
"response_format": "url",
}
req = urllib.request.Request(
f"{api_host}/v1/image_generation",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
data = json.loads(resp.read().decode("utf-8"))
status = data.get("base_resp", {}).get("status_code")
if status not in (None, 0):
msg = data.get("base_resp", {}).get("status_msg", "unknown error")
return f"Error: MiniMax image generation failed ({status}): {msg}"
urls = data.get("data", {}).get("image_urls") or []
return urls[0] if urls else f"Error: no image URL returned: {data}"
except Exception as e:
return f"Error: {e}"


# ═══════════════════════════════════════════════════════════
# NEW in s02: 工具定义(s01 只有一个 bash,现在扩展到 5 个)
# NEW in s02: tool definitions (s01 has only bash; s02 has 6 tools)
# ═══════════════════════════════════════════════════════════

TOOLS = [
Expand All @@ -129,6 +165,8 @@ def run_glob(pattern: str) -> str:
"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"]}},
{"name": "generate_image", "description": "Generate an image from a text prompt using MiniMax.",
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string", "maxLength": 1500}, "aspect_ratio": {"type": "string", "default": "1:1", "enum": ["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]}}, "required": ["prompt"]}},
]

# ═══════════════════════════════════════════════════════════
Expand All @@ -137,7 +175,7 @@ def run_glob(pattern: str) -> str:

TOOL_HANDLERS = {
"bash": run_bash, "read_file": run_read, "write_file": run_write,
"edit_file": run_edit, "glob": run_glob,
"edit_file": run_edit, "glob": run_glob, "generate_image": run_generate_image,
}


Expand Down Expand Up @@ -171,7 +209,7 @@ def agent_loop(messages: list):


if __name__ == "__main__":
print("s02: Tool Use — 在 s01 基础上加了 4 个工具")
print("s02: Tool Use - adds 5 tools on top of s01")
print("输入问题,回车发送。输入 q 退出。\n")

history = []
Expand Down
Loading