Runtime

How to generate charts from LLM-written code in a sandbox

Run the model's plotting code in a sandbox's code interpreter and take each chart back as a PNG result, or as an SVG or PDF file it saved.

On Runtime 10,000 charts cost about $2.71 on 2 vCPU, 4 GiB sandboxes that each run 30 seconds, at the rates in force on 25 September 2026, because the sandbox is billed for the CPU the plotting uses. matplotlib 3.6.3, pandas and NumPy are in the default image, and a matplotlib figure comes back from the interpreter as a PNG with no file handling at all.

The short answer

Give the model one tool that runs Python, and pick the images out of the cell's results:

TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 600 });// The code below is what the model wrote.const cell = await sbx.interpreter.run(  [    "import matplotlib.pyplot as plt",    "months = ['Jan', 'Feb', 'Mar', 'Apr']",    "signups = [120, 180, 150, 240]",    "plt.figure(figsize=(6, 3.5))",    "plt.plot(months, signups, marker='o')",    "plt.title('Sign-ups by month')",    "plt.show()",  ].join("\n"),  { timeoutMs: 30_000 },);let n = 0;for (const result of cell.results) {  const png = result.data["image/png"];  if (typeof png === "string") await writeFile(`chart-${n++}.png`, Buffer.from(png, "base64"));}console.log(cell.status, cell.error?.value ?? "no error", `${n} chart(s)`);
Pythonimport base64from withruntime import Sandboxwith Sandbox.create(network={"internet": False}, timeout_seconds=600) as sbx:    cell = sbx.interpreter.run(        "\n".join([            "import matplotlib.pyplot as plt",            "months = ['Jan', 'Feb', 'Mar', 'Apr']",            "signups = [120, 180, 150, 240]",            "plt.figure(figsize=(6, 3.5))",            "plt.plot(months, signups, marker='o')",            "plt.title('Sign-ups by month')",            "plt.show()",        ]),        timeout_ms=30_000,    )    charts = [r["data"]["image/png"] for r in cell["results"] if "image/png" in r["data"]]    for n, png in enumerate(charts):        with open(f"chart-{n}.png", "wb") as file:            file.write(base64.b64decode(png))    print(cell["status"], cell["error"], len(charts), "chart(s)")

Collect every PNG in the results, since one cell can draw more than one chart. Send the model the cell's stdout and error, not the image bytes, and show the images to the user.

When the model's chart fails

Plotting code written by a model fails in predictable ways. The cell tells you which, so the model can fix it on the next turn:

What went wrong What comes back What to send the model
A typo or a wrong column name status: "error", with the error name and traceback The error value and last lines
A plot that loops or never ends status: "timeout" after timeoutMs "The cell timed out after 30 s"
No figure drawn status: "ok", no image/png result "No chart was produced"
A library the image lacks ModuleNotFoundError in the error Nothing: add it to the image

Because interpreter variables persist, the model's second attempt can reuse the data it already loaded instead of starting over.

Vector charts and other formats

A PNG suits a chat reply. For a report or a slide, have the code save the file and read it back:

TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run(  "import matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\n" +    "plt.bar(['north', 'south'], [42, 17])\n" +    "plt.savefig('/workspace/chart.svg')\nplt.savefig('/workspace/chart.pdf')",);await writeFile("chart.svg", await sbx.files.readText("/workspace/chart.svg"));await writeFile("chart.pdf", await sbx.files.read("/workspace/chart.pdf"));

An SVG is text, so it can go straight into an HTML page. The interpreter also runs R, and R plots come back as PNG the same way; R installs the first time a sandbox uses it, which took about 90 seconds on 23 September 2026, or bake it into an image to skip that wait.

More plotting libraries

The default image carries matplotlib. Put anything else your charts need into a custom image once, and every sandbox starts with it:

Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="charts", recipe={"pip": ["seaborn", "plotly"]})with runtime.sandboxes.create(image="charts", network={"internet": False}) as sbx:    cell = sbx.interpreter.run("import seaborn\nseaborn.__version__")    print(cell["results"][0]["data"]["text/plain"])

Building an image is free. A stored image costs $0.08 per GB per 30-day month, and the trial stores the first three free (custom images).

Keep the chart code away from everything else

The model decides what code runs, and the data may be a customer's. The sandbox is a Firecracker microVM with its own kernel, so a bad cell cannot reach your servers. With the internet off, the code cannot send the data anywhere, and root in the guest cannot turn the network back on. For a chatbot where one conversation makes many charts, keep one sandbox per conversation with Sandbox.getOrCreate(name, { idlePauseSeconds: 300 }), so its data stays loaded between questions and it pauses when the user goes quiet.

What a chart-generation feature needs

Need How Runtime covers it
Plotting in Python matplotlib 3.6.3, pandas and NumPy in the default image
Charts back without files Figures return as base64 PNG results from interpreter.run
Vector output savefig to SVG or PDF, then files.readText or files.read
R graphics The interpreter's R language; plots return as PNG
Errors the model can fix Error name, value and traceback on each cell
Bounded run time timeoutMs per cell
From a terminal runtime sandbox run-code <id> plot.py --out-dir charts saves PNGs

What it costs

Take 10,000 chart requests a month, each in a 2 vCPU, 4 GiB sandbox that runs for 30 seconds and uses 3 CPU-seconds to draw:

TextCPU:     10,000 × 3 s / 3,600 × $0.025             = $0.21Memory:  10,000 × 30 s / 3,600 × 4 GiB × $0.0075   = $2.50Total:                                               $2.71

Memory is most of the bill, so a chart sandbox of 2 GiB halves that line. Runtime charges $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory (pricing).

Start

Terminalnpx withruntime sandbox run --trial -- python3 -c 'import matplotlib; print(matplotlib.__version__)'

Approve the link in your browser the first time. Then runtime sandbox run-code <id> plot.py --out-dir charts writes each figure a script draws to the charts folder (CLI).

Related: data analysis agent, code interpreter for chatbots, spreadsheet analysis, what a code interpreter is.

Facts on this page were checked on 25 September 2026.