⭐ Lesson 02:工具使用
"加一个工具,只加一个 handler" — 循环不用动,新工具注册进 dispatch map 就行。
Harness 层:工具分发 — 扩展模型能触达的边界。
[[00-课程概览/综述|← 返回综述]] | [[00-课程概览/教学大纲|📋 教学大纲]]
学习目标
完成本节课后,你将能够:
- 理解 dispatch map 模式:
{tool_name: handler_function}替代 if/elif 链 - 实现路径沙箱
safe_path()防止工具逃逸工作区 - 在 Agent Loop 中注册多个工具(read / write / edit)
- 理解 加工具不需要改循环 这一核心原则
问题
[[01-智能体循环|Lesson 01 ⭐]] 的 Agent 只有一个工具:bash。所有操作都走 shell:
cat截断大文件时输出不可预测sed遇到特殊字符就崩- 每次 bash 调用都是不受约束的安全面
关键洞察:专用工具可以在工具层面做安全控制。read_file 可以加行数限制,write_file 可以做路径沙箱 — 这些安全逻辑不需要混进 bash。
但更重要的是另一个洞察:加工具不需要改循环。
解决方案:Dispatch Map
架构总览
+--------+ +-------+ +------------------+
| User | ---> | LLM | ---> | Tool Dispatch |
| prompt | | | | { |
+--------+ +---+---+ | bash: run_bash |
^ | read: run_read |
| | write: run_wr |
+-----------+ edit: run_edit |
tool_result | } |
+------------------+
The dispatch map is a dict: {tool_name: handler_function}.
One lookup replaces any if/elif chain.LLM 生成 tool_use 块时,携带 name(工具名)和 input(参数)。循环只需要按 name 查字典,然后调用 handler。查找 + 调用 = 两行代码。
核心模式
一个字典,一把搞定:
TOOL_HANDLERS = {
"bash": run_bash,
"read_file": run_read,
"write_file": run_write,
"edit_file": run_edit,
}模型在 response 中携带 tool_name,循环按名称查找处理函数。不需要 if/elif,不需要 switch/case。
这就是扩展点。加一个新工具 = 写一个 handler + 加一条 schema 定义。
路径沙箱
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每个文件操作工具都经过 safe_path() 校验。路径沙箱在工具层集中做,而不是在每次 bash 调用中分散做。
为什么工具放外面?
bash 关在沙箱里能做任何事,但模型需要为每一次操作支付推理 token。如果每次读文件都走 bash cat,LLM 要先生成 bash 命令,然后处理 shell 的输出格式。
专用工具的好处:
- 路径安全集中管控 — 所有文件操作统一经过
safe_path(),不依赖 shell 的通配符和转义 - 参数明确 —
read_file(path, limit)比bash cat file | head -n 10清晰得多 - token 节省 — 工具调用比生成 shell 命令更短
- 错误可预测 — 工具 handler 的返回值格式固定,LLM 容易解析
Handler 实例
def run_read(path: str, limit: int = None) -> str:
text = safe_path(path).read_text()
lines = text.splitlines()
if limit and limit < len(lines):
lines = lines[:limit]
return "\n".join(lines)[:50000]
def run_write(path: str, content: str) -> str:
safe_path(path).parent.mkdir(parents=True, exist_ok=True)
safe_path(path).write_text(content)
return f"Written {len(content)} bytes to {path}"
def run_edit(path: str, old_text: str, new_text: str) -> str:
text = safe_path(path).read_text()
if text.count(old_text) != 1:
return "Error: old_text not found or ambiguous"
text = text.replace(old_text, new_text)
safe_path(path).write_text(text)
return f"Edited {path}"Dispatch Map
TOOL_HANDLERS = {
"bash": lambda **kw: run_bash(kw["command"]),
"read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
"write_file": lambda **kw: run_write(kw["path"], kw["content"]),
"edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"],
kw["new_text"]),
}为什么用 lambda **kw?因为不同的工具需要不同的参数。lambda **kw 将整个 input dict 展开为关键字参数,handler 只取自己需要的字段。
工具 Schema 定义
除了 handler,每个工具还需要在 tool schema 中声明自己的接口。这个 schema 告诉 LLM:
- 这个工具叫什么名字
- 它接受什么参数(类型、是否必填)
- 它做什么用(description 影响模型选择策略)
TOOLS = [
{
"name": "read_file",
"description": "读取文件内容,支持行数限制",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"},
"limit": {"type": "integer", "description": "最大行数(可选)"},
},
"required": ["path"],
},
},
# ... bash, write_file, edit_file 类似
]schema 是模型和 harness 之间的契约。模型参考 description 决定用哪个工具,根据 properties 构造参数。所以 工具的 description 写得越清晰,模型选择就越准确。
Agent Loop 中的调用
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler \
else f"Unknown tool: {block.name}"
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})和 s01 的循环完全一致。 唯一的变化是,现在 TOOL_HANDLERS 字典里有更多条目,TOOLS 列表也更长。循环体本身一个字都没变。
错误处理模式
工具 handler 的返回值分为两种:
| 情况 | 策略 | 示例 |
|---|---|---|
| 成功 | 返回简短结果 | "Written 42 bytes to greet.py" |
| 失败 | 返回描述性错误 | "Error: path escapes workspace: ../../etc/passwd" |
Agent Loop 对工具返回值 不做判断,直接塞回消息队列。模型自己阅读结果,决定下一步是继续调用工具还是给出最终回答。
这又是一个 Harness 工程原则:让模型做判断,harness 只传递信息。
模型如何选择工具?
LLM 选择一个工具不是随机的,而是基于三个因素:
- 工具描述 —
description字段告诉模型这个工具适合什么场景。例如"读取文件内容"和"执行 shell 命令",模型会根据任务内容匹配 - 参数列表 — 工具需要的参数越匹配当前的上下文,模型越倾向于选择它
- 历史经验 — 如果之前的工具调用成功返回了预期结果,模型更可能继续使用同一个工具
这意味着 写清楚的 description 和 parameter description 直接决定了 Agent 的行为质量。 这不是文档问题,是架构问题。
# 好的描述 → 模型选择准确
{
"name": "bash",
"description": "Execute arbitrary shell commands. Use for compilation, git ops, testing, running scripts, and package management.",
"input_schema": { ... },
}
# 模糊的描述 → 模型困惑
{
"name": "bash",
"description": "run command",
"input_schema": { ... },
}Claude Code 中每个内置工具都有精心撰写的描述,LLM 根据这些描述在数百个工具中做选择。这就是为什么仔细打磨工具定义值得投入时间。
核心原则:循环属于 Agent,扩展属于 Harness
这是 Harness 工程最重要的原则之一:
| Agent Loop | Tool Handlers | |
|---|---|---|
| 职责 | 协调消息收发 | 执行具体操作 |
| 变化频率 | 几乎不变 | 频繁扩展 |
| 谁来改 | 核心框架 | 插件/技能开发者 |
| 错误处理 | 统一捕获异常 | 各自返回消息 |
Agent Loop 就是核心骨架。Tool Handlers 是可插拔的肌肉。骨架不随肌肉的增长而改变。
相对 s01 的变更
| 组件 | 之前 (s01) | 之后 (s02) |
|---|---|---|
| Tools | 1(仅 bash) | 4(bash, read, write, edit) |
| Dispatch | 硬编码 bash 调用 | TOOL_HANDLERS 字典 |
| 路径安全 | 无 | safe_path() 沙箱 |
| Agent loop | 不变 | 不变 |
练习
环境准备
cd learn-claude-code
pip install -r requirements.txt # 第一次需要运行
python agents/s02_tool_use.py尝试的 Prompt
英文 prompt 对 LLM 效果更好,但也可以用中文
Read the file requirements.txtCreate a file called greet.py with a greet(name) functionEdit greet.py to add a docstring to the functionRead greet.py to verify the edit worked
观察要点
- 模型在什么情况下选择
read_file而不是bash cat? - 当你让模型写出
tools列表时,它能看到哪些工具? TOOL_HANDLERS中没有的工具,模型还能不能调用?
挑战
如果让你加一个 delete_file 工具,需要改动几个地方?
答案:两个 — 写 handler 函数 + 注册到 TOOL_HANDLERS。循环不用碰。
⭐ 核心路径
本节课处于核心路径第二环:
s01 [智能体循环] → s02 [工具使用] → s03 [待办写入] → s04 [子智能体]
→ s05 [技能加载] → s07 [任务系统] → s09 [智能体团队][[01-智能体循环|← Lesson 01:智能体循环]] ⭐ | [[03-待办写入|Lesson 03:待办写入 →]] ⭐
和现实世界的关系
Claude Code 实际使用的工具数量远超 4 个 — 它内置了数十个工具(读取、写入、搜索、替换、grep、glob、LSP、终端、Web 搜索等),遵循的正是同一个 dispatch map 模式。
每当 Anthropic 发布新能力(如 WebSearch 工具、MCP 集成),本质是:
- 在
TOOL_HANDLERS里加一条条目 - 在
TOOLS列表里加一条 schema - 循环不用改
这就是 加一个工具,只加一个 handler 在现实中的体现。Claude Code 的 Agent Loop 和 s01/s02 的循环在核心逻辑上完全一致。
[[任务系统|Lesson 07 ⭐]] 将演示如何让 Agent 自己管理可用的工具集,[[智能体团队|Lesson 09 ⭐]] 则引入跨 Agent 的工具共享模式。
延伸阅读
- 《Anthropic 工具使用指南》— 了解工具描述如何影响模型选择
- [[术语表#Tool Dispatch|Tool Dispatch]] — Dispatch Map 的详细定义
- [[讨论课/工具安全边界|讨论课:工具安全边界]] — 路径沙箱之外的更多安全模式