跳转到内容

第 25 节 · 工具的设计哲学:4 个关键选择

一句话回答

工具的设计不是"实现功能"就完了,而是"帮 LLM 不犯错"。 今天讲 4 个最关键的设计选择,每一个都对应一类典型的 LLM 翻车场景。

选择 ① edit 用 old_content / new_content,不用行号

edit 为什么用 old new 不用行号

翻车场景

假设 edit 接收 line_number 参数:

python
# 第 1 步:在第 5 行前面插入新函数(3 行)
edit_at_line("main.py", line=5, mode="insert_before", content="...")
# → 此时第 12 行 = 原文件的第 9 行

# 第 2 步:修改"原本的第 12 行"
edit_at_line("main.py", line=12, mode="replace", content="...")
# → 但 LLM 不知道行号偏移了,改错了位置

解法

接收 old_content / new_content按内容定位

python
edit("main.py",
     old_content="def greet():\n    print('hi')",
     new_content="def greet():\n    print('hello')")

不管前面增删多少行,目标文本永远精确。

唯一性约束

old_content 必须在文件中只出现一次。这是为什么?

old_content = "return"

文件里 30 个 return——LLM 不知道改哪个。强制要求唯一就能逼模型加足够的上下文。

old_content = "def add(a, b):\n    return a + b"

这就唯一了。

这个约束是 Cursor / Claude Code 的 edit 工具几乎一致的设计。

old/new 不是唯一答案——2026 年三种主流编辑设计

old/new(Cursor / Claude Code / 我们)apply_patch V4A diff(OpenAI Codex)hashline / anchor(实验性)
怎么定位按文本片段(要求唯一)按差异块的前后文(contextual diff)按「行号 + 内容哈希」双重校验
优点实现最简、模型最熟、错误最直观一次能改多处、token 紧凑抗「模型记错内容」、抗「文件被改过」
缺点多处一样的代码要 LLM 自己加上下文patch 解析器要写得稳,错位时报错难懂要求 read 输出带哈希,工具更复杂

学完 Day5 的 old/new,再去看 OpenAI 的 apply_patch 文档 就能秒懂——本质都是「按内容定位、不按行号定位」。

选择 ② bash 工具的四道安全闸门

bash 工具的四道安全闸门

bash 是最强大也最危险的工具——能跑任意命令。LLM 一不小心就能把你仓库删了

四道闸门是底线:

闸门长什么样为什么
超时timeout=30LLM 偶尔会写 while True,没超时直接挂死
输出截断max_output=50000一次 find / 几百万行返回会爆 messages
工作目录限定cwd=workdir防止跑到 ~/etc 那种地方动东西
危险命令拦截黑名单 regexrm -rf /sudo、远程脚本下载、fork 炸弹

黑名单不是终极方案

我们的 bash.py 里有这样一段:

python
DANGER_PATTERNS = [
    r"\brm\s+-rf\s+/",
    r"\bsudo\b",
    r"\b(curl|wget)\s+.*\|\s*(sh|bash)",
    ...
]

这是最低限度。真正生产级的方案是:

  • 容器化(Docker / Firejail)—— Agent 跑在隔离的小盒子里
  • 命令白名单(只允许 python / pytest / git 等明确命令)
  • 文件系统 read-only 挂载(除了 workdir)

今天我们做最低版,让你看到"边界在哪"。

2026 年的工业沙箱实践

2025-2026 年发生过几起标志性事件,让「黑名单不够用」成为业界共识:Claude Code 多次被报出 rm -rf 误删用户文件、有研究员演示了通过 /proc/self/root/... 路径绕过 denylist。主流方案演进成三层防御

  1. 细粒度权限:Claude Code 的 settings.json 把每个工具拆成 allow / deny / ask 三档(例如 Bash(npm run *) 自动放行、Bash(rm -rf *) 直接拒绝、Bash(git push *) 必须人工确认),比黑名单 regex 精细得多
  2. 沙箱隔离:Cursor 3 的 Background Agents 跑在 isolated VM 里、Claude Code 推荐 Docker 容器(2026.01 上线 Docker Sandboxes,每个用独立 microVM)、OpenAI Codex 直接跑在云端
  3. 行为分类器:Claude Code 2026.03 的 「Auto Mode」用一个独立模型实时判断「动作越权 / 提示注入 / 触碰未知系统」,比静态黑名单灵活

底线:纯黑名单只能挡住「教科书级」翻车,生产环境一定要加沙箱。我们今天做的是教学版黑名单,目的是让你看到「边界在哪、为什么不够」。

选择 ③ 读类工具大胆调,写类工具严防死守

读类工具大胆调 写类工具严防死守

工具分两类,对它们的设计心态完全不同

读类(read / list_dir / glob / grep)写类(write / edit / bash)
失败成本低(重试就行)高(可能写坏文件 / 删错东西)
副作用有(文件系统改了)
模型策略LLM 可以"试错"每次必须有明确意图
设计心态友好 / 容错 / 信息丰富严格 / 校验 / 可回滚

