Spring AI Structured Output Example
Spring AI offers a seamless integration for AI-driven applications, especially when interacting with large language models (LLMs). One key feature is the ability to generate structured outputs, simplifying the interaction between our application and AI services. In this guide, we will explore how Spring AI handles structured output, helping us create AI-powered applications that provide easily consumable results, such as lists, maps, or even custom beans.
1. Overview of Structured Output: ChatModel and Converters
The Structured Output API in Spring AI allows us to control how AI-generated responses are parsed and returned to the application. This API lets us turn AI-generated text into structured data like Java beans, lists, or maps.
The core component that allows us to send prompts to AI models in Spring AI is the ChatModel
interface:
public interface ChatModel extends Model<Prompt, ChatResponse> { default String call(String message) { } @Override ChatResponse call(Prompt prompt); }
The call()
method is responsible for sending a message to the AI model and receiving a response. While the call()
method can accept a string, using a Prompt
object is more practical as it supports multiple messages and parameters to control the model’s creativity.
The ChatModel
can be autowired for use in our application. To obtain structured output from the AI, Spring AI provides tools that wrap the ChatModel
using the Structured Output API, with the main interface being StructuredOutputConverter
.
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {}
This interface combines two others, starting with FormatProvider
:
public interface FormatProvider { String getFormat(); }
The second interface, Converter
, handles parsing the AI’s response into a specific data structure:
@FunctionalInterface public interface Converter<S, T> { @Nullable T convert(S source); }
2. Exploring Available Converters in Spring AI
Spring AI provides several built-in converters that make handling structured outputs straightforward. These converters enable us to transform raw AI responses into usable Java objects easily:
- BeanOutputConverter: For converting output into custom Java beans.
- ListOutputConverter: For handling lists.
- MapOutputConverter: For dealing with maps.
2.1 Project Configuration with OpenAI:
To integrate OpenAI with a Spring AI project, you must configure your API keys. Start by signing into your OpenAI account or creating a new one if needed. Once logged in, go to the API section of your OpenAI dashboard and click on “Create New Secret Key.” A pop-up will display your new API key, which you should copy immediately as it will not be retrievable later. This key is essential for authenticating your Spring AI project with OpenAI.
Additionally, since OpenAI’s API services are paid, you need to add a balance to your account to use them. Navigate to the Billing section of your OpenAI dashboard, choose a payment method and add funds. In your application.properties
file, configure the API key with the line: spring.ai.openai.api-key=your-api-key-here
, and set the application name and model like this:
spring.ai.openai.api-key=api-key spring.ai.openai.chat.options.model=gpt-4o-mini
2.2 Bean Configuration
Set up a configuration class to define the beans required for interacting with OpenAI’s API. Below is an example of the configuration class:
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AIConfig { @Value("${spring.ai.openai.api-key}") private String apiKey; @Bean OpenAiApi openAiApi() { return new OpenAiApi(apiKey); } @Bean ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } }
The code snippet defines a Spring configuration class named AIConfig
. It uses the @Value
annotation to inject the OpenAI API key from the application properties into the apiKey
field. The openAiApi()
method initializes an OpenAiApi
instance using the injected API key, while the chatClient()
method sets up a ChatClient
bean using a builder pattern.
The rest of this article will explore how each of these converters functions, along with examples.
3. Using BeanOutputConverter for Custom Java Beans
The BeanOutputConverter
is designed for parsing structured AI outputs into custom Java beans. This is useful when an application expects a response that maps directly to a specific class.
import java.util.Map; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AIService { @Autowired OpenAiChatModel aiChatModel; public MovieRecommendation returnStructuredResponse(String movie) { BeanOutputConverter<MovieRecommendation> beanOutputConverter = new BeanOutputConverter<>(MovieRecommendation.class); String format = beanOutputConverter.getFormat(); String template = """ return movie details {movie}. {format} """; PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("movie", movie, "format", format)); Prompt prompt = new Prompt(promptTemplate.createMessage()); Generation generation = aiChatModel.call(prompt).getResult(); return beanOutputConverter.convert(generation.getOutput().getContent()); } }
This code snippet defines a method returnStructuredResponse
that uses a BeanOutputConverter
to handle AI-generated responses. It first creates a BeanOutputConverter
for MovieRecommendation
and constructs a prompt template to request movie details in a specific format.
The method then generates a prompt from the template and sends it to the aiChatModel
using its call
method. The resulting response is converted into a MovieRecommendation
object using the BeanOutputConverter
.
Below is the MovieRecommendation
class:
// Define your custom bean public class MovieRecommendation { private String title; private String genre; private int releaseYear; // Getters and setters }
Here’s how to use a Java record
for MovieRecommendation
:
public record MovieRecommendation(String title, String genre, int releaseYear) {}
4. Handling Collections with MapOutputConverter and ListOutputConverter
When dealing with collections of structured data, Spring AI provides two powerful converters: the ListOutputConverter
and MapOutputConverter
. These converters are helpful when we expect multiple entries or key-value
pairs in our AI output.
4.1 Example Using ListOutputConverter
Here is an example of how to convert AI output into a list:
public List<String> returnListStructuredResponse(String movie) { ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService()); String format = listOutputConverter.getFormat(); String template = """ return all movie details {movie}. {format} """; Generation generation = aiChatModel .call(new Prompt( new PromptTemplate(template, Map.of("movie", movie, "format", format)).createMessage())) .getResult(); return listOutputConverter.convert(generation.getOutput().getContent()); }
In this case, the method returnListStructuredResponse
retrieves a list of movie details from an AI model. It initializes a ListOutputConverter
with a default conversion service DefaultConversionService()
to handle the AI response.
The DefaultConversionService()
is used to enable type conversion in the ListOutputConverter
, which processes the AI model’s string response and converts it into a structured List<String>
. The DefaultConversionService
provides pre-configured converters to handle common type conversions. In this case, it allows the AI’s textual response about movie details to be transformed into a list format that can be easily managed within the application.
4.2 Example Using MapOutputConverter
If the AI response is in the form of key-value
pairs, the MapOutputConverter
can be used:
public Map<String, Object> returnMapStructuredResponse(String movie) { MapOutputConverter outputConverter = new MapOutputConverter(); String format = outputConverter.getFormat(); String template = """ return all movie details {movie}. {format} """; Prompt prompt = new Prompt(new PromptTemplate(template, Map.of("movie", movie, "format", format)).createMessage()); Generation generation = aiChatModel.call(prompt).getResult(); return outputConverter.convert(generation.getOutput().getContent()); } }
Here, the returnMapStructuredResponse
method gets movie details from the AI and turns them into a map. It uses a MapOutputConverter
to change the AI’s reply into a Map<String, Object>
. The method creates a prompt asking for movie details, sends it to the AI with aiChatModel.call()
, and then converts the reply into a map.
5. Expose API Endpoints
Here’s how to set up a REST controller to provide endpoints for structured output in different formats:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AIController { @Autowired AIService aiService; @GetMapping("/bean-output-converter") ResponseEntity<Object> beanOutputConverter(@RequestParam(value = "movie") String movie) { return new ResponseEntity<>(aiService.returnStructuredResponse(movie), HttpStatus.OK); } @GetMapping("/list-output-converter") ResponseEntity<Object> listOutputConverter(@RequestParam(value = "movie") String movie) { return new ResponseEntity<>(aiService.returnListStructuredResponse(movie), HttpStatus.OK); } @GetMapping("/map-output-converter") ResponseEntity<Object> mapOutputConverter(@RequestParam(value = "movie") String movie) { return new ResponseEntity<>(aiService.returnMapStructuredResponse(movie), HttpStatus.OK); } }
Testing Structured Output
To test the structured output, start the Spring Boot application and use Postman or another API testing tool to call the endpoints. Open Postman and create new GET requests with the following URLs:
- For bean output conversion:
http://localhost:8080/api/bean-output-converter?movie=Inception
- For list output conversion:
http://localhost:8080/api/list-output-converter?movie=Inception
- For map output conversion:
http://localhost:8080/api/map-output-converter?movie=Inception
Replace Inception
with any movie name to test the different endpoints.
6. Conclusion
In this article, we explored how to leverage Spring AI for handling structured output from AI models. We covered the essentials of configuring your environment, including setting up API keys. We then demonstrated how to create and configure beans for interacting with the AI, followed by examples of converting AI responses into structured data formats such as beans, lists, and maps.
7. Download the Source Code
This article covered structure output in Spring Artificial Intelligence.
You can download the full source code of this example here: spring artificial intelligence structure output