Sitemap
Craig Walls

Craig Walls

Author Spring AI in Action, Spring in Action, and Build Talking Apps for Alexa

Spring AI Recipe: Running Python with Hosted Code Interpreter

9 min readNov 2, 2026

--

LLMs are great at generating code, but what if they could actually run it, too? With Spring AI and OpenAI’s hosted Code Interpreter, your application can turn a prompt into executable Python, run it in the cloud, and even show you the code behind the answer.

Press enter or click to view image in full size

One of the most exciting capabilities of OpenAI’s Responses API is its support for hosted tools. Unlike tools you implement yourself or expose through MCP, hosted tools are provided and executed entirely by OpenAI. You don’t have to implement them, deploy them, or even worry about where they run.

The Responses API supports several hosted tools:

  • Web Search — Search the web for up-to-date information.
  • File Search — Search and retrieve information from uploaded documents.
  • Code Interpreter — Generate and execute Python code to solve problems.
  • Image Generation — Generate images from text prompts.
  • Remote MCP — Connect to tools exposed by remote MCP servers.

Each of these tools deserves its own Spring AI recipe, and we’ll eventually explore them all. But we’re going to start with one of the most fascinating: Code Interpreter.

Code Interpreter gives an LLM something it doesn’t ordinarily have: the ability to write and execute code to solve problems. Rather than relying entirely on its own reasoning to perform calculations, analyze data, or run simulations, the model can generate Python code, execute it in OpenAI’s hosted environment, and incorporate the results into its response.

And with Spring AI 2.1.0-RC1, enabling this capability is surprisingly easy.

Let’s see how it works. Along the way, we’ll even peek behind the curtain to see the Python code that OpenAI generates and executes.

Setting Up the Application

We’ll start with a fresh Spring Boot application containing a simple ApplicationRunner that uses ChatClient to interact with OpenAI.

Using the Spring Boot Initializr (or your favorite IDE), create a new Spring Boot project with the OpenAI starter dependency. The dependencies section of build.gradle should look something like this:

dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-openai'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Next, make sure you’re using Spring AI 2.1.0-RC1 or newer. The relevant portion of build.gradle should look like this:

ext {
set('springAiVersion', "2.1.0-RC1")
}

Spring AI 2.1.0 introduces support for OpenAI’s Responses API, including its hosted tools. To enable the Responses API and Code Interpreter, add these two properties to application.properties:

spring.ai.openai.chat.api=responses
spring.ai.openai.responses.hosted-tools.code-interpreter.enabled=true

The first property tells Spring AI to use the Responses API instead of the Chat Completions API, as we saw in an earlier recipe. The second property makes OpenAI’s hosted Code Interpreter tool available to the model.

That’s it! There’s no tool implementation to write, no Python environment to configure, and no additional service to deploy.

Of course, we still need to give the model something to do.

Putting Code Interpreter to Work

For our example, let’s give the model a problem that’s easy to describe but tedious to solve manually.

Suppose we roll five six-sided dice 100,000 times. We’d like to know which total occurs most frequently and the probability of rolling a total of at least 25. This is a perfect job for a Python simulation.

Add the following ApplicationRunner bean to your application:

@Bean
ApplicationRunner go(ChatClient.Builder chatClientBuilder) {
return args -> {
var chatClient = chatClientBuilder.build();
var task = """
Use Python to simulate rolling five six-sided dice
100,000 times. Report the most common total and
the probability of rolling at least 25.
""";
var answer = chatClient.prompt()
.user(task)
.call()
.content();
System.out.println(answer);
};
}

There’s nothing particularly remarkable about this code. We create a ChatClient, give it a prompt, and print the response. Notice, however, that we haven’t written any Python code. We’ve simply asked the model to perform a simulation using Python.

When you run the application, you should get a response resembling this:

I simulated 100,000 trials of rolling five six-sided dice.

Results:
- Most common total: 18 (occurred 10,144 times ≈ 10.14% of trials)
- Estimated probability of rolling at least 25: 0.03318 (≈ 3.318%)

Your results may differ because the simulation involves random dice rolls.

