Enterprise Java

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.

Download
You can download the full source code of this example here: spring artificial intelligence structure output

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button