代码即动作

10 分钟读完

K12
深入解析 · 工具与能力设计

代码即动作:当智能体写出一段程序,而不是挑一件工具。

Anthropic 报告过一条从 Google Drive 到 Salesforce 的工作流,token 消耗从 150,000 降到 2,000——砍掉 98.7%——办法是让模型写代码去调工具,而不是一步吐一次工具调用;Cloudflare 则把 2,500 个 API 端点从约 244,000 token 的 schema 压到约 1,000 token,只暴露两件工具加一个沙箱。这些数字是真的,而且它们不是免费的:你付出去的那样东西叫动作日志。一串工具调用是策略引擎能设闸、审批人能读懂、审计员能重建的;一段程序不是。决定这个模式该用在哪里的,是这笔交换,而不是那个 token 数。

STEP 1

真正改变的是:中间结果不再进入上下文。

这笔节省常被描述成"工具定义变小了"。那是次要的一半。结构性的改变在于:模型本来就不需要读的数据,不再穿过模型。

  • 经典循环为每一个中间值付两遍钱。每条工具结果都会追加进对话记录,而整份记录会在下一步被重发一次。第 3 步取回的一份 40,000 token 的表格,到第 12 步你还在为它付费——这就是智能体成本控制所描述的平方级增长。
  • 在程序里,这个值住在一个变量里。取表、过滤、聚合、写结果——只有结果回到上下文里。模型写出了这个变换,却从未看过那些行,而这既是节省,也是风险。
  • 工具定义从"常驻"变成"可发现"。不再是每个 schema 每一轮都占着上下文,而是把工具做成智能体按需列举与读取的文件或模块。这正是 Cloudflare 那个数字如此极端的原因:2,500 个端点作为常驻 schema 是不可能的;作为一个可搜索的文件系统,它就是两件工具。
  • 往返从 n 次塌缩到一次。二十次有依赖关系的工具调用就是二十趟推理,每一趟都要在一个不断变长的提示词上做预填充。用代码表达的循环在一轮之内以机器速度执行——这份延迟收益通常远大于 token 收益,原因见预填充、解码与 KV 缓存
  • 控制流变得可表达。重试、条件分支,以及边界取决于数据的循环,在逐轮驱动的循环里都很别扭,在程序里则微不足道。一个会写 for 的模型,不需要你事先预判迭代次数。

精简版:工具调用把模型放在数据通路上;代码即动作把它拿了出来。这个模式所有的好处与所有的危险,都从这一句话里生出来。

STEP 2

收益真实的地方,与它只是舍入误差的地方。

那些公开的数字来自最佳情况,而它们对此也很诚实。把这个模式套到形状不合的地方,只是拿一个沙箱、一个解释器和一类全新的失败,换来了零。

  • 真实:模型并不需要读的大块中间数据。过滤结果集、连接两个数据源、聚合一份日志、变换一份文档。"取回的字节数"与"做决定所需的字节数"之间的差距越大,收益越大。
  • 真实:对一个集合做扇出。"对这 60 个未关闭 issue,各取其关联 PR 并检查测试是否跑过。"用工具调用是 120 次往返;用程序就是一个循环,而模型只读那份汇总。
  • 真实:大到无法内联的工具面。超过几十件工具,选择准确率就已经在退化了,这一点工具发现与文档讲过。通过文件系统渐进披露,比一个更大的提示词是更好的答案。
  • 虚假:三次调用、结果都很小。解释器那次往返、模型必须写出的那段代码、以及错误处理,加起来比它们替换掉的那三个 JSON 块还贵。
  • 虚假:模型必须查看数据才能决定的工作。如果下一个动作取决于读那些行,那些行就必须进上下文;于是你保留了成本,还多加了一个沙箱。
  • 负收益:单次有后果的动作。"发起这笔退款"是一次调用、一个参数。把它包进一段程序不带来任何效率,却拿掉了审批面——那正是下一步要谈的。
STEP 3

没人计价的那笔成本:你的动作日志变成了一段程序。

这是本页的论点。生产智能体系统所依赖的三项控制,都建立在同一个假设上——动作是一次一个、以结构化对象的形式、在执行之前抵达的。代码即动作同时打破了这三者所依赖的那个假设。

  • 策略执行是以工具调用为钥匙的。"超过 500 元的退款需要审批"这样一条规则,是作为一个拦截器实现在"模型提议的调用"与"该调用被执行"之间的。当模型吐出的是一段程序时,那个被提议的调用在程序已经跑起来之前根本不作为对象存在——于是拦截点搬进了沙箱内部,策略即代码必须在那里重新实现一遍,否则就等于完全没在执行。
  • 审批闸门丢掉了它的对象。给用户看"智能体想用这些参数调用 send_email"是可复核的。给他看四十行 Python 并问要不要运行,那不是复核,那是一次由一个并不想做代码审计的人所做的代码审计——它通不过智能体的交互设计里那道复核成本检验。
  • 审计轨迹记录的粒度错了。"执行了脚本 #4471"对调查者而言,关于"动过哪些记录"什么也没说。要重建它,需要那段脚本、它的输入,以及一次你多半没有的确定性重放。决策回执如今变成了一段程序加一个环境。
  • 爆炸半径不再能被事先枚举。用逐次调用的工具,你可以靠数数给一次运行定界:最多三次写入,写进这两个系统。而一个循环可以把同一件写工具调用一千次,于是这个界必须从"计划评审"变成"运行时配额"。

要讲得精确,因为含糊的版本会导向错误的修法:这个模式并没有让智能体更不安全,它把执行点从你的编排层挪进了代码运行时。如果你让控制随之一起搬,你什么也没损失。如果没有——而默认实现都没有——那你就为了省 token 悄悄删掉了三项控制。

STEP 4

运行时就是新的执行边界——照这个来造。

修法不是放弃这个模式,而是把解释器当作一道有自己策略的安全边界,而不是当作一个"顺便能跑代码"的便利设施。

  • 那些绑定就是能力授予。你注入进解释器全局作用域的函数,恰好就是这段程序能调用的全部工具——别的都不存在。这比提示词里的一份工具清单是更干净的能力模型,因为它由运行时强制执行,而不是向模型请求。
  • 从内部、在绑定处记日志。每个被注入的函数都记录它自己的这次调用——参数、结果大小、耗时、任务 ID——于是即便模型从未吐出过一次工具调用,trace 里依然有一串工具调用。这一个决定就找回了 STEP 3 拿走的大部分东西,而代价只是一层包装。
  • 把有副作用的工具留在程序之外。这个模式最强的版本是不对称的:读取、变换与查询在沙箱里跑;写入、支付、消息以及任何不可逆的动作,作为普通工具调用回到模型这一轮,由你既有的闸门拦截。你在占 95% 的读调用上拿到 token 收益,同时在真正要紧的那 5% 上保住复核面。
  • 给运行时上配额,不要信任那段程序。墙钟时间、内存、每个绑定的调用次数,以及一次运行的写入预算——由解释器强制执行,失败闭合。一个模型写出的、差一位的循环,比一段恶意程序要可能得多。
  • 这个沙箱和别的沙箱一样需要那五条决策。文件系统、出站、凭据、算力、生命周期——沙箱与代码执行讲了它们,而在这里最要紧的是出站,因为一段能够到网络的程序,可以把它刚刚过滤出来的一切外泄出去,而全程都不必让模型看见。
  • 确定性重放值得花钱去买。把程序、绑定版本与输入都存下来。能够对着录下的工具响应重跑一遍脚本,会把一件读不懂的审计物件变回读得懂的,而这是"第 4471 次运行到底做了什么"这个问题唯一实用的答案。
STEP 5

新的失败模式,以及它们为何比工具调用的失败更安静。

逐次调用的工具用法失败得很吵:一个错误参数返回一条错误,模型读到它并在下一轮做出反应。程序失败的方式,是产出一个看着很合理的答案。

  • 一次异常会丢掉整批。60 次迭代里第 43 条记录格式不对,脚本就中止了;除非模型写了逐条的错误处理——它第一次尝试时通常没写——否则另外 59 条结果也一并没了。逐次调用的循环在这里会优雅降级;程序不会。
  • 无声的部分成功才是危险的形态。一段捕获了异常并继续跑下去的程序,返回的汇总看起来是完整的。"已处理 60 条记录"是模型对自己那段代码作出的一个断言,而不是一次观察,而对话记录里没有任何东西与之矛盾。
  • 模型在变换它从未看过的数据。按一个它假定存在的列过滤、解析一种它猜出来的日期格式、按一个并不唯一的字段去重。在逐次调用的模式里,数据在上下文中,这类错误是看得见的;在这里,错误答案被正确地计算了出来。
  • 调试要跨过一道边界。故障发生在一段生成的代码里,跑在一个注入了绑定的沙箱中,所以调用栈只有在能以模型可据以行动的形式回到对话记录里时才有用——这与工具错误消息提出的要求相同,只是如今用在了运行时异常上。
  • 非确定性会叠加。同一个任务跑两次会产出两段不同的程序、带着不同的边界情况处理。你本已有的采样方差,如今表现为结构上不同的执行路径——在设定评测样本量时值得记住这一点。
STEP 6

那条决策规则。

上面的一切收敛成一个问题,而且是逐件工具去问、不是逐个系统去问——这个模式不是你只做一次的架构选择。

  • 每一次单独的副作用都需要被授权吗?如果需要,这件工具就留在模型那一轮,闸门所在的地方。如果不需要,它可以住进沙箱。
  • "搬动的字节"与"需要的字节"之间差距大吗?大,这个模式就划算。如果模型必须读数据才能决定,就不划算。
  • 这份工作是迭代式的、形状取决于数据吗?循环与扇出,正是程序以不小的优势胜过逐轮循环的地方。
  • 你负担得起一个带真正出站控制的代码沙箱吗?负担不起,你就负担不起这个模式,因为替代方案是一段握着你的凭据、网络还敞着的程序。
  • 要给复核者看的是程序,还是副作用?如果你的界面必须把这个渲染给人看,那就先设计那份摘要,再去造运行时——事后给生成代码补上可读性,是行不通的。

从不对称开始:只把读取与变换类工具挪到代码运行时后面,把每一次写入都留在审批闸门已经在的那一轮模型回合里,并给每个注入的绑定套一层包装,让它照常记录一条工具调用跨度。你会拿到那 98.7% 中的绝大部分——字节都在读那一侧——而你的策略引擎、审批界面与审计轨迹原封不动地继续工作。token 的节省来自把模型移出数据通路;危险来自把你的控制一并移了出去,而只要你有意去分,这两件事是可以分开的。

相关:工具粒度讲这会改变哪个尺寸决策,高级工具编排模式讲还有哪些备选,MCP 工具设计讲 schema 膨胀问题的服务端一侧,在循环层面控制成本讲还有什么会撬动同一个数字。