Mistral function calling: run code from Mistral models in a sandbox
Add a run_code tool to chat.complete, run each tool call in a microVM with no internet, and append a tool message with the result.
Runtime runs the code Mistral writes, in Python, JavaScript, TypeScript or
bash, from Chat Completions, the API where Mistral's own code interpreter does
not work. Mistral's documentation says its code_interpreter tool "works
with the Conversations API and the Agents API" and "isn't supported in the Chat
Completions API". A function tool backed by a Runtime sandbox works in any of
them. Each run gets a Firecracker microVM that started its first command in
351 ms at the median on 24 September 2026 (speed). The model
here is mistral-medium-3-5, Mistral Medium 3.5, which Mistral lists as its
frontier model for agentic and coding work, checked 25 September 2026.
The loop
Mistral's Chat Completions follow the familiar shape: the reply's
tool_calls hold an id, a function name and arguments; you append the
assistant message, then one tool message per call with the same id. The
TypeScript SDK spells the fields in camelCase: toolCalls, toolCallId.
TypeScriptimport { Mistral } from "@mistralai/mistralai";import type { ChatCompletionRequestMessage } from "@mistralai/mistralai/models/components";import { Sandbox } from "withruntime";const client = new Mistral({ apiKey: process.env.MISTRAL_API_KEY ?? "" });const RUNNERS = { python: { file: "main.py", argv: ["python3", "main.py"] }, javascript: { file: "main.mjs", argv: ["node", "main.mjs"] }, typescript: { file: "main.ts", argv: ["bun", "main.ts"] }, bash: { file: "main.sh", argv: ["bash", "main.sh"] },} as const;type Language = keyof typeof RUNNERS;async function runCode(language: Language, code: string): Promise<string> { const runner = RUNNERS[language] ?? RUNNERS.python; await using sbx = await Sandbox.create({ vcpu: 1, memoryMiB: 2048, network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop", }); await sbx.files.write(`/workspace/${runner.file}`, code); const run = await sbx.exec([...runner.argv], { timeoutMs: 60_000 }); return JSON.stringify({ exit_code: run.exitCode, timed_out: run.timedOut, stdout: run.stdout, stderr: run.stderr, });}const tools = [ { type: "function" as const, function: { name: "run_code", description: "Run a complete program in an isolated Linux sandbox with no internet and return " + "exit_code, timed_out, stdout and stderr. Python 3.12, Node.js 24, Bun and bash.", parameters: { type: "object", properties: { language: { type: "string", enum: Object.keys(RUNNERS) }, code: { type: "string" }, }, required: ["language", "code"], }, }, },];const messages: ChatCompletionRequestMessage[] = [ { role: "user", content: "Write a TypeScript function that checks a Luhn number, test it on 4539 1488 0343 6467, and run it.", },];for (let turn = 0; turn < 8; turn++) { const response = await client.chat.complete({ model: "mistral-medium-3-5", messages, tools }); const message = response.choices[0]?.message; if (!message) break; messages.push({ ...message, role: "assistant" }); if (!message.toolCalls?.length) { console.log(message.content); break; } for (const call of message.toolCalls) { const args = typeof call.function.arguments === "string" ? JSON.parse(call.function.arguments) : call.function.arguments; messages.push({ role: "tool", name: call.function.name, toolCallId: call.id ?? "", content: await runCode(args.language, args.code), }); }}Pythonimport jsonimport osfrom mistralai.client import Mistralfrom withruntime import Sandboxclient = Mistral(api_key=os.environ["MISTRAL_API_KEY"])RUNNERS = { "python": ("main.py", ["python3", "main.py"]), "javascript": ("main.mjs", ["node", "main.mjs"]), "typescript": ("main.ts", ["bun", "main.ts"]), "bash": ("main.sh", ["bash", "main.sh"]),}def run_code(language: str, code: str) -> str: file, argv = RUNNERS.get(language, RUNNERS["python"]) with Sandbox.create( vcpu=1, memory_mib=2048, network={"internet": False}, timeout_seconds=300, on_lease_end="stop" ) as sbx: sbx.files.write(f"/workspace/{file}", code) run = sbx.exec(argv, 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_code", "description": "Run a complete program in an isolated Linux sandbox with no internet and return " "exit_code, timed_out, stdout and stderr. Python 3.12, Node.js 24, Bun and bash.", "parameters": { "type": "object", "properties": {"language": {"type": "string", "enum": list(RUNNERS)}, "code": {"type": "string"}}, "required": ["language", "code"], }, }, }]messages = [ {"role": "user", "content": "Write a TypeScript function that checks a Luhn number, test it on 4539 1488 0343 6467, and run it."}]for _ in range(8): message = client.chat.complete(model="mistral-medium-3-5", messages=messages, tools=tools).choices[0].message messages.append(message) if not message.tool_calls: print(message.content) break for call in message.tool_calls: args = call.function.arguments args = json.loads(args) if isinstance(args, str) else args messages.append( {"role": "tool", "name": call.function.name, "content": run_code(args["language"], args["code"]), "tool_call_id": call.id} )The program's text goes into a file and runs as an argument list, so the
model's code reaches python3, node, bun or bash inside the sandbox and
never a shell on your server. Bun runs TypeScript directly, with no build step (Bun).
Each call's sandbox has 1 vCPU and 2 GiB, and it stops when the handler
returns.
Three ways to run code with Mistral
| Approach | API it works with | Where the code runs |
|---|---|---|
Mistral's code_interpreter built-in tool |
Agents and Conversations APIs only | Mistral's "isolated container" |
| A function tool run on your own server | Any | Your machine, beside your data and keys |
| A function tool run in a Runtime sandbox | Any, including Chat Completions | A microVM with its own kernel, no route to your network |
Mistral's page on the built-in tool, checked 25 September 2026, does not state
its languages, packages or time limits. With your own tool, all three are
settings you choose: the runners above, the image, and timeoutMs.
Keep a sandbox between calls
When the model builds on its own earlier output, create the sandbox once per
conversation and run every call in it, so a file written in one call is there
in the next. To keep variables in memory between calls, use the
code interpreter: sbx.interpreter.run(code, { language: "typescript" })
keeps state per language, and adds R, Java and Go. A sandbox that goes quiet
can pause itself with idlePauseSeconds and wake on the next call.
Keys and network
MISTRAL_API_KEY stays on your server; the sandbox gets only the program. If
the program needs a package, allow the registry by name, install, and switch
the internet off before running the model's code
(turn off sandbox internet). If code in
the sandbox must call Mistral itself, store the key as a Runtime secret bound
to api.mistral.ai, which the sandbox sees only as a placeholder
(secrets).
What it costs
A 1 vCPU, 2 GiB sandbox costs $0.01625 an hour while it waits (memory at $0.0075 per GiB-hour plus the CPU floor of a twentieth of a vCPU at $0.025 per vCPU-hour) and $0.04 an hour with its CPU busy (pricing). A five-second test run costs less than a hundredth of a cent. New accounts get 50 free sandbox hours, no card:
Terminalnpx withruntime sandbox run --trial -- bun --versionMore: TypeScript in a sandbox, run untrusted LLM code, grade student code, microVM vs container.
Sources
Checked 25 September 2026.
- Mistral: function calling
- Mistral: code interpreter tool
- Mistral: Mistral Medium 3.5, API names
mistral-medium-3-5andmistral-medium-latest - @mistralai/mistralai on npm (2.7.0) and mistralai on PyPI (2.10.1)
Facts on this page were checked on 25 September 2026.