让模型写代码来调工具,行不行?
本课一问:每一次工具调用都要让模型回一次:模型写调用 → harness 执行 → 结果放进下一轮请求 → 模型再写。能不能让模型一次写一段脚本,在脚本里连着调好几个工具?
这一课先不碰:脚本的运行环境怎么隔离到操作系统层(A4 沙箱);脚本调用的每个工具该不该放行(A3)。
从已有出发
第 4 课算过工具定义的账,岔路课 B1 回放过往返。把两者合起来看贯穿任务的真实运行 pd-truncate-s1:7 次模型调用,全程 21,112 个 token,模型生成一共用了约 251 秒。每一次往返,都要把越来越长的历史重发一遍。
先自己设计:每一步都回模型
现在的做法就是朴素方案:模型每调一次工具,都要等结果回来、再发一次完整请求,才能决定下一步。
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
在下面的列表里,每一行是一次真实的模型调用和它发起的工具。
模型写一段程序,例如先列目录、再跑测试、再读失败测试指向的文件,一次交给 harness 执行,只把最后的结果交回模型。合并掉的模型调用,连同它们的输入输出 token,都省了。
数据:本课项目 miniagent 的真实运行 pd-truncate-s1(A5 预算 2400 token,朴素截断(seed 1),2026-09-26;模型 Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数用该模型的 tokenizer 与 chat template 重算,逐轮与服务端 usage.prompt_tokens 核对一致;「服务端缓存」是 mlx_lm.server 自报的 cached_tokens,它的前缀缓存跨请求保留,读数请看项目 README 的说明。
试三件事:① 只勾第 1、2 轮之间和第 2、3 轮之间:模型调用从 7 次变 5 次,省下的 token 就是第 2、3 轮的真实用量;② 再试着勾第 4、5 轮之间:第 4 轮的 edit 报了「2 处」,模型要看到这句报错才知道第 5 轮该怎么改——合并就意味着它得事先写好「报错了怎么办」;③ 全部勾上,看数字上限,再想想这是不是一个模型写得出来的脚本。
改进:两家的实现
Codex 给模型的是两个工具:exec 与 wait,名字在 code-mode-protocol/src/lib.rs:49-50;脚本跑在内嵌的 V8 里,code-mode-runtime/Cargo.toml:24 引入 v8 时打开了 v8_enable_sandbox 特性。
OpenCode 的 packages/codemode 自己写了一个树遍历解释器,只执行一个 JavaScript 子集。它的设计文档把边界写得很清楚:脚本只能调用 host 提供的工具,没有文件系统、进程、网络等「环境权限」;不支持 import、eval。
被关起来的是脚本本身:它不能自己读文件、开进程、上网。可脚本调用的工具照样有全部能力。OpenCode 的设计文档原话是「CodeMode can only confine programs to supplied tools. The host chooses those tools, and each tool enforces its own authorization and side-effect policy.」——授权还得靠每个工具自己。OpenCode 的 bash 仍是脚本之外的直接工具,而 OpenCode 没有操作系统级沙箱(A4 会讲)。Code Mode 改变的是「往返的次数」,不是「能做什么」。
🔍 Anthropic 的「150,000 → 2,000」说的是什么?
Anthropic 工程博客《Code execution with MCP》(2025-11-04)描述的场景是:工具定义很多、中间结果很大(比如整份文档),全部经过模型上下文时要 150,000 token;改成让模型写代码调 MCP 工具、中间结果留在执行环境里、只把需要的部分交回模型,降到 2,000,「a time and cost saving of 98.7%」。那是博客里的示例场景,不是一个评测;本页的数字则来自我们自己的 trace,规模小得多,但每一个都是真实用量。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 往返次数多、每次都重发全部历史;中间结果不必都进上下文 |
| ② 为什么可能有效? | 合并掉的模型调用,其输入输出 token 与生成时间就不再发生 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条算术事实:本页组件直接把被合并那几轮的真实 usage 与延迟相加。前提是合并后的脚本能写对——这一点算术保证不了。 出处待补 |
| ③ 实验上呢? | Anthropic 的 98.7% 是博客示例(官方自报) 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条示例场景,不是对照评测。 出处Anthropic 工程博客(官方自报) ↗需联网 ;Code Mode 对任务成功率的影响,本轮没有找到公开对照数据 |
演进地图:你在这里
- 解决了模型不用复述原文,只报行号,输出最短。卡在行号是「上一次读文件时」的快照:第一处修复只要多插 2 行,第二次按旧行号 41 改,就落在
if m:上(第 1 课现算);CoreCoder README 的原话是「Line numbers are a trap」。 - 解决了不需要定位,格式最不容易错:aider 同一个 Qwen2.5-Coder-32B,whole 格式合规 99.6%,diff 格式只有 71.6%。卡在只改 1 行,也要把 44 行的文件整个再写一遍:参数 487 token,片段替换只要 54(9.0 倍,项目模型 tokenizer 实数);还会覆盖别人在此期间的改动。
- 解决了按内容定位,不怕行号过期;0 处或多处都报错、文件不动,让模型自己补上下文。卡在真实 trace pd-truncate-s1:第 4 轮 old_string 在文件里 2 处、第 5 轮缩进多 4 格 0 处,两次报错多跑了两轮,花掉 8,481 token、109.5 秒;唯一也不等于对:pd-v2-unlimited-s1 第 7 轮的 old_string 恰好是已修好那一行的前半截,计数 = 1,于是
+ 1变成了+ 1 + 1。 - 解决了模型复述时的小漂移不再白费一轮:上面第 5 轮那条缩进多 4 格的 edit,OpenCode 直接接住(LineTrimmed 级)。卡在接住 ≠ 改对:多转义一层的引号,OpenCode 的 EscapeNormalized 级照样接住,写回去的是
\",模块导入即 SyntaxError;OpenCode 的 v2(packages/core)眼下只有精确匹配,「edit fuzzy parity」还列在待移植清单里。 - 解决了语法由解码器保证,不再靠 prompt 教。对照:靠 prompt 教格式时,aider 榜上 47 个 diff 格式条目的「格式正确」比例最低只有 64.4%(官方自报)。卡在语法只管格式,定位仍是 seek_sequence:它不检查唯一、取第一处——两处相同的
return datetime.date(year, month, day)会改到第 31 行 ISO 分支,test_parse_iso 跟着坏;整行比较还会把没写缩进的 new 行原样写进去,得到 IndentationError。 - 解决了一套协议接任意外部工具;同一个 harness 给 GPT 换上 apply_patch、给 Claude 用 edit。卡在接进来的每个工具每轮都要付上下文:chrome-devtools-mcp 的 30 个工具在项目模型的 chat template 里占 7,047 token,是 miniagent 自己 6 个工具(685)的 10.3 倍。
- 解决了N 次「模型 → 工具 → 模型」往返压成一段脚本。卡在省的是往返,不省判断:真实 trace 里模型每一步都要看上一步的结果才知道下一步做什么(岔路课逐轮算)。
一句话
先停在这里:回主线去 A3。
再深一层:在列表里找出「必须看到结果才能决定下一步」的那几轮,数一数:它们就是 Code Mode 在这个任务上省不掉的下限。
下一个缺口
回主线:工具能执行 rm -rf,谁来拦? → A3 第 1 课
你可能会问
- 「Code Mode 和 MCP 是二选一吗?」 不是。OpenCode 的 codemode 调的正是 MCP 工具,而且开启 Code Mode 时 MCP 工具按组延迟披露(设计文档「MCP tools … are deferred while CodeMode is enabled」)。
- 「本课项目要不要做 Code Mode?」 不在里程碑里。贯穿任务每一步都依赖上一步的结果,上面的实验能帮你判断:它省得下来的往返不多。