Trial
0020. Code interpreter as a Conversations-backed tool
Trial
- Status: Trial
- Date: 2026-11-03
Context
Section titled “Context”ADR 0018 established
the stateless one-shot Conversations pattern for the Conversations-only
builtin tools: Chat Completions rejects code_interpreter with HTTP 400
code 1800 (live-probed 2026-11-03, same as web_search), while
POST /v1/conversations with inputs[], tools: [{type: "code_interpreter"}], stream: false, store: false and a fixed
top-level instructions string is accepted. Probing the code interpreter
on the live endpoint (2026-11-03) surfaced wire facts that differ from
web search in one important way: the tool.execution entry’s
info.result is an array of content chunks, not a JSON string —
observed shapes are {type:"text", text} and {type:"file_url", file_url, file_name, file_type, summary} where file_url is a signed
blob URL for a generated file (plots, CSVs, …). info additionally
carries code (the executed Python source) and code_output (captured
stdout/stderr as a string). The message.output entry also references the
generated files via tool_file chunks, but like the web-search message
text it is discarded — only the tool.execution info is the tool’s payload.
The model matrix is asymmetric in a different way than web search:
mistral-small-latest, mistral-medium-latest and
magistral-medium-latest run the code interpreter on /v1/conversations,
while mistral-large-4 does not refuse with 3004 here — it accepts the
request but silently skips the tool (no tool.execution in outputs[]).
Hard forcing is unavailable as with web search (completion_args.tool_choice
“any” is 422-rejected), so instructions is the only forcing lever. Two
empty outcomes are distinct wire shapes: no tool.execution entry means
the backend skipped execution (retry-worthy), while a tool.execution
entry with an empty result array is a genuinely executed run with no
output.
Decision
Section titled “Decision”The code interpreter ships as one client-side pi tool,
mistral_code_interpreter (extensions/tools/code-interpreter.ts), over
the same stateless one-shot Conversations call as web search: a fresh
POST /v1/conversations with inputs: [{role:"user", content: <prompt>}],
tools: [{type:"code_interpreter"}], stream: false, store: false and
the fixed instructions string “Always use the code_interpreter tool to run
Python code before answering. Never answer from memory.” The returned
conversation_id is discarded — the tool never continues a conversation.
The shared client method createConversation is generalized over the
builtin tool type and builds the instructions string per tool type; the
web-search strings are unchanged.
- Parsing:
outputs[]is scanned fortool.executionentries matchingname/functioncode_interpreter; eachinfo.resultarray is mapped defensively to chunks — text chunks verbatim,file_urlchunks to{type:"file_url", fileUrl, fileName, fileType}— and unknown chunk shapes are folded away, never dumped. Multiple executions concatenate their chunks in outputs order.code/code_outputsurface when strings. Themessage.outputentry is never read. A matching entry whoseinfo.resultis not a readable array fails with an unreadable-result error, never a raw dump. The two empty outcomes stay distinct: a missingtool.executionentry yields an ok result withskipped: true(retry- worthy), an emptyresultarray yields an ok “ran but produced no output” result. Neither is an error. - Signed URLs and privacy: generated files arrive as signed blob URLs.
They go into the model-facing
content/structuredContentbut never into the persisteddetails, which carry only{tool, model, prompt, executed/skipped, chunk count}— per the security rules in the Tools reference. - Model: the Conversations call runs under the same search model as web
search —
knowledge.searchModel, defaultmistral-small-latest— not under the chat model. A 400 with code 3004 is mapped defensively to the same friendly error as web search (naming the key and the default); the skip path is the main safety net becausemistral-large-4skips silently instead of refusing. - Usage: prompt/completion tokens (plus details) map onto the tool
result like the web-search path; cost stays 0 because the pay-per-use
connector_tokensare not priceable locally.
Consequences
Section titled “Consequences”- Running Python works on every model the chat stream can use: the
execution runs under the search model’s capabilities, not the chat
model’s — on
mistral-large-4sessions the tool still runs (mistral-small-latestby default) even though large-4 itself would silently skip the tool. - Generated files (plots, data files) come back as short-lived signed
download links in the tool result — usable by the model and the user,
never persisted into transcripts/logs via
details. - Forcing execution is probabilistic: the
instructionsstring steers the server-side agent but cannot guarantee it, so skip detection (the distinctskipped: trueresult) remains the safety net. - Code-interpreter execution is pay-per-use via
connector_tokens— not priceable in the local cost display (tool-result cost stays 0; the token counts are surfaced). - The Conversations call is Beta-endpoint usage — but stateless and
one-shot (
store: false, conversation discarded), so no server-side conversation state can corrupt anything.