本文从技术角度阐述,为何应用小型类型化代码执行环境替代庞大的工具箱,让模型编写普通代码,同时在模型层以下强制执行安全策略。
AI translation, not an official translation. Refer to the original for technical details.
Adapted from @kylejeong# 代码模式就是你所需要的一切:为什么让智能体编写代码优于调用工具 简而言之:对于大量智能体任务而言,最好的"工具"是一个小型代码执行环境。让模型针对类型化能力编写普通代码,然后在模型层以下强制执行安全策略,而不是将每个操作都包装成另一个定制工具调用。 如果你想体验交互式组件,请访问我们网站上的博客。(https://www.browserbase.com/blog/code-mode-is-all-you-need) ## 你的智能体可能需要更少的工具 我们不断给智能体配备更大的工具箱,而实际上它们只需要更好的工具。 一个 MCP 服务器暴露 12 个工具,下一个暴露 30 个。再加上 CLI、沙箱、数据库、CRM、浏览器,模型突然要把相当一部分上下文用来学习接口,而不是完成实际工作。 与此同时,前沿模型已经花了数年时间,在一个接口上变得出奇地出色:代码。 ## 工具层正在向下移动一层 第一代智能体需要明确的动词。 search_customer。get_invoice。click_element。query_logs。download_file。 当时模型不够可靠,上下文窗口较小,每个被允许的操作都需要严格的约束。如果你想让智能体完成一个 10 步的工作流,你就给它 10 个精心描述的工具,然后希望它按正确的顺序选择正确的工具。 "代码模式"改变了这一接口的形态。 它不再为每个操作向模型传授新的 JSON 模式,而是在执行环境中暴露一小组类型化能力。模型自己编写胶水代码。 它可以并行调用 3 个服务,在 20 MB 的响应到达上下文之前对其进行过滤,重试某个失败的分支,将中间结果写入磁盘,并用它已经烂熟于心的语言来编排整个工作流。 ## 上下文的差异 对于这个工作流,粗略估计如下: 上下文减少约 88%。将工具箱扩展到 40 个始终加载的工具,光是 schema 就可能在智能体开始工作之前消耗约 12,000 个 token。 一次代码执行可以替代一长串工具调用。更重要的是,调用之间的逻辑变得确定性。JavaScript 负责处理 join、filter、loop 和错误路径。模型只需决定要编写什么程序。 ## 模型本就会说代码 每个自定义工具都要求模型学习新的"行话"。 名称是全新的,参数和错误格式也是。模型必须推断 customer_search、find_customer 和 lookup_account 是同义词还是微妙不同的操作。将这种情况乘以每个供应商和每个内部服务,复杂度可想而知。 代码为模型提供了适用于所有这些情况的熟悉语法。 类型提供词汇,编译器捕获格式错误的调用,运行时错误构成反馈循环,文件提供工作内存,标准编程语言特性提供分支、并发、解析和转换,无需再经过模型进行额外处理。 单个工具调用是一个决策,而程序是一棵决策树。 这正是为什么代码模式在质感上与添加一个新工具截然不同。模型可以将一个模糊的任务转化为一个临时的、特定于任务的工具。它编写出自己所需的精确抽象,使用它,检查结果,然后可以决定丢弃它,或将其保存为可复用的技能。 ## 浏览器让这一模式更加清晰 在 @browserbase,我们更早地观察到了这一转变,因为我们构建的是浏览器智能体。 每一个网页本身就已经具备编程接口。DOM 描述其结构,浏览器 API 暴露状态,JavaScript 可以查询元素、读取应用数据、派发事件、调用页面本地函数、检查性能条目,并在返回结果前对其进行转换。 `page.evaluate()` 在页面上下文中运行 JavaScript,在那里 `window`、`document` 以及应用自身的运行时均可访问。 Agent 不需要 `extract_product_cards` 工具。它编写的是页面所需的提取逻辑(且仅限提取逻辑)。 同样的模式同样适用于交互: 你怎么可能为每一个网站上的每一种交互定义一套通用工具? 你不需要,让模型编写代码,以它所需的精确方式控制浏览器即可。 当然,仍然存在限制。页面内的 JS 无法控制浏览器的每一处 chrome 界面、无法绕过源边界、无法安全处理所有凭证,也无法保证合成事件的行为与可信用户输入完全一致。视觉界面、canvas、跨源 iframe、下载以及原生权限提示(即广告弹窗)仍然受益于专用原语。 但对于浏览器工作中的绝大多数场景而言,最通用的浏览器工具就是 JavaScript。 # 1 个程序胜过 20 轮对话 代码模式的提升不仅仅体现在对界面的熟悉程度上。 ## 它压缩上下文 以工具为主的循环往往会将每一个中间结果都返回给模型。而代码可以在返回真正重要的 10 行内容之前,先对结果进行过滤、聚合,并将大量数据写入磁盘。 ## 它让组合变得廉价 一个程序可以在单次执行中控制浏览器、查询数据仓库、规范化 CSV 文件并更新 CRM。控制流存在于 TypeScript 中,而不是在每一轮模型推理中反复重新决策。 ## 它创造持久化制品 一次成功的运行可以转化为脚本、测试或技能。下一次运行从可用的代码出发,而不是从对话记录中重新还原工作流。 ## 它能自我调试 编译器和运行时产生结构化的反馈,Agent 利用这些反馈编辑程序并在紧密循环中重新运行。 演进过程大致如下: 1. 工具模式:模型每次选择一个动作。 1. 代码模式:模型编写并执行整个工作流。 1. 稳定模式:一个成功的程序成为默认路径,只有当程序出错时,模型才回到代码模式。 一旦 Agent 发现了可行的工作流,在每次运行时都付出全新的推理成本就是浪费。保存代码,然后确定性地执行它。当环境发生变化时,再让模型回来进行修复。 ## 我们在内部 Agent BB 中使用了这一模式 我们的内部 Agent `bb` 跨越工程、支持、销售、市场营销和运营等多个领域。它负责调查浏览器会话、查询内部数据、读取文件、记录功能请求并编写代码。 我们本可以给它一个庞大的扁平工具目录。但我们没有这样做,它的核心接口保持精简。最重要的能力是 `exec`,即针对类型化服务接口的 JS 执行。 Agent 可以编写代码来调用数据仓库、CRM、支持系统、可观测性栈或浏览器。它可以并行化请求、在结果进入上下文之前对其进行转换,并将大量输出移至文件。技能向它传授某一领域的操作手册,而代码负责具体执行。 我在下方的文章中详细介绍了完整架构,包括沙箱、技能系统和凭证代理。 这种设计还降低了新集成的成本。我们只需定义一次有类型的能力,将其置于执行边界之后,再添加一个描述何时使用它的小型技能说明。代理负责编写特定任务的组合逻辑。 工具箱可以持续扩展,而无需同步扩展顶层的调度框架。 ## 沙箱只是起点 如果你对安全性存有疑虑,那是完全合理的。任意代码执行所带来的爆炸半径,远比一次工具调用大得多。"在沙箱中运行"是必要条件,但远远不够。 沙箱限制了代码在执行环境中能触及的范围,但它无法自动阻止代理泄露凭证、调用破坏性 API、向未经授权的域名发送数据,或在部分失败后重复产生副作用。 安全边界必须位于生成程序的下方。 1. 代理凭证。沙箱获得的是短期引用,而非原始的生产密钥。 1. 在代理层强制执行能力控制。策略层决定一次运行可以调用哪些服务方法,生成的代码无法自行获取额外权限。 1. 收窄底层身份范围。即使代理层失效,只读仓库角色和窄范围 OAuth 授权依然有效。 1. 控制出口流量。域名白名单和请求拦截机制约束数据的流向。 1. 读写分离。破坏性或不可逆操作应配备更严格的工具、审批流程或显式确认步骤。(基于代理的访问控制) 1. 保留追踪记录。存储程序、输入、输出、网络行为和策略决策,以便事后审查每次运行。 1. 为重试而设计。浏览器表单提交与数据库读取具有不同的重试语义。 让模型在箱子内随意编写代码,然后让箱子在物理层面就无法执行未经授权的操作。 这正是专用工具应当发挥作用的地方。支付操作、权限变更、生产环境部署等,都应携带一份简洁但明确的契约。代码模式应负责编排读取和转换层,而高风险写操作则应受到严格管控。 ## 代理即工程师 工具目录会持续增长,但面向模型的接口不应随之膨胀。 SDK、MCP 服务器和 CLI 仍可提供传输、发现、认证、可观测性以及人工访问能力。对于能力更强的代理,这些接口将越来越多地位于可编程执行层之下。 代理看到的是类型和代码,运行时看到的是权限和策略,服务看到的是一个正常的已认证请求。 每一代新模型都能编写更好的代码、调试更长的工作流,并更有效地利用执行反馈。我们应当停止将这种能力强行压缩进数百个陌生的细碎 schema 中。 给模型一门它本就熟悉的语言,给这门语言一个安全的运行环境,再将严格的护栏置于二者之下。 最好的代理调度框架会同时使用工具和代码,将少量高风险工具与通用执行层并置配合。 条条大路通罗马,让代理去写代码吧。 → Kyle 如果你想体验交互式组件,请访问我们网站上的博客。(https://www.browserbase.com/blog/code-mode-is-all-you-need)