Runtime

DeepSeek tool calls: how to run DeepSeek's code safely

Point the OpenAI SDK at api.deepseek.com, give the model a run_python tool, and run each call in a microVM that installs from PyPI only.

DeepSeek's API runs no code for you, so where its code runs is your decision, and a Runtime sandbox makes it a throwaway microVM that costs $0.03125 an hour while it waits. DeepSeek's tool-calls guide says so plainly: "The model itself does not execute specific functions." Every Runtime sandbox is a Firecracker microVM with its own Linux kernel, and its network rules are enforced on the host, where root inside cannot change them (security). The examples use deepseek-flash, which DeepSeek's pricing page mapped to DeepSeek-V4.1-Flash on 25 September 2026.

Let the model ask for packages

Model-written Python often starts with an import the default image lacks. This tool takes a list of packages as well as the code. The handler installs them while the sandbox can reach PyPI and nothing else, then switches the internet off before the model's script runs.

TypeScriptimport OpenAI from "openai";import { Sandbox } from "withruntime";const deepseek = new OpenAI({  baseURL: "https://api.deepseek.com",  apiKey: process.env.DEEPSEEK_API_KEY,});const PACKAGE = /^[A-Za-z0-9][A-Za-z0-9._-]*(==[A-Za-z0-9.]+)?$/;async function runPython(code: string, packages: string[]): Promise<string> {  const bad = packages.filter((name) => !PACKAGE.test(name));  if (bad.length) return JSON.stringify({ error: `not a package name: ${bad.join(", ")}` });  await using sbx = await Sandbox.create({    network: { internet: true, allow: ["pypi.org", "*.pythonhosted.org"] },    timeoutSeconds: 600,    onLeaseEnd: "stop",  });  if (packages.length) {    const install = await sbx.exec(["pip", "install", "--quiet", ...packages], {      timeoutMs: 180_000,    });    if (install.exitCode !== 0) return JSON.stringify({ stage: "install", stderr: install.stderr });  }  await sbx.network.set({ internet: false }); // nothing leaves while the script runs  await sbx.files.write("/workspace/main.py", code);  const run = await sbx.exec(["python3", "main.py"], { timeoutMs: 60_000 });  return JSON.stringify({    exit_code: run.exitCode,    timed_out: run.timedOut,    stdout: run.stdout,    stderr: run.stderr,  });}const tools: OpenAI.Chat.ChatCompletionTool[] = [  {    type: "function",    function: {      name: "run_python",      description:        "Run a Python 3.12 script in a disposable sandbox. List any PyPI packages it needs " +        "in `packages`; they are installed first, then the script runs with no internet.",      parameters: {        type: "object",        properties: {          code: { type: "string" },          packages: { type: "array", items: { type: "string" } },        },        required: ["code", "packages"],      },    },  },];const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [  { role: "user", content: "Use sympy to factor x**6 - 1 and show the result." },];for (let turn = 0; turn < 8; turn++) {  const completion = await deepseek.chat.completions.create({    model: "deepseek-flash",    messages,    tools,  });  const message = completion.choices[0]!.message;  messages.push(message); // keeps reasoning_content, which DeepSeek requires back  if (!message.tool_calls?.length) {    console.log(message.content);    break;  }  for (const call of message.tool_calls) {    if (call.type !== "function") continue;    const args = JSON.parse(call.function.arguments) as { code: string; packages?: string[] };    messages.push({      role: "tool",      tool_call_id: call.id,      content: await runPython(args.code, args.packages ?? []),    });  }}
Pythonimport jsonimport osimport refrom openai import OpenAIfrom withruntime import Sandboxclient = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")PACKAGE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*(==[A-Za-z0-9.]+)?$")def run_python(code: str, packages: list[str]) -> str:    bad = [name for name in packages if not PACKAGE.match(name)]    if bad:        return json.dumps({"error": f"not a package name: {', '.join(bad)}"})    with Sandbox.create(        network={"internet": True, "allow": ["pypi.org", "*.pythonhosted.org"]},        timeout_seconds=600,        on_lease_end="stop",    ) as sbx:        if packages:            install = sbx.exec(["pip", "install", "--quiet", *packages], timeout_ms=180_000)            if install.exit_code != 0:                return json.dumps({"stage": "install", "stderr": install.stderr})        sbx.network.set(internet=False)  # nothing leaves while the script runs        sbx.files.write("/workspace/main.py", code)        run = sbx.exec(["python3", "main.py"], timeout_ms=60_000)        return json.dumps({"exit_code": run.exit_code, "timed_out": run.timed_out, "stdout": run.stdout, "stderr": run.stderr})tools = [    {        "type": "function",        "function": {            "name": "run_python",            "description": "Run a Python 3.12 script in a disposable sandbox. List any PyPI packages it needs "            "in `packages`; they are installed first, then the script runs with no internet.",            "parameters": {                "type": "object",                "properties": {"code": {"type": "string"}, "packages": {"type": "array", "items": {"type": "string"}}},                "required": ["code", "packages"],            },        },    }]messages = [{"role": "user", "content": "Use sympy to factor x**6 - 1 and show the result."}]for _ in range(8):    message = client.chat.completions.create(model="deepseek-flash", messages=messages, tools=tools).choices[0].message    messages.append(message)  # keeps reasoning_content, which DeepSeek requires back    if not message.tool_calls:        print(message.content)        break    for call in message.tool_calls:        args = json.loads(call.function.arguments)        messages.append({"role": "tool", "tool_call_id": call.id, "content": run_python(args["code"], args.get("packages", []))})

Why the handler is written this way

Line in the handler What it stops
The package-name pattern A "package" such as --index-url=https://evil.example changing pip
allow: ["pypi.org", "*.pythonhosted.org"] The install fetching from anywhere but PyPI
network.set({ internet: false }) The script sending data out; the rule applies to open connections too
exec([...]) with an array Any part of the model's text being read by a shell
timeoutMs An endless loop; the result comes back with timedOut: true
await using / with A sandbox left running after the answer

A package that runs code while it installs runs it inside the same throwaway machine, behind the same PyPI-only rule, so it can reach nothing else either (egress control).

DeepSeek details that break loops

  • Pass reasoning back. DeepSeek's thinking mode is on by default, and its guide warns that for requests with tools, "the reasoning_content must be fully passed back to the API in all subsequent requests", or the API returns a 400 error. Appending the whole assistant message, as above, does that.
  • Strict schemas are beta. Setting strict: true on a function needs the base URL https://api.deepseek.com/beta.
  • Prices change with the clock. DeepSeek charges a peak rate from 01:00 to 04:00 and 06:00 to 10:00 UTC on weekdays, excluding Chinese public holidays.
Model (25 September 2026) Input, cache miss, per 1M tokens Output per 1M tokens
deepseek-flash, off-peak / peak $0.15 / $0.30 $0.60 / $1.20
deepseek-v4-pro, off-peak / peak $0.66 / $1.32 $1.98 / $3.96

Where the DeepSeek key goes

The loop runs on your server, so DEEPSEEK_API_KEY stays in its environment and never enters the sandbox. Store it as a Runtime secret, bound to api.deepseek.com, only if something inside the sandbox must call DeepSeek itself, such as an agent you run there; the sandbox then sees a placeholder that works on that host alone (secrets).

What the sandbox costs

A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour waiting and $0.08 with both CPUs busy, billed on the CPU the code uses (pricing). The install is part of each run; to skip it for packages the model asks for often, bake them into a custom image and create from that. New accounts get 50 free sandbox hours, no card:

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

More: run untrusted LLM code, turn off sandbox internet, agent evals and SWE-bench.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.