# 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](/docs/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](/docs/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. ```ts check import OpenAI from "openai"; import { Sandbox } from "withruntime"; const openai = new OpenAI(); // OPENAI_API_KEY, on your server only async function runPython(code: string): Promise { 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 ```python check import json from openai import OpenAI from withruntime import Sandbox client = OpenAI() # OPENAI_API_KEY, on your server only def 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](/docs/javascript#run-commands)). 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: ```bash no-run printf %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](/docs/security#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](/compare/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](/docs/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: ```bash no-run npx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)' ``` More: [run untrusted LLM code](/use-cases/run-untrusted-llm-code), [add a code interpreter to a chatbot](/use-cases/code-interpreter-for-chatbots), [turn off sandbox internet](/how-to/turn-off-sandbox-internet), [Codex in a sandbox](/integrations/codex). ## Sources Checked 25 September 2026. - [OpenAI: function calling guide](https://developers.openai.com/api/docs/guides/function-calling) - [OpenAI: conversation state](https://developers.openai.com/api/docs/guides/conversation-state), for `previous_response_id` - [OpenAI: models](https://developers.openai.com/api/docs/models) - [OpenAI: Code Interpreter guide](https://developers.openai.com/api/docs/guides/tools-code-interpreter) - [openai on npm](https://www.npmjs.com/package/openai) (7.23.0) and [on PyPI](https://pypi.org/project/openai/) (3.19.2) Facts on this page were checked on 25 September 2026.