跳转至

s02 - 工具调度与 Python 语法

s02 的核心思想

s01 的循环体完全没变。只是多加了工具 + 一个分发表。

s01: 1 个工具 (bash)  → 硬编码调用 run_bash
s02: 4 个工具          → 用字典 TOOL_HANDLERS 分发

Python 语法详解

1. 类型注解(p: str-> Path

def safe_path(p: str) -> Path:

纯给人看的提示,Python 运行时完全忽略:

部分 含义
p: str 参数 p 是字符串类型
-> Path 函数返回 Path 对象
# 这两个完全等价
def safe_path(p: str) -> Path:   # 有注解
def safe_path(p):                 # 没注解,一样跑

2. Path 的 / 不是除法,是路径拼接

WORKDIR = Path.cwd()          # Path 对象,不是字符串
path = (WORKDIR / p).resolve()

pathlib 库重载了 / 运算符,用来拼接路径:

Path("D:/proj") / "notes/a.md"
# → Path("D:/proj/notes/a.md")

# 等价于
os.path.join("D:/proj", "notes/a.md")

.resolve() 把路径绝对化,解析掉 ...

Path("D:/proj/../test").resolve()   # → Path("D:/test")

3. lambda 表达式(匿名函数)

lambda **kw: run_bash(kw["command"])

lambda 就是一行写完的匿名函数。等价于:

def 匿名函数(**kw):
    return run_bash(kw["command"])

语法lambda 参数: 返回值

示例对比:

# lambda 写法
f = lambda x: x * 2
f(5)    # → 10

# def 等价写法
def f(x):
    return x * 2
f(5)    # → 10

为什么这里用 lambda? 因为每个工具的处理逻辑就一行,没必要单独定义函数。


4. **kw(关键字参数打包)

lambda **kw: run_bash(kw["command"])

**kw 把所有传入的参数打包成一个字典:

def f(**kw):
    print(kw)

f(a=1, b=2)    # → {"a": 1, "b": 2}
f(name="test")  # → {"name": "test"}

反向操作是 **dict 拆开:

d = {"command": "ls"}
run_bash(**d)   # 等价于 run_bash(command="ls")

5. 默认参数值(limit: int = None

def run_read(path: str, limit: int = None) -> str:

limit = None 意味着调用时可以不传这个参数:

run_read("a.txt")           # limit 取默认值 None,读全部
run_read("a.txt", limit=10) # limit 取 10,只读前 10 行

6. kw.get("limit") vs kw["limit"]

lambda **kw: run_read(kw["path"], kw.get("limit"))
写法 key 不存在时 用在
kw["limit"] 报 KeyError 必填参数
kw.get("limit") 返回 None 可选参数

path 是必填的所以用 kw["path"]limit 可选所以用 kw.get("limit")


TOOL_HANDLERS 分发表详解

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"]),
}

本质:字典,键是工具名,值是处理函数。

调用时(第 126-127 行):

handler = TOOL_HANDLERS.get(block.name)          # 按名字找到对应函数
output = handler(**block.input)                  # 拆开参数字典传入
# 例:block.name = "bash", block.input = {"command": "ls"}
# → handler = lambda **kw: run_bash(kw["command"])
# → handler(command="ls") → run_bash("ls")

如果不用 lambda + **kw,等价写法:

def handle_bash(**kw):
    return run_bash(kw["command"])

def handle_read(**kw):
    return run_read(kw["path"], kw.get("limit"))

TOOL_HANDLERS = {
    "bash": handle_bash,
    "read_file": handle_read,
    ...
}

safe_path 安全检查

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("notes/a.md")
# → Path("D:/proj/notes/a.md")  ✓ 在工作目录内

# 恶意情况
safe_path("../../etc/passwd")
# → Path("C:/etc/passwd")
# → is_relative_to(WORKDIR) = False → 报错拦截!

s01 → s02 的变化对比

s01 s02
工具数量 1 (bash) 4 (bash/read/write/edit)
调用方式 硬编码 if block.type == "tool_use" TOOL_HANDLERS[block.name](**input)
循环体 不变 不变

s02 的教训:加工具不需要改循环,只需要 1) 写处理函数 2) 加到分发表 3) 加到 TOOLS 定义。


2026-05-01:SDK 是什么

问题

Anthropic SDK 是啥?跟 API 什么关系?

答案

SDK = Software Development Kit(软件开发工具包),就是别人写好的代码库,让你直接调用,不用从零写。

调用链

你的代码: client.messages.create(...)   ← 一行调用
    │
    ▼
Anthropic SDK: 把参数转成 HTTP 请求       ← SDK 干的事
    │
    ▼
网络: POST https://api.anthropic.com/...  ← 发到服务器
    │
    ▼
Claude 服务器: 推理,返回结果              ← API 干活

没有 SDK 的话,你得自己写 HTTP 请求:

import requests
response = requests.post(
    "https://api.anthropic.com/v1/messages",
    headers={"x-api-key": "...", "anthropic-version": "2023-06-01"},
    json={"model": "claude-sonnet-4-6", "messages": [...], "max_tokens": 8000}
)

SDK 把 HTTP 请求、认证、重试、错误处理这些琐碎细节封装好,给你干净的 client.messages.create() 用。

类比:SDK 是现成的工具箱,API 是工具箱里的工具,你不需要自己打铁造工具。


2026-05-01:subprocess 是什么

问题

subprocess 是啥?

答案

Python 标准库模块,作用:在 Python 代码里执行 shell 命令

r = subprocess.run(
    command,             # 要执行的命令,比如 "ls"
    shell=True,          # 通过 shell 执行(支持管道 |、重定向 >)
    cwd=os.getcwd(),     # 在哪个目录执行
    capture_output=True, # 抓取命令的输出
    text=True,           # 输出是字符串(不然是字节 bytes)
    timeout=120          # 超过 120 秒就杀掉
)

本质:Python → 开一个子进程 → 执行命令 → 把结果拿回来。

类比:你在终端里敲 ls 看结果,subprocess.run() 就是 Python 替你敲了这个命令,然后把终端显示的文字抓回 Python 变量里。


2026-05-01:any(d in command for d in dangerous) 拆解

问题

这行判断啥意思?

if any(d in command for d in dangerous):

答案

逐层拆解:

dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]

# 1. for d in dangerous → 遍历危险词列表
# 2. d in command      → 检查这个危险词是否出现在用户命令里
# 3. any(...)          → 只要有一个 True,整体就是 True

手动等价写法:

def 手动版(command):
    for d in dangerous:       # 逐个检查
        if d in command:      # 出现在命令里?
            return True       # 中一个就立刻 True(短路)
    return False              # 全没中,False

any() 就是把这个循环压缩成一行,短路逻辑:一旦找到匹配就立即返回 True,后面不再检查。


Lang/Python #Domain/Agent #Type/原理 #Session/s02