读类工具的设计要点

  • 路径不存在 → 友好报错(不要 raise,让模型看到"文件不存在: xxx" 自己处理)
  • 大文件 → 自动截断(默认 100KB,避免一次塞满 messages)
  • 输出带行号(让 LLM 后续能精确引用)

写类工具的设计要点

  • write: 默认拒绝覆盖已有文件(要 overwrite=True 才能覆盖)
  • edit: 强制 old_content 唯一
  • bash: 4 道闸门
  • 所有写类工具: 失败时 不要 raise,把错误转成字符串返回 —— 让 LLM 能看到错误自己重试

选择 ④ 工具失败 → 字符串返回,不抛异常

python
def edit(path, old_content, new_content):
    if not os.path.isfile(path):
        return f"❌ 文件不存在: {path}"   # ← 不 raise
    if count == 0:
        return f"❌ 未找到匹配内容..."    # ← 不 raise
    ...

为什么

如果 raise,整个 Agent Loop 就崩了。但如果返回字符串:

LLM edit("a.py", "old", "new")

工具返回:"❌ 未找到匹配内容: a.py"

LLM 看到错误 "啊我搞错了,先 read 看看再来" 自己纠正

让模型自己看到错误,自己决定下一步。这是 Day2 第 8 节就讲过的"工具异常兜底"原则——Day5 的所有工具一律遵守。

错误消息的「三段式」格式

光「返回字符串」还不够——错误消息要让模型有足够信息恢复。Anthropic、OpenAI 工程团队总结的最佳实践是 三段式

 <出了什么问> what went wrong
   <预期是什么>         what was expected
   <一个修复示例 / 下一步建> example of correct usage

对照看 edit 的两个错误消息:

场景不及格及格
0 处匹配Error: not found❌ 未找到匹配内容。请用 read 重新看一下当前内容,再调一次 edit。
多处匹配Error: ambiguous❌ old_content 匹配了 3 处,请加更多上下文使其唯一(如包含函数签名、前后空行)。

一句话:错误消息是给 LLM 的「恢复说明书」。

把这 4 条总结成一句话

工具的输出是给 LLM 看的 UI——参数怎么命名、输出什么格式、怎么报错,全部都是「给 LLM 看」,不是给人看。

Anthropic 工程团队把这件事提到了「投入和 HCI 一样多的设计精力」的高度,并给出 3 条最有杠杆的建议(Writing effective tools for AI agents,2025):

  1. 参数名要无歧义user 改成 user_idtime 改成 start_iso8601——名字本身就是文档
  2. 零上下文心态:写 description 时假装「给一个新来的实习生看」,不要默认 LLM 知道你的内部约定
  3. 工具是契约:deterministic 程序和 non-deterministic agent 之间的契约——工具自己要 robust to error,不要把校验责任甩给 LLM

一些常见的"误用工具"案例

误用现象怎么防
LLM 用 write 覆盖已有文件整个文件被替换write 默认拒绝覆盖
LLM 用 edit 改文件,但 old_content 写错静默失败唯一性约束 + 友好报错
LLM 用 bash 跑 find /几百万行返回爆 messages输出截断 + 工作目录限定
LLM 反复同样调用同一工具死循环Day3 已经讲了:max_iters + 重复检测
工具 description 写得太简略LLM 不知道何时调、参数怎么填按「零上下文」心态写 description + 给 1-2 个示例

设计思路总结

写好 Coding Agent 的工具,3 件事:

  1. 默认安全:写类工具默认拒绝危险操作(覆盖、删除、远程执行)
  2. 失败可读:错误转字符串返回,含具体原因 + 修复建议
  3. 输出友好:带行号 / 含状态码 / 含元数据,让下一个工具的 LLM 调用方便

跟 full-agent 的工具集对照看,examples/full-agent/tools/impl/ 全部 9 个工具都遵守这 3 条。

动手试试

bash
cd labs/05-coding-agent-tools
python demo_25_design_philosophy.py

源码:demo_25_design_philosophy.py

会跑 3 个对比演示:edit 唯一性约束、行号偏移问题、bash 危险命令拦截。

小结

设计选择防什么翻车
edit 用 old/new防多步编辑后行号偏移
edit 唯一性约束防一次修改改了不该改的多处
bash 4 道闸门 + 沙箱防仓库被删 / 输出爆 / 死循环
失败返回字符串防 Agent Loop 崩溃,让模型自己纠错
错误消息三段式让 LLM 能从错误中恢复
参数无歧义命名让模型不用猜、少出错

理论结束。下面 lab:

  • Part 1(60min):实现 read / list_dir(读类,失败成本低,grep 选做)
  • Part 2(60min):实现 write / edit / bash(写类,严防死守)
  • 末尾 5min:Git 速通 5 个命令把代码推到 CNB

Released under the MIT License.