在本教程中,你将构建一个通过运行代码来回答问题的 Worker。Workers AI 模型编写 JavaScript,Dynamic Worker 在无网络访问的情况下运行它,模型再将结果用于回答。
完成后,Worker 会回答关于质数的问题,并返回模型运行的代码:
{
"answer": "There are 9,592 prime numbers below 100,000.",
"runs": [
{
"code": "function isPrime(n){if(n<2)return false; if(n%2===0) return n===2; for(let i=3;i*i<=n;i+=2){if(n%i===0) return false;} return true;} let count=0; for(let i=2;i<100000;i++){ if(isPrime(i)) count++; } return count;",
"output": { "result": 9592, "logs": [] }
}
]
}你将学习如何:
- 在没有网络访问且带有 CPU 时间限制的 Dynamic Worker 中运行模型生成的 JavaScript。
- 向模型提供运行代码并返回结果的工具。
- 将代码错误发回模型,以便它修复代码。
- 注册 Cloudflare 账户 ↗。
- 安装
Node.js↗。
-
创建一个 Worker 项目:
npm create cloudflare@latest -- sandbox-code-interpreter --category=hello-world --type=hello-world --lang=ts --no-deploy --no-git --no-agentsyarn create cloudflare sandbox-code-interpreter --category=hello-world --type=hello-world --lang=ts --no-deploy --no-git --no-agentspnpm create cloudflare@latest sandbox-code-interpreter --category=hello-world --type=hello-world --lang=ts --no-deploy --no-git --no-agents -
进入项目目录:
cd sandbox-code-interpreter -
安装 Agents SDK、AI SDK ↗ 和 Zod ↗。Worker 使用
agents/models/ai-sdk中的createAI调用 Workers AI:npm i agents ai @ai-sdk/provider zodyarn add agents ai @ai-sdk/provider zodpnpm add agents ai @ai-sdk/provider zodbun add agents ai @ai-sdk/provider zod -
替换
wrangler.jsonc,添加 Workers AI 绑定和 Worker Loader 绑定:{ "$schema": "node_modules/wrangler/config-schema.json", "name": "sandbox-code-interpreter", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-11-14", "observability": { "enabled": true, }, "upload_source_maps": true, "ai": { "binding": "AI", }, "worker_loaders": [ { "binding": "LOADER", }, ], }"$schema" = "node_modules/wrangler/config-schema.json" name = "sandbox-code-interpreter" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-11-14" upload_source_maps = true [observability] enabled = true [ai] binding = "AI" [[worker_loaders]] binding = "LOADER"AI调用 Workers AI 模型。LOADER在运行时创建 Dynamic Workers。 -
为绑定生成类型:
npx wrangler typesyarn wrangler typespnpm wrangler types
创建 src/sandbox.ts。runJavaScript() 函数将代码加载到新的 Dynamic Worker,并返回其结果和日志。如果代码未在五秒内完成,它会抛出错误:
const timeoutMs = 5_000;
export async function runJavaScript(env, code) {
const sandbox = env.LOADER.load({
compatibilityDate: "2026-11-14",
mainModule: "code.js",
modules: {
"code.js": `
import { WorkerEntrypoint } from "cloudflare:workers";
function format(value) {
return typeof value === "string" ? value : JSON.stringify(value);
}
export class Code extends WorkerEntrypoint {
async run() {
const logs = [];
const console = {
log: (...values) => logs.push(values.map(format).join(" ")),
};
const result = await (async () => {
${code}
})();
return { result, logs };
}
}
`,
},
globalOutbound: null,
limits: { cpuMs: 50 },
});
let timer = null;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`The code did not finish within ${timeoutMs} ms`));
}, timeoutMs);
});
try {
return await Promise.race([sandbox.getEntrypoint("Code").run(), timeout]);
} finally {
clearTimeout(timer);
}
}import type { WorkerEntrypoint } from "cloudflare:workers";
export type Output = {
result?: unknown;
logs: string[];
};
type CodeEntrypoint = WorkerEntrypoint & {
run(): Promise<Output>;
};
const timeoutMs = 5_000;
export async function runJavaScript(env: Env, code: string): Promise<Output> {
const sandbox = env.LOADER.load({
compatibilityDate: "2026-11-14",
mainModule: "code.js",
modules: {
"code.js": `
import { WorkerEntrypoint } from "cloudflare:workers";
function format(value) {
return typeof value === "string" ? value : JSON.stringify(value);
}
export class Code extends WorkerEntrypoint {
async run() {
const logs = [];
const console = {
log: (...values) => logs.push(values.map(format).join(" ")),
};
const result = await (async () => {
${code}
})();
return { result, logs };
}
}
`,
},
globalOutbound: null,
limits: { cpuMs: 50 },
});
let timer: number | null = null;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`The code did not finish within ${timeoutMs} ms`));
}, timeoutMs);
});
try {
return await Promise.race([
sandbox.getEntrypoint<CodeEntrypoint>("Code").run(),
timeout,
]);
} finally {
clearTimeout(timer);
}
}Dynamic Worker 会限制生成代码能做的事:
globalOutbound: null会使fetch()和connect()抛出错误,因此代码没有网络访问。- Dynamic Worker 收不到
env,因此代码无法调用 Workers AI 或你的 Worker 能访问的任何其他资源。 limits: { cpuMs: 50 }会停止使用超过 50 毫秒 CPU 时间的代码。请参阅 自定义资源限制。- 如果代码在不使用 CPU 的情况下等待(例如长时间的
setTimeout()),五秒超时会阻止你的 Worker 继续等待。计时器运行在你的 Worker 中,因此生成的代码无法更改它。 - 每次调用
load()都会创建新的 Dynamic Worker,因此一次运行中的值在下一次运行中不可用。
生成的代码会成为异步函数的函数体,因此它可以使用 await,并且必须 return 其结果。本地 console 对象会收集 console.log() 输出。代码仍然可以更改 Dynamic Worker 内部的任何内容,包括其包装器,但无法访问 Dynamic Worker 未收到的任何内容。
替换 src/index.ts。Worker 向模型提供 runJavaScript 工具,并返回模型的答案以及模型运行的每段代码:
import { generateText, isStepCount, tool } from "ai";
import { createAI } from "agents/models/ai-sdk";
import { z } from "zod";
import { runJavaScript } from "./sandbox";
const model = "@cf/openai/gpt-oss-120b";
const Question = z.object({ question: z.string().min(1) });
const instructions = `Answer the question.
Always call the runJavaScript tool before you answer. Do not calculate, count, or sort in your head.
Write the body of an async JavaScript function and return the value you need.
If the tool returns an error, fix the code and run it again.
Answer in one or two plain-text sentences.`;
export default {
async fetch(request, env) {
if (request.method !== "POST") {
return new Response("Send a POST request", { status: 405 });
}
const body = Question.safeParse(await request.json().catch(() => null));
if (!body.success) {
return Response.json(
{ error: "Send a JSON body with a question" },
{ status: 400 },
);
}
const ai = createAI({ binding: env.AI });
const runs = [];
try {
const result = await generateText({
model: ai(model),
instructions,
prompt: body.data.question,
tools: {
runJavaScript: tool({
description:
"Run JavaScript in a sandbox with no network access. The code is the body of an async function. Return a JSON-serializable value. console.log output is captured.",
inputSchema: z.object({
code: z.string().describe("Body of an async JavaScript function"),
}),
execute: async ({ code }) => {
try {
const output = await runJavaScript(env, code);
runs.push({ code, output });
return output;
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
runs.push({ code, error: message });
return { error: message };
}
},
}),
},
// Stop after five model steps, so a model that keeps failing cannot
// run code indefinitely
stopWhen: isStepCount(5),
});
return Response.json({ answer: result.text, runs });
} catch (error) {
console.error("Model request failed", error);
return Response.json(
{ error: "The model request failed. Try again later.", runs },
{ status: 502 },
);
}
},
};import { generateText, isStepCount, tool } from "ai";
import { createAI } from "agents/models/ai-sdk";
import { z } from "zod";
import { runJavaScript, type Output } from "./sandbox";
const model = "@cf/openai/gpt-oss-120b";
const Question = z.object({ question: z.string().min(1) });
const instructions = `Answer the question.
Always call the runJavaScript tool before you answer. Do not calculate, count, or sort in your head.
Write the body of an async JavaScript function and return the value you need.
If the tool returns an error, fix the code and run it again.
Answer in one or two plain-text sentences.`;
type Run = { code: string } & ({ output: Output } | { error: string });
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Send a POST request", { status: 405 });
}
const body = Question.safeParse(await request.json().catch(() => null));
if (!body.success) {
return Response.json(
{ error: "Send a JSON body with a question" },
{ status: 400 },
);
}
const ai = createAI({ binding: env.AI });
const runs: Run[] = [];
try {
const result = await generateText({
model: ai(model),
instructions,
prompt: body.data.question,
tools: {
runJavaScript: tool({
description:
"Run JavaScript in a sandbox with no network access. The code is the body of an async function. Return a JSON-serializable value. console.log output is captured.",
inputSchema: z.object({
code: z.string().describe("Body of an async JavaScript function"),
}),
execute: async ({ code }) => {
try {
const output = await runJavaScript(env, code);
runs.push({ code, output });
return output;
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
runs.push({ code, error: message });
return { error: message };
}
},
}),
},
// Stop after five model steps, so a model that keeps failing cannot
// run code indefinitely
stopWhen: isStepCount(5),
});
return Response.json({ answer: result.text, runs });
} catch (error) {
console.error("Model request failed", error);
return Response.json(
{ error: "The model request failed. Try again later.", runs },
{ status: 502 },
);
}
},
} satisfies ExportedHandler<Env>;generateText() 会将问题发送给模型。当模型调用 runJavaScript 时,AI SDK 会运行 execute() 并将返回值发回模型。然后模型可以运行更多代码或作答。
如果代码抛出错误、超过 CPU 时间限制或超时,或调用 fetch(),execute() 会返回错误消息而不是让请求失败。模型会读取错误并可以修复其代码。
Worker 会在调用模型之前用 Zod 验证请求正文。如果 Workers AI 请求失败(例如因为 速率限制),Worker 会记录错误,并以 502 以及已经运行的代码进行响应。wrangler.jsonc 中的 observability 会将这些日志保留在 Workers Logs 中。
-
启动本地开发 server:
npx wrangler devyarn wrangler devpnpm wrangler dev在
wrangler dev期间,Dynamic Worker 在本地运行。Workers AI 请求始终在 Cloudflare 上运行,并计入你的 Workers AI 用量。 -
向 Wrangler 打印的 URL POST 一个问题。默认是
http://localhost:8787:curl http://localhost:8787 --json '{"question":"How many prime numbers are there below 100,000?"}'响应包含答案以及模型运行的代码:
{ "answer": "There are 9,592 prime numbers below 100,000.", "runs": [ { "code": "function isPrime(n){if(n<2)return false; if(n%2===0) return n===2; for(let i=3;i*i<=n;i+=2){if(n%i===0) return false;} return true;} let count=0; for(let i=2;i<100000;i++){ if(isPrime(i)) count++; } return count;", "output": { "result": 9592, "logs": [] } } ] }模型每次会写出不同的代码和措辞。
result是9592。 -
提出需要排序的问题:
curl http://localhost:8787 --json '{"question":"Sort these words by length, then alphabetically: pear, fig, banana, kiwi, apple, date."}'答案会列出
fig, date, kiwi, pear, apple, banana。
-
部署你的 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy -
向 Wrangler 打印的
workers.devURL POST 一个问题:curl https://sandbox-code-interpreter.<YOUR_SUBDOMAIN>.workers.dev --json '{"question":"How many prime numbers are there below 100,000?"}'响应形状与本地响应相同。
- 让生成的代码调用 Worker 提供的方法。请参阅 Bindings。
- 让模型从生成的代码中调用带类型的工具。请参阅 Code Mode。
- 运行 shell 命令或其他 Linux 工具。请参阅 运行 Linux 命令。
- 选择不同的模型。请参阅 Workers AI 模型。