将 AI 智能体置于沙箱中,速度提升 100 倍

分类业界资讯
作者CloudFlare
来源跳转
发表时间

内容

去年 9 月,我们介绍了 Code Mode,其核心理念是:智能体执行任务时,不应通过调用工具(tool calls)来完成,而应通过编写调用 API 的代码来完成。我们已经展示过,只需将一个 MCP 服务器转换为 TypeScript API,就可以▶ 将 token 使用量削减 81%。我们还演示了,Code Mode 也可以运行在 MCP 服务器的后端而不是前端,从而构建出全新的 Cloudflare MCP 服务器:它仅用两个工具、不到 1,000 个 token,就暴露了整个 Cloudflare API

但如果一个智能体(或一个 MCP 服务器)要执行由 AI 动态生成的代码来完成任务,那么这些代码总得有个地方运行,而且这个地方必须是安全的。你不能直接在应用中对 AI 生成的代码执行 eval():恶意用户很容易通过提示词诱导 AI 注入漏洞。

你需要一个沙箱(sandbox):一个与应用本身、与外部世界相隔离的代码执行环境,只开放这段代码确实需要访问的特定能力。

沙箱是 AI 行业的热门话题。对于这类任务,大多数人会选择容器(container)。借助基于 Linux 的容器,你可以启动几乎任何你想要的代码执行环境。Cloudflare 甚至还为此提供了容器运行时Sandbox SDK

但容器代价高、启动慢:启动往往要花数百毫秒,运行还要占用数百 MB 内存。为了避免延迟,你大概率需要让它们保持热启动(warm),而这又会让你倾向于在多个任务之间复用已有容器,从而损害安全性。

如果我们想支持面向消费者规模的智能体,也就是每个终端用户都有一个智能体(甚至很多个!),并且每个智能体都会写代码,那么容器并不够。我们需要更轻量的方案。

而我们已经有了。

Dynamic Worker Loader:轻量级沙箱

去年 9 月那篇 Code Mode 文章里,还夹带发布了一个新的实验性功能:Dynamic Worker Loader API。这个 API 允许一个 Cloudflare Worker 在运行时动态指定代码,并即时实例化出一个运行在独立沙箱中的新 Worker。

Dynamic Worker Loader 现已进入公开测试版(open beta),对所有付费版 Workers 用户开放。

完整细节请参阅文档,下面是它的使用方式:

let agentCode: string = `
export default {
async myAgent(param, env, ctx) {
// ...
}
}
`;

let chatRoomRpcStub = ...;

let worker = env.LOADER.load({

  compatibilityDate: "2026-03-01",
  mainModule: "agent.js",
  modules: {
    "agent.js": agentCode
  },

  env: {
    CHAT_ROOM: chatRoomRpcStub
  },

  globalOutbound: null,
});

await worker.getEntrypoint().myAgent(param);

就是这么简单。

快 100 倍

Dynamic Worker 使用的底层沙箱机制,正是整个 Cloudflare Workers 平台自 8 年前发布以来一直采用的技术:isolate(隔离实例)。isolate 是 V8 JavaScript 执行引擎的一个实例,而 V8 也正是 Google Chrome 使用的引擎。它们就是 Workers 的运行基础

一个 isolate 启动只需几毫秒,内存占用也只需几 MB。相比典型容器,这大约快 100 倍,且内存效率高 10 到 100 倍。

这意味着,如果你想为每个用户请求按需启动一个全新的 isolate,运行一小段代码后再将其丢弃,你完全可以这么做。

无限扩展性

许多基于容器的沙箱服务商都会限制全局并发沙箱数量,以及沙箱创建速率。Dynamic Worker Loader 没有这些限制。因为它本质上只是一个 API,调用的是一直以来支撑我们平台的同一套技术,而这套技术本来就支持 Workers 平滑扩展到每秒数百万请求。

想要处理每秒一百万个请求,并且每一个请求都加载一个独立的 Dynamic Worker 沙箱、全部并发运行?完全没问题!

零延迟

一次性使用的 Dynamic Worker 通常运行在创建它的 Worker 所在的同一台机器上——甚至是同一个线程里。你无需在全球范围内寻找一个热启动的沙箱,也无需跨地域通信。isolate 轻量到我们可以直接在请求落地的地方运行它。Dynamic Workers 已在 Cloudflare 全球数百个节点全部支持。

全部都是 JavaScript

与容器相比,唯一的限制是:你的智能体需要编写 JavaScript。

严格来说,Workers(包括动态 Workers)也支持 Python 和 WebAssembly,但对于小段代码——比如由智能体按需生成的那种——JavaScript 的加载和运行速度会快得多。

我们人类往往对编程语言有强烈偏好。有人喜欢 JavaScript,也有人更偏爱 Python、Rust 或其他无数语言。

但这里讨论的不是人类,而是 AI。AI 可以编写你想要的任何语言。LLM 对所有主流语言都很擅长,而它们在 JavaScript 上的训练数据尤其庞大。

JavaScript 因其 Web 天生属性,就是为沙箱环境而设计的。它是这项工作的正确语言。

用 TypeScript 定义工具

如果我们希望智能体能够做点真正有用的事情,它就必须能和外部 API 通信。那我们该如何告诉它自己可以访问哪些 API 呢?

MCP 定义的是扁平化工具调用(flat tool calls)的模式,而不是编程 API。OpenAPI 提供了表达 REST API 的方式,但它非常冗长——无论是规范本身,还是你为调用它而编写的代码,都是如此。

对于暴露给 JavaScript 的 API,答案显而易见,而且只有一个:TypeScript。

智能体懂 TypeScript。TypeScript 本身就是为简洁表达而设计的。只用很少的 token,你就能让智能体精确理解你的 API。

interface ChatRoom {

  getHistory(limit: number): Promise < Message[] > ;

  subscribe(callback: (msg: Message) => void): Promise < Disposable > ;

  post(text: string): Promise < void > ;
}

type Message = {
  author: string;
  time: Date;
  text: string;
}

再看看等价的 OpenAPI 规范(长到你得滚动才能看完):

openapi: 3.1.0
info:
title: ChatRoom API
description: >
与聊天室交互的接口。
version: 1.0.0

paths:
/messages:
get:
operationId: getHistory
summary: 获取最近的聊天记录
description: 按时间从新到旧返回最近 `limit` 条消息。
parameters:
- name: limit
in: query
required: true
schema:
type: integer
minimum: 1
responses:
"200":
description: 消息列表。
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Message"

post:
operationId: postMessage
summary: 向聊天室发送消息
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- text
properties:
text:
type: string
responses:
"204":
description: 消息发送成功。

/messages/stream:
get:
operationId: subscribeMessages
summary: 通过 SSE 订阅新消息
description: >
打开一个服务器发送事件(Server-Sent Events)流。每个事件携带一个
JSON 编码的 Message 对象。客户端通过关闭连接来取消订阅。
responses:
"200":
description: 新消息的 SSE 流。
content:
text/event-stream:
schema:
description: >
每个 SSE `data` 字段都包含一个 JSON 编码的 Message 对象。
$ref: "#/components/schemas/Message"

components:
schemas:
Message:
type: object
required:
- author
- time
- text
properties:
author:
type: string
time:
type: string
format: date-time
text:
type: string

我们认为 TypeScript API 更好。它所需 token 更少,也更容易理解(无论对智能体还是对人类都是如此)。

Dynamic Worker Loader 让你可以很方便地在自己的 Worker 中实现这样的 TypeScript API,然后将其作为方法参数或通过 env 对象传给 Dynamic Worker。Workers Runtime 会自动在沙箱与你的宿主代码(harness code)之间建立一座 Cap'n Web RPC 桥,这样智能体就能跨越安全边界调用你的 API,而且完全不会意识到自己并不是在使用本地库。

这意味着你的智能体可以编写如下代码:

let history = await env.CHAT_ROOM.getHistory(1000);
return history.filter(msg => msg.author == "alice");

HTTP 过滤与凭据注入

如果你更愿意给智能体提供 HTTP API,也完全支持。通过 worker loader API 的 globalOutbound 选项,你可以注册一个回调函数,让它在每次 HTTP 请求发生时被调用;你可以在其中检查请求、改写请求、注入认证密钥、直接返回响应、阻止请求,或做任何你想做的事。

例如,你可以用它来实现凭据注入(token injection):当智能体向一个需要授权的服务发起 HTTP 请求时,你在请求发出前为其添加认证凭据。这样一来,智能体本身永远不会知道这些秘密凭据,因此也就无法泄露它们。

当智能体要访问一个众所周知、可能已包含在训练集中的 API 时,或者当你希望智能体使用某个基于 REST API 构建的库时,采用纯 HTTP 接口可能是合适的(该库可以运行在智能体的沙箱内)。

不过,在没有兼容性要求的前提下,TypeScript RPC 接口优于 HTTP:

  • 如上所示,描述一个 TypeScript 接口所需的 token,远少于描述一个 HTTP 接口。
  • 智能体编写调用 TypeScript 接口的代码时,所需 token 也远少于等价的 HTTP 调用。
  • 使用 TypeScript 接口时,由于你本来就在定义自己的包装接口(wrapper interface),因此更容易将接口收窄到你希望提供给智能体的精确能力范围,这在简洁性和安全性上都有优势。相比之下,HTTP 往往意味着你要对某个现有 API 上的请求做过滤。这很难,因为你的代理必须完整理解每一次 API 调用的含义,才能正确决定是否放行;而 HTTP 请求本身又很复杂,带有许多可能都具有语义的头部和其他参数。到头来,通常还不如直接写一个 TypeScript 包装层,只实现你允许暴露的那些函数。

久经实战考验的安全性

加固基于 isolate 的沙箱并不容易,因为它的攻击面比硬件虚拟机更复杂。尽管所有沙箱机制都会有漏洞,但 V8 中的安全漏洞通常比典型 hypervisor(虚拟机监控器)中的安全漏洞更常见。因此,当你用 isolate 来隔离可能带有恶意的代码时,必须有额外的纵深防御层(defense-in-depth)。例如,Google Chrome 就因此实现了严格的进程隔离,但这并不是唯一的解决方案。

在保护基于 isolate 的平台方面,我们已有近十年的经验。我们的系统会在数小时内自动将 V8 安全补丁部署到生产环境——比 Chrome 自己还快。我们的安全架构包含一层定制的第二层沙箱,并能基于风险评估动态隔离租户。我们还扩展了 V8 沙箱本身,以利用 MPK 等硬件特性。我们与顶尖研究人员合作(并将其中一些人招入团队),开发了针对 Spectre 的新型防御机制。我们还有系统会扫描代码中的恶意模式,并自动阻断或施加额外的沙箱层。以及更多。

当你在 Cloudflare 上使用 Dynamic Workers 时,这一切都会自动随之而来。

辅助库

我们构建了一些你在使用 Dynamic Workers 时可能会觉得很有帮助的库:

Code Mode

@cloudflare/codemode 简化了通过 Dynamic Workers 运行模型生成代码并访问 AI 工具的过程。其核心是 DynamicWorkerExecutor(),它会构建一个专门设计的沙箱,包含代码规范化(code normalisation)能力,用于处理常见格式错误;同时还能直接访问 globalOutbound fetcher,以控制沙箱内 fetch() 的行为——将其设为 null 可实现完全隔离,或传入一个 Fetcher 绑定以对沙箱发出的出站请求进行路由、拦截或增强。

const executor = new DynamicWorkerExecutor({
  loader: env.LOADER,
  globalOutbound: null,
});

const codemode = createCodeTool({
  tools: myTools,
  executor,
});

return generateText({
  model,
  messages,
  tools: {
    codemode
  },
});

Code Mode SDK 还提供了两个服务端工具函数。codeMcpServer({ server, executor }) 可以包装现有的 MCP Server,将其工具表面(tool surface)替换为一个单独的 code() 工具。openApiMcpServer({ spec, executor, request }) 更进一步:给定一个 OpenAPI 规范和一个 executor,它会构建一个完整的 MCP Server,并提供与 Cloudflare MCP Server 一样的 search()execute() 工具,更适合较大的 API。

在这两种情况下,模型生成的代码都运行在 Dynamic Workers 中,而对外部服务的调用则通过传递给 executor 的 RPC 绑定完成。

进一步了解这个库及其使用方式。

打包

Dynamic Workers 需要预先打包好的模块。 @cloudflare/worker-bundler 可以帮你处理这件事:你只需提供源码文件和一个 package.json,它就会从 registry 解析 npm 依赖,使用 esbuild 完成打包,并返回 Worker Loader 所需的模块映射。

import {
  createWorker
} from "@cloudflare/worker-bundler";

const worker = env.LOADER.get("my-worker", async () => {
  const {
    mainModule,
    modules
  } = await createWorker({
    files: {
      "src/index.ts": `
import { Hono } from 'hono';
import { cors } from 'hono/cors';

const app = new Hono();
app.use('*', cors());
app.get('/', (c) => c.text('Hello from Hono!'));
app.get('/json', (c) => c.json({ message: 'It works!' }));

export default app;
`,
      "package.json": JSON.stringify({
        dependencies: {
          hono: "^4.0.0"
        }
      })
    }
  });

  return {
    mainModule,
    modules,
    compatibilityDate: "2026-01-01"
  };
});

await worker.getEntrypoint().fetch(request);

它还通过 createApp 支持全栈应用——可将服务端 Worker、客户端 JavaScript 和静态资源一起打包,并内置资源分发能力,处理内容类型、ETag 和 SPA 路由。

进一步了解这个库及其使用方式。

文件操作

@cloudflare/shell 为你的智能体在 Dynamic Worker 中提供了一个虚拟文件系统。智能体代码通过 state 对象上的类型化方法来执行操作——读取、写入、搜索、替换、diff、glob、JSON 查询/更新、归档——使用结构化输入输出,而不是解析字符串。

存储后端由一个持久化的 Workspace 支撑(SQLite + R2),因此文件可以跨执行持久保存。像 searchFilesreplaceInFilesplanEdits 这样的粗粒度操作可最大限度减少 RPC 往返次数——智能体只需发起一次调用,而不必循环处理单个文件。批量写入默认具有事务性:如果任意一次写入失败,之前的写入会自动回滚。

import {
  Workspace
} from "@cloudflare/shell";
import {
  stateTools
} from "@cloudflare/shell/workers";
import {
  DynamicWorkerExecutor,
  resolveProvider
} from "@cloudflare/codemode";

const workspace = new Workspace({
  sql: this.ctx.storage.sql,
  r2: this.env.MY_BUCKET,
  name: () => this.name
});

const executor = new DynamicWorkerExecutor({
  loader: env.LOADER
});

const result = await executor.execute(
  `async () => {
// 在所有 TypeScript 文件中搜索某个模式
const hits = await state.searchFiles("src/**/*.ts", "answer");
// 将多处编辑规划为单个事务
const plan = await state.planEdits([
{ kind: "replace", path: "/src/app.ts",
search: "42", replacement: "43" },
{ kind: "writeJson", path: "/src/config.json",
value: { version: 2 } }
]);
// 原子性应用——失败时回滚
return await state.applyEditPlan(plan);
}`,
  [resolveProvider(stateTools(workspace))]
);

这个包还附带预构建的 TypeScript 类型声明和一个系统提示模板,因此你只需很少几个 token,就能把完整的 state API 放进 LLM 上下文里。

进一步了解这个库及其使用方式。

人们是怎么用它的?

Code Mode

开发者希望智能体通过针对工具 API 编写并执行代码来完成任务,而不是一次只进行一个串行工具调用。借助 Dynamic Workers,LLM 可以生成一个单独的 TypeScript 函数,把多个 API 调用串联起来,在 Dynamic Worker 中运行,并将最终结果返回给智能体。这样,进入上下文窗口的只有输出结果,而不是每个中间步骤。这既降低了延迟,也减少了 token 使用量,同时还能带来更好的效果,特别是在工具表面较大的情况下。

我们自己的 Cloudflare MCP server 就是完全按这种方式构建的:它仅通过两个工具——search 和 execute——就在不到 1,000 个 token 中暴露了整个 Cloudflare API,因为智能体面对的是类型化 API 编程,而不是在数百个单独工具定义中摸索。

构建自定义自动化

开发者正在使用 Dynamic Workers 让智能体即时构建自定义自动化。Zite 就是一个例子:它正在打造一个应用平台,用户通过聊天界面进行交互——LLM 在幕后编写 TypeScript,用来构建 CRUD 应用、连接 Stripe、Airtable、Google Calendar 等服务,并运行后端逻辑,而用户从头到尾都不会看到一行代码。每个自动化都运行在其各自独立的 Dynamic Worker 中,并且只能访问该端点所需的特定服务和库。

“为了让 Zite 的 LLM 生成应用支持服务端代码,我们需要一个具备即时性、隔离性和安全性的执行层。Cloudflare 的 Dynamic Workers 在这三方面都达到了要求,而且在我们针对速度和库支持做的基准测试中,表现优于所有其他平台。其兼容 NodeJS 的运行时支持了 Zite 的全部工作流,使我们能够接入数百个第三方集成,同时不牺牲启动时间。借助 Dynamic Workers,Zite 现在每天可处理数百万次执行请求。” —— Antony Toron,Zite 联合创始人兼 CTO

运行 AI 生成的应用

开发者正在构建这样的平台:由 AI 生成完整应用,既可面向他们的客户,也可供内部团队搭建原型。借助 Dynamic Workers,每个应用都可以按需启动,然后在再次调用前回到冷存储。快速的启动时间让活跃开发过程中的变更预览变得非常容易。平台还可以拦截或阻止生成代码发出的任何网络请求,从而确保 AI 生成应用的运行安全。

定价

动态加载的 Workers 的定价为:每个每天加载的唯一 Worker 收费 0.002 美元(截至本文发布时),此外还要加上常规 Workers 的 CPU 时间和调用次数费用。

对于 AI 生成的 “code mode” 使用场景,即每个 Worker 都是一次性的唯一实例,这意味着每加载一个 Worker 的价格是 0.002 美元(另加 CPU 和调用费用)。与生成代码所需的推理成本相比,这项成本通常可以忽略不计。

在 Beta 测试期间,0.002 美元的收费将被免除。由于定价可能发生变化,请始终查看我们的 Dynamic Workers 定价页面 以获取最新信息。

快速开始

如果你使用的是 Workers 付费计划,今天就可以开始使用 Dynamic Workers

Dynamic Workers Starter

https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers

使用这个“hello world”入门模板,即可部署一个能够加载并执行 Dynamic Workers 的 Worker。

Dynamic Workers Playground

https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground

你也可以部署 Dynamic Workers Playground,在那里你可以编写或导入代码,使用 @cloudflare/worker-bundler 在运行时打包,通过 Dynamic Worker 执行,查看实时响应和执行日志。

BLOG-3243 2

Dynamic Workers 快速、可扩展且轻量。欢迎在 Discord 上联系我们,如果你有任何问题。我们非常期待看到你构建出的作品!

评论

(0)
未配置登录方式
暂无评论