循环没变,只是工具变多了
「循环一点没改,我只是往 TOOLS 数组里加了东西。」——s02_tool_use.py 原话。
加一个工具要动哪几处?
S01 的 agent 只会 bash。要让它也能 read_file / write_file / edit_file,你会怎么改?
很多人第一反应是:改 loop。错。loop 一行都不用动。只需要三件事:
- 写一个 Python handler 函数(
run_read(path, limit))。 - 把它注册到
TOOL_HANDLERS映射表("read_file": lambda **kw: run_read(...))。 - 在
TOOLS数组里加一条 JSON schema 声明(告诉模型这个工具叫什么、接受什么参数)。
循环看到一个 tool_use 块,就按 block.name 到 dispatch map 里查函数、执行、把输出塞回 tool_result。跟 bash 走的是完全一样的路。
# Dispatch map: name → handler lambda 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"]), }
看 dispatch 怎么路由
下面这个 widget 让你点一个模型可能发来的 tool_use 请求,看它如何被路由到具体的 Python 函数。注意:block.name 决定走哪条线。
safe_path:一条不能省的防线
给 agent 文件访问权限,安全上最容易翻车的是路径逃逸:模型本来该在 /home/user/project/ 里工作,结果它发了个 read_file("../../etc/passwd")。
s02 里有一个小函数专治这个:
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
关键在 .resolve() + .is_relative_to() 这两步——解析到绝对路径再检查是否仍在沙箱内。少了前者,foo/../../etc 这种能穿过去;少了后者,连检查都没做。
辨别路径安全性
下面 5 个路径都是模型可能发来的 read_file 参数。哪些会被 safe_path 放行,哪些会被拒?假设 WORKDIR = /home/user/project。
不要在 agent 里加危险工具
加工具简单,但要记住:你加的每一个工具都是模型的新能力边界。s02 里 bash 已经有黑名单(rm -rf /、sudo、shutdown),write_file 有 safe_path 限制。往生产里加工具前问自己三个问题:
- 这个工具能让模型做出不可逆的事吗?(rm、发邮件、git push)
- 它能泄漏什么?(env vars、.ssh、cookies)
- 错误输出会不会被当作指令回灌?(prompt injection via tool output)
s07 课会补上一个「权限层」把这些决定从代码里拉出来,做成声明式的。