循環沒變,只是工具變多了
「循環一點沒改,我只是往 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 課程會補上一個「權限層」把這些決定從程式碼裡拉出來,做成聲明式的。