引言
上一篇我们讲了 Agent Loop 和 Harness 工程------Agent 有了一个"身体框架",可以持续运转。但如果你仔细想想,一个只能"说话"的 Agent 其实什么都做不了。它能告诉你"你应该把第 42 行的 bug 修掉",但它自己改不了那一行代码。
这就像一个被绑住双手的专家:脑子清楚得很,但你让他修电脑,他只能嘴上指挥。
工具系统就是给 Agent 松绑的那把钥匙。

工具调用的本质:翻译官模式
很多人一听"AI 调用工具",脑子里浮现的是某种复杂的插件系统。其实本质简单得令人发指:
- 模型在回复中说:"我要用
read_file工具,参数是path: main.py" - 你的代码(Harness)看到这句话,去真的读了
main.py的内容 - 把文件内容作为"工具结果"喂回给模型
- 模型拿到内容,继续思考下一步
就这么简单。模型本身并不能真的读文件------它只是"表达了读文件的意图",是你写的代码帮它执行了这个意图。
类比一下:模型是一个只会说英语的老板,工具系统是翻译官。老板说"Read that document",翻译官跑去把文件拿来、翻译好、递回去。老板从头到尾没碰过那份文件。
Claude Code 的核心工具箱
Claude Code 给模型配备了一套精心设计的工具,每一个都对应开发者日常的一个基本操作:
| 工具 | 做什么 | 类比 |
|---|---|---|
bash |
执行 shell 命令 | 开发者的终端 |
read_file |
读取文件内容 | 打开文件看一眼 |
write_file |
写入文件 | 新建或覆盖一个文件 |
edit_file |
精确替换文件中的文本 | 用编辑器改几行代码 |
glob |
按模式搜索文件名 | find . -name "*.py" |
grep |
搜索文件内容 | 在代码里搜关键字 |
注意这些工具的特点:每一个都很小、很具体、只做一件事。没有一个"万能工具"可以同时读写文件还能跑命令。这是有意为之的,后面会讲为什么。
调度表模式:优雅的路由机制
工具定义好了,Agent Loop 收到模型的工具调用请求后,怎么知道该执行哪个函数?
最朴素的做法是 if-else:
python
if tool_name == "bash":
result = run_bash(args)
elif tool_name == "read_file":
result = run_read(args)
elif tool_name == "write_file":
result = run_write(args)
# ... 继续堆叠
能跑,但丑。每加一个工具就要多写一个分支,代码越来越长,改起来还容易漏。
项目中用的是调度表模式(dispatch map)------一个字典,把工具名映射到处理函数:
python
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"]),
}
调用的时候一行搞定:
python
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
新增工具?往字典里加一行。删工具?删一行。工具的定义和调度完全解耦。
这个模式在 Web 框架的路由表、命令行工具的子命令分发、甚至游戏引擎的事件系统里都能看到。本质上就是用数据结构代替控制结构------用一张表代替一堆 if-else。

打开项目中的 s02_tool_use.py,你会看到 Agent Loop 的代码和 s01 几乎一模一样------唯一的变化就是工具数组从一个变成了四个,加了一张调度表。循环没变,只是手变多了。 这就是好架构的力量:扩展能力不需要修改核心逻辑。
安全边界:有手,不能乱摸
给 Agent 工具就像给实习生权限------你希望他能干活,但不希望他把生产数据库删了。
项目中实现了两层安全防护:
第一层:危险命令黑名单
python
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
这是最粗暴但最有效的防线。模型如果尝试执行 rm -rf /,直接拦截,返回错误。
第二层:路径逃逸检查
python
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
这一层更精细。模型传入的文件路径,先解析成绝对路径(处理掉 ../ 之类的相对路径),然后检查它是否还在工作目录内。如果模型试图读 /etc/passwd 或者用 ../../ 跳出项目目录,直接报错。
类比一下:第一层是门口保安,看到可疑人物直接拦;第二层是门禁系统,只允许刷卡进入自己有权限的楼层。
实际的 Claude Code 产品里,安全机制远比这复杂------有权限分级、用户确认弹窗、沙箱执行等。但核心思路是一样的:默认不信任模型的操作意图,每一步都校验。
工具设计的三个原则
回头看这套工具体系,有三个设计原则值得记住:
原则一:原子化------一个工具做一件事
read_file 只读文件,write_file 只写文件。不会有一个 file_manager 工具同时承担读写删改搜索五种功能。
为什么?因为模型在选择工具时依赖的是工具描述。一个功能明确的工具,模型很容易理解什么时候该用它。一个瑞士军刀式的工具,模型反而容易选错参数、用错模式。
原则二:可组合------工具之间可以串联
模型可以先用 glob 找到所有 .py 文件,再用 read_file 逐个读取,再用 edit_file 修改其中需要改的。这种组合不是你提前编排好的------是模型自己决定的调用顺序。
原子化的工具天然支持组合。就像 Unix 哲学里的管道:ls | grep | sort,每个命令只做一件事,但组合起来威力巨大。
原则三:描述清晰------模型靠描述理解工具
模型不会看你的 Python 代码来理解工具怎么用。它看的是你在工具定义里写的 description 和参数的 input_schema。
python
{"name": "edit_file",
"description": "Replace exact text in file.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"old_text": {"type": "string"},
"new_text": {"type": "string"}
},
"required": ["path", "old_text", "new_text"]
}}
这段描述就是工具的"使用说明书"。写得好,模型用得准;写得含糊,模型会误用。在实际项目中,工具描述的打磨往往比工具实现本身花的时间还多。

对应代码:s01 与 s02
在项目的 agents/ 目录下:
s01_agent_loop.py:只有一个bash工具的最简 Agent。展示了核心循环。s02_tool_use.py:扩展到四个工具(bash、read_file、write_file、edit_file),引入了调度表模式。
对比这两个文件,你会发现核心的 agent_loop 函数几乎没变------变化全在工具定义和调度表上。这不是巧合,这是架构设计的目标:开闭原则的活教材。对扩展开放(加工具),对修改关闭(不动循环)。
小结
工具系统把 Agent 从"只能说"变成了"能做事"。调度表模式让工具扩展变得轻而易举,安全边界确保 Agent 不会越权操作,而原子化 + 可组合 + 描述清晰的设计原则保证了工具体系的可维护性和可扩展性。
但有了手还不够。你给一个人一堆工具,他如果没有计划、没有条理,干着干着就会乱套------读了一堆文件但忘了为什么要读,改了一处代码但忘了还有三处要改。
下一篇,我们聊聊怎么给 Agent 装一个"大脑"------规划能力和子代理机制。