That’s pretty impressive! We asked the model to run a simulation, and it returned the results without us having to write a single line of Python.

But how do we know that it actually executed Python code? Couldn’t the model have simply generated a plausible-looking answer?

We don’t have to take its word for it. Spring AI gives us a way to find out.

Verifying That Code Interpreter Was Used

So far, we’ve used ChatClient's content() method to retrieve the model's answer as a String. But that only gives us the text of the response. To find out what happened behind the scenes, we need the complete ChatResponse.

Change the ChatClient call to this:

var chatResponse = chatClient.prompt()
.user(task)
.call()
.chatResponse();

var answer = chatResponse.getResult().getOutput().getText();

Instead of calling content(), we're now calling chatResponse() to retrieve the full response, including its metadata. We can still get the answer as text, but now we can also inspect additional information about the response.

In particular, Spring AI’s Responses API support provides metadata describing which hosted tools were invoked. Add the following code:

var toolCalls = chatResponse.getResult()
.getMetadata()
.get(OpenAiResponsesMetadata.HOSTED_TOOL_CALLS);

System.out.println("Hosted tool calls: " + toolCalls);

Run the application again, and you should see something like this:

Hosted tool calls: [{type=code_interpreter_call,
id=ci_04367fd674f5fd91016adfbc2e267887d1873bd42ddb04f22e,
status=completed}]

There it is! The metadata tells us that a hosted tool was invoked. More specifically, its type is code_interpreter_call, confirming that the Code Interpreter tool was used. The status=completed value indicates that the tool call finished.

Get Craig Walls’s stories in your inbox

Join Medium for free to get updates from this writer.

That’s enough to establish that the model didn’t merely invent an answer. It actually invoked OpenAI’s Code Interpreter tool. But wouldn’t it be interesting to see the Python code it executed?

As it turns out, we can do that, too.

Peeking at the Generated Python

One of the additions in Spring AI 2.1.0-M1 was support for message parts. Rather than treating every model response as a single block of text, Spring AI can represent a response as a collection of parts, potentially containing different kinds of content.

This is especially useful when working with the Responses API, where the response may contain more than just the model’s final answer.

In our case, the response includes information about the Code Interpreter invocation, including the Python code that was generated. That information is available in a message part of type UnknownPart.

Despite its name, UnknownPart isn't necessarily something unexpected or erroneous. It's a way for Spring AI to preserve response content that doesn't have a dedicated message-part representation. We can use that to extract the Python code.

Add the following code after retrieving the ChatResponse:

var mapper = new ObjectMapper();

for (var part : chatResponse.getResult().getOutput().getParts()) {
if (part instanceof UnknownPart unknownPart
&& "code_interpreter_call".equals(unknownPart.kind())) {
var json = mapper.readTree(unknownPart.rawJson());
var code = json.path("code").asText();
System.out.println(code);
}
}

Let’s break down what’s happening here. First, we retrieve all of the message parts from the response:

chatResponse.getResult().getOutput().getParts()

Then we look for parts that are instances of UnknownPart and whose kind() is code_interpreter_call.

That second condition is important because other kinds of content could also be represented as UnknownPart objects. We only want the ones associated with Code Interpreter.

Once we’ve found a matching part, we retrieve its raw JSON:

unknownPart.rawJson()

We then use Jackson’s ObjectMapper to parse the JSON and extract its code property:

var json = mapper.readTree(unknownPart.rawJson());
var code = json.path("code").asText();

Finally, we print the extracted Python code.

And now comes the really interesting part.

Seeing What OpenAI Actually Executed

Run the application again, and in addition to the model’s answer, you should see Python code resembling this:

# Simulate rolling five six-sided dice 100,000 times and report results

import numpy as np

np.random.seed(0) # for reproducibility
n_trials = 100_000
rolls = np.random.randint(1, 7, size=(n_trials, 5)) # values 1..6
totals = rolls.sum(axis=1)

# Frequency of each possible total (5..30)
unique, counts = np.unique(totals, return_counts=True)
freq = dict(zip(unique, counts))

# Most common total(s)
max_count = counts.max()
modes = unique[counts == max_count]
mode_relative = max_count / n_trials

# Probability of rolling at least 25
prob_at_least_25 = (totals >= 25).mean()

# Print results
print(f"Simulated {n_trials} trials of 5d6.\n")
print("Total : Count")
for t, c in zip(unique, counts):
print(f"{t:5d} : {c:6d}")
print("\nMost common total(s):", list(modes))
print(f"Count for most common total: {max_count} ({mode_relative:.4%})")
print(f"Estimated probability of rolling at least 25: {prob_at_least_25:.4%} ({prob_at_least_25:.6f})")

Look at what happened here. OpenAI generated a complete Python program that uses NumPy to simulate 100,000 rolls of five six-sided dice. The program calculates the frequency of each possible total, identifies the most common total, and estimates the probability of rolling at least 25. It then prints those results so that the model can use them in its final answer.

And we didn’t have to write or execute any of that code ourselves!

The exact Python code may vary from one run to another. The model might choose a different approach, use different libraries, or organize the code differently. It might even make multiple Code Interpreter calls while working toward an answer.

But the important thing is that the model isn’t limited to generating text about how a problem could be solved. It can actually write code, execute it, and use the results. All of that happens in OpenAI’s hosted environment, without requiring a local Python installation.

Do You Have to Ask for Python?

You may have noticed that our prompt explicitly instructed the model to use Python:

Use Python to simulate rolling five six-sided dice
100,000 times. Report the most common total and
the probability of rolling at least 25.

But that isn’t strictly necessary. Once Code Interpreter is enabled, the model can decide to invoke it when it determines that executing Python would be helpful.

For example, we could have simply asked:

Simulate rolling five six-sided dice 100,000 times.
Report the most common total and the probability
of rolling at least 25.

The model might recognize that this is a task better suited to a Python simulation than to reasoning alone and invoke Code Interpreter automatically.

However, enabling a hosted tool doesn’t guarantee that the model will use it. By explicitly asking for Python, we’re strongly encouraging the model to invoke Code Interpreter rather than attempting to answer without it.

That’s particularly useful for a recipe like this one, where our goal is to demonstrate the tool and inspect the code it generates. In a real application, you might prefer to let the model decide when Code Interpreter is appropriate.

Is Python the Only Option?

One limitation worth mentioning is that Code Interpreter executes Python, and only Python. You can’t ask it to generate and execute Java, JavaScript, Ruby, or another programming language instead. For example, you can’t instruct Code Interpreter to run a Node.js application or compile and execute a Java class.

If you need to execute another language, you’ll have to provide your own execution environment, perhaps through a custom Spring AI tool or an MCP server.

But for many computational tasks, Python is an excellent choice. Its extensive collection of libraries makes it particularly well suited for calculations, simulations, statistical analysis, and data processing. And since you’re not writing the Python code yourself, the language restriction may not matter much.

Wrapping Up

OpenAI’s hosted Code Interpreter tool gives Spring AI applications a powerful new capability: the ability to generate and execute Python code as part of producing an answer.

And thanks to Spring AI 2.1.0’s support for the Responses API, getting started requires little more than two configuration properties.

In this recipe, we saw how to enable Code Interpreter, use it to run a dice simulation, verify that the tool was invoked, and even retrieve the Python code that OpenAI generated. That last part is particularly interesting. Instead of simply trusting that the model performed a calculation correctly, we can inspect the code it used to arrive at its answer.

Code Interpreter is just one of several hosted tools available through the Responses API. In upcoming recipes, we’ll explore some of the others and see what they bring to Spring AI applications.

For now, though, it’s remarkable how much we accomplished without writing a single line of Python ourselves.

Get the code for this and other Spring AI recipes at https://github.com/habuma/spring-ai-recipes.

If you’re exploring Spring AI, I’ve written a book (Spring AI in Action) that covers the core foundations — and I’ll continue sharing newer patterns like this here as the ecosystem evolves. You can find my book on the publisher’s website, at Amazon, or anywhere you buy technical books.

Press enter or click to view image in full size

--

--

Craig Walls
Craig Walls