Runtime

How to run code from OpenAI function calling in a sandbox

Define a run_python function tool, run each call's code in a fresh microVM with no network, and return stdout, stderr and exit code.

On Runtime the tool's handler is a dozen lines, and a run is billed on the CPU it uses. Each call gets its own Firecracker microVM, which ran its first Python command 351 ms after the request at the median on 24 September 2026 (speed). A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits and $0.08 an hour with both CPUs busy (pricing). The examples use gpt-6-astra, the model OpenAI's model guide said to start with on 25 September 2026.

The loop in TypeScript

The model never runs anything. It returns a function_call item with a call_id and JSON arguments; your code runs it and answers with a function_call_output for the same call_id. previous_response_id carries the conversation, so each follow-up request sends only the outputs.

TypeScriptimport OpenAI from "openai";import { Sandbox } from "withruntime";const openai = new OpenAI(); // OPENAI_API_KEY, on your server onlyasync function runPython(code: string): Promise<string> {  await using sbx = await Sandbox.create({    network: { internet: false },    timeoutSeconds: 300,    onLeaseEnd: "stop",    maxCostMicros: 50_000,  });  await sbx.files.write("/workspace/main.py", code);  const run = await sbx.exec(["python3", "main.py"], { timeoutMs: 30_000 });  return JSON.stringify({    exit_code: run.exitCode,    timed_out: run.timedOut,    stdout: run.stdout,    stderr: run.stderr,  });}const tools: OpenAI.Responses.Tool[] = [  {    type: "function",    name: "run_python",    description:      "Run a Python 3.12 script in an isolated Linux sandbox with no internet. " +      "Returns exit_code, timed_out, stdout and stderr. Print what you need to see.",    parameters: {      type: "object",      properties: { code: { type: "string", description: "The whole script" } },      required: ["code"],      additionalProperties: false,    },    strict: true,  },];let response = await openai.responses.create({  model: "gpt-6-astra",  tools,  input: "What is the 40th Fibonacci number? Compute it, do not recall it.",});for (let turn = 0; turn < 8; turn++) {  const calls = response.output.filter((item) => item.type === "function_call");  if (calls.length === 0) break;  const outputs: OpenAI.Responses.ResponseInputItem[] = [];  for (const call of calls) {    const { code } = JSON.parse(call.arguments) as { code: string };    outputs.push({      type: "function_call_output",      call_id: call.call_id,      output: await runPython(code),    });  }  response = await openai.responses.create({    model: "gpt-6-astra",    tools,    previous_response_id: response.id,    input: outputs,  });}console.log(response.output_text);

The same loop in Python

Pythonimport jsonfrom openai import OpenAIfrom withruntime import Sandboxclient = OpenAI()  # OPENAI_API_KEY, on your server onlydef run_python(code: str) -> str:    with Sandbox.create(        network={"internet": False},        timeout_seconds=300,        on_lease_end="stop",        max_cost_micros=50_000,    ) as sbx:        sbx.files.write("/workspace/main.py", code)        run = sbx.exec(["python3", "main.py"], timeout_ms=30_000)        return json.dumps(            {"exit_code": run.exit_code, "timed_out": run.timed_out, "stdout": run.stdout, "stderr": run.stderr}        )tools = [    {        "type": "function",        "name": "run_python",        "description": "Run a Python 3.12 script in an isolated Linux sandbox with no internet. "        "Returns exit_code, timed_out, stdout and stderr. Print what you need to see.",        "parameters": {            "type": "object",            "properties": {"code": {"type": "string", "description": "The whole script"}},            "required": ["code"],            "additionalProperties": False,        },        "strict": True,    }]response = client.responses.create(    model="gpt-6-astra",    tools=tools,    input="What is the 40th Fibonacci number? Compute it, do not recall it.",)for _ in range(8):    calls = [item for item in response.output if item.type == "function_call"]    if not calls:        break    outputs = [        {"type": "function_call_output", "call_id": call.call_id, "output": run_python(json.loads(call.arguments)["code"])}        for call in calls    ]    response = client.responses.create(        model="gpt-6-astra", tools=tools, previous_response_id=response.id, input=outputs    )print(response.output_text)

strict: true makes the arguments match the schema, which OpenAI requires to list every property in required and set additionalProperties to false. The eight-turn cap stops a model that keeps calling the tool. The sandbox stops when the handler returns, even if the script raised.

What the handler sends back, and why

OpenAI's guide says a function's output "should typically be a string" and leaves the format to you. JSON with four fields gives the model enough to fix its own mistakes:

Field Where it comes from What the model does with it
exit_code The script's exit status; null if killed Tells success from failure without guessing
timed_out true when timeoutMs ended the command Rewrites a loop that never finishes
stdout Up to 64 KiB, with a flag if more was cut Reads the answer it printed
stderr Up to 64 KiB: tracebacks, warnings Sees the exception and corrects the code

A command's timeout is a result, not an exception, so a runaway script still produces a tool output the model can read (JavaScript SDK). The array form of exec runs python3 directly with no shell, so nothing in the model's text is ever parsed as a shell command.

Where the OpenAI key lives

Keep OPENAI_API_KEY in your server's environment. The loop calls OpenAI from your process and only the model's script enters the sandbox, so the sandbox never needs the key.

Store it as a Runtime secret only when code inside the sandbox calls OpenAI itself, such as an agent you run there:

Terminalprintf %s "$OPENAI_API_KEY" | npx withruntime secrets set OPENAI_API_KEY --host api.openai.com

The sandbox then sees a placeholder, and the host's proxy writes the real key into HTTPS requests to api.openai.com and nowhere else (secrets sandboxes never see).

Code Interpreter, or your own function?

OpenAI's hosted tool is {"type": "code_interpreter"}. Its guide, checked 25 September 2026, describes Python only ("the model knows it as the 'python tool'"), containers of 1g, 4g, 16g or 64g, and a container that "expires if it is not used for 20 minutes".

You need Better fit
Quick Python analysis inside one OpenAI chat Code Interpreter
Packages you choose, or a custom image Your function and Runtime
JavaScript, TypeScript, R, Java, Bash or Go too Your function and Runtime
A session that pauses for days and resumes Your function and Runtime
The same tool behind models from other vendors Your function and Runtime

Prices for both, session by session, are in OpenAI Code Interpreter alternative.

Using the OpenAI Agents SDK instead

With the Agents SDK there is no loop to write: RuntimeCloudSandboxClient runs a SandboxAgent's shell and file edits in a Runtime sandbox, and the agent definition stays as it is (OpenAI Agents SDK).

What it costs

Each call above creates a 2 vCPU, 4 GiB sandbox and stops it when the script ends. Runtime bills measured CPU at $0.025 per vCPU-hour, with a floor of a twentieth of a vCPU, and memory at $0.0075 per GiB-hour. A thousand 60-second runs that use 20 CPU-seconds each cost $0.64. maxCostMicros: 50_000 refuses a create whose first lease would cost more than five cents. OpenAI's tokens are billed by OpenAI.

New accounts get 50 free sandbox hours, no card:

Terminalnpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'

More: run untrusted LLM code, add a code interpreter to a chatbot, turn off sandbox internet, Codex in a sandbox.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.