CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-starter-model-openai

Spring Boot Starter for OpenAI integration providing auto-configuration for chat completion, embeddings, image generation, audio speech synthesis, audio transcription, and content moderation models. Includes high-level ChatClient API and conversation memory support.

Overview
Eval results
Files

chat-client.mddocs/reference/

ChatClient

The ChatClient provides a high-level, fluent DSL for building and executing chat interactions with built-in advisor support, observation logging, and simplified API.

Import

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.Advisor;
import org.springframework.ai.chat.model.ChatOptions;
import org.springframework.beans.factory.annotation.Autowired;

API

ChatClient.Builder

package org.springframework.ai.chat.client;

import org.springframework.core.io.Resource;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public interface Builder {
    // Set default system message
    Builder defaultSystem(String text);
    Builder defaultSystem(Resource text);
    Builder defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer);

    // Set default user message
    Builder defaultUser(String text);
    Builder defaultUser(Resource text);
    Builder defaultUser(Consumer<PromptUserSpec> userSpecConsumer);

    // Set default messages
    Builder defaultMessages(Message... messages);
    Builder defaultMessages(List<Message> messages);

    // Set default advisors
    Builder defaultAdvisors(Advisor... advisors);
    Builder defaultAdvisors(List<Advisor> advisors);
    Builder defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer);

    // Set default advisor parameters
    Builder defaultAdvisorParams(Map<String, Object> params);

    // Set default options
    Builder defaultOptions(ChatOptions options);

    // Set default tool support
    Builder defaultToolNames(String... toolNames);
    Builder defaultTools(Object... tools);
    Builder defaultToolCallbacks(FunctionCallback... functionCallbacks);
    Builder defaultToolCallbacks(List<FunctionCallback> functionCallbacks);
    Builder defaultToolContext(Map<String, Object> toolContext);

    // Build the ChatClient
    ChatClient build();
}

ChatClient

package org.springframework.ai.chat.client;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.observation.conventions.AiObservationConventions;
import io.micrometer.observation.ObservationRegistry;

public interface ChatClient {
    // Static factory methods
    static Builder builder(ChatModel chatModel);
    static ChatClient create(ChatModel chatModel);
    static ChatClient create(ChatModel chatModel, ObservationRegistry observationRegistry);
    static ChatClient create(
        ChatModel chatModel,
        ObservationRegistry observationRegistry,
        ChatClientObservationConvention chatClientObservationConvention,
        AdvisorObservationConvention advisorObservationConvention
    );

    // Start building a prompt
    ChatClientRequestSpec prompt();

    // Start building a prompt with text
    ChatClientRequestSpec prompt(String text);

    // Advanced prompt with Prompt object
    ChatClientRequestSpec prompt(Prompt prompt);

    // Create a mutable copy of this ChatClient
    Builder mutate();
}

ChatClientRequestSpec

package org.springframework.ai.chat.client;

import org.springframework.ai.model.function.FunctionCallback;
import java.util.function.Consumer;

public interface ChatClientRequestSpec {
    // Add system message
    ChatClientRequestSpec system(String text);
    ChatClientRequestSpec system(Resource text);
    ChatClientRequestSpec system(Consumer<PromptSystemSpec> systemSpecConsumer);

    // Add user message
    ChatClientRequestSpec user(String text);
    ChatClientRequestSpec user(Resource text);
    ChatClientRequestSpec user(Consumer<PromptUserSpec> userSpecConsumer);

    // Add messages
    ChatClientRequestSpec messages(Message... messages);
    ChatClientRequestSpec messages(List<Message> messages);

    // Add advisors
    ChatClientRequestSpec advisors(Advisor... advisors);
    ChatClientRequestSpec advisors(List<Advisor> advisors);
    ChatClientRequestSpec advisors(Consumer<AdvisorSpec> advisorSpecConsumer);

    // Set options
    ChatClientRequestSpec options(ChatOptions options);

    // Tool support
    ChatClientRequestSpec toolNames(String... toolNames);
    ChatClientRequestSpec tools(Object... tools);
    ChatClientRequestSpec toolCallbacks(FunctionCallback... functionCallbacks);
    ChatClientRequestSpec toolCallbacks(List<FunctionCallback> functionCallbacks);
    ChatClientRequestSpec toolContext(Map<String, Object> toolContext);

    // Execute the call
    CallResponseSpec call();

    // Stream the response
    StreamResponseSpec stream();

    // Create a mutable copy
    ChatClientRequestSpec mutate();
}

CallResponseSpec

package org.springframework.ai.chat.client;

import org.springframework.ai.converter.StructuredOutputConverter;

public interface CallResponseSpec {
    // Get full response
    ChatResponse chatResponse();

    // Get ChatClient-specific response wrapper
    ChatClientResponse chatClientResponse();

    // Get response entity (with metadata)
    ResponseEntity<ChatResponse> responseEntity();

    // Get content string
    String content();

    // Get content as ResponseEntity
    ResponseEntity<String> contentEntity();

    // Get entity (deserialize response)
    <T> T entity(Class<T> type);
    <T> T entity(ParameterizedTypeReference<T> type);
    <T> T entity(StructuredOutputConverter<T> structuredOutputConverter);

    // Get entity as ResponseEntity
    <T> ResponseEntity<T> entityEntity(Class<T> type);
    <T> ResponseEntity<T> entityEntity(ParameterizedTypeReference<T> type);
}

StreamResponseSpec

package org.springframework.ai.chat.client;

import reactor.core.publisher.Flux;

public interface StreamResponseSpec {
    // Stream full responses
    Flux<ChatResponse> chatResponse();

    // Stream ChatClient-specific responses
    Flux<ChatClientResponse> chatClientResponse();

    // Stream content
    Flux<String> content();
}

PromptUserSpec

package org.springframework.ai.chat.client;

import org.springframework.ai.chat.prompt.ChatOptionsBuilder;
import org.springframework.util.MimeType;
import java.net.URL;
import java.util.Map;

public interface PromptUserSpec {
    // Set text content
    PromptUserSpec text(String text);
    PromptUserSpec text(Resource text);

    // Set template parameters
    PromptUserSpec params(Map<String, Object> params);
    PromptUserSpec param(String key, Object value);

    // Add media content
    PromptUserSpec media(MimeType mimeType, String data);
    PromptUserSpec media(MimeType mimeType, URL url);
    PromptUserSpec media(MimeType mimeType, Resource resource);

    // Set message metadata
    PromptUserSpec metadata(Map<String, Object> metadata);
    PromptUserSpec metadata(String key, Object value);

    // Options builder access
    ChatOptionsBuilder options();
}

PromptSystemSpec

package org.springframework.ai.chat.client;

import java.util.Map;

public interface PromptSystemSpec {
    // Set text content
    PromptSystemSpec text(String text);
    PromptSystemSpec text(Resource text);

    // Set template parameters
    PromptSystemSpec params(Map<String, Object> params);
    PromptSystemSpec param(String key, Object value);

    // Set message metadata
    PromptSystemSpec metadata(Map<String, Object> metadata);
    PromptSystemSpec metadata(String key, Object value);
}

AdvisorSpec

package org.springframework.ai.chat.client;

import java.util.Map;

public interface AdvisorSpec {
    // Set advisor parameters
    AdvisorSpec params(Map<String, Object> params);
    AdvisorSpec param(String key, Object value);
}

Usage Examples

Basic ChatClient Setup

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ChatClientService {

    private final ChatClient chatClient;

    @Autowired
    public ChatClientService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    public String chat(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
}

ChatClient with Default System Message

@Autowired
public ChatClientService(ChatClient.Builder chatClientBuilder) {
    this.chatClient = chatClientBuilder
        .defaultSystem("You are a helpful assistant that provides concise answers.")
        .build();
}

public String chat(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .call()
        .content();
}

ChatClient with Default Options

import org.springframework.ai.openai.OpenAiChatOptions;

@Autowired
public ChatClientService(ChatClient.Builder chatClientBuilder) {
    OpenAiChatOptions options = OpenAiChatOptions.builder()
        .model("gpt-4o")
        .temperature(0.7)
        .maxTokens(500)
        .build();

    this.chatClient = chatClientBuilder
        .defaultOptions(options)
        .build();
}

Simple Prompt

public String simpleChat(String question) {
    return chatClient.prompt(question)
        .call()
        .content();
}

System and User Messages

public String chatWithSystem(String systemPrompt, String userMessage) {
    return chatClient.prompt()
        .system(systemPrompt)
        .user(userMessage)
        .call()
        .content();
}

Per-Call Options

import org.springframework.ai.openai.OpenAiChatOptions;

public String chatWithOptions(String userMessage) {
    OpenAiChatOptions options = OpenAiChatOptions.builder()
        .temperature(0.9)
        .build();

    return chatClient.prompt()
        .user(userMessage)
        .options(options)
        .call()
        .content();
}

Get Full ChatResponse

import org.springframework.ai.chat.model.ChatResponse;

public ChatResponse getFullResponse(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .call()
        .chatResponse();
}

public void analyzeResponse(String userMessage) {
    ChatResponse response = getFullResponse(userMessage);

    String content = response.getResult().getOutput().getContent();
    Integer promptTokens = response.getMetadata().getUsage().getPromptTokens();
    Integer completionTokens = response.getMetadata().getUsage().getGenerationTokens();

    System.out.println("Content: " + content);
    System.out.println("Prompt tokens: " + promptTokens);
    System.out.println("Completion tokens: " + completionTokens);
}

Streaming Responses

import reactor.core.publisher.Flux;

public Flux<String> streamChat(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .stream()
        .content();
}

public void streamChatToConsole(String userMessage) {
    streamChat(userMessage)
        .doOnNext(System.out::print)
        .blockLast();
}

Stream Full ChatResponse

import org.springframework.ai.chat.model.ChatResponse;

public Flux<ChatResponse> streamFullResponse(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .stream()
        .chatResponse();
}

Entity Mapping (Structured Output)

public record Person(String name, int age, String occupation) {}

public Person extractPerson(String text) {
    return chatClient.prompt()
        .user("Extract person information from: " + text)
        .call()
        .entity(Person.class);
}

// Usage
public void demonstrateEntityMapping() {
    Person person = extractPerson("John Doe is a 30-year-old software engineer.");
    System.out.println(person.name()); // "John Doe"
    System.out.println(person.age()); // 30
}

Complex Entity Mapping

import org.springframework.core.ParameterizedTypeReference;
import java.util.List;

public record Book(String title, String author, int year) {}

public List<Book> extractBooks(String text) {
    return chatClient.prompt()
        .user("Extract all books mentioned in: " + text)
        .call()
        .entity(new ParameterizedTypeReference<List<Book>>() {});
}

Using Advisors

import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;

@Autowired
public ChatClientService(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
        .build();
}

public String chatWithMemory(String conversationId, String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .advisors(advisor -> advisor.param("conversationId", conversationId))
        .call()
        .content();
}

Multiple Advisors

import org.springframework.ai.rag.preretrieval.query.expansion.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.rag.preretrieval.query.expansion.advisor.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.vectorstore.VectorStore;

@Autowired
public ChatClientService(
    ChatClient.Builder chatClientBuilder,
    VectorStore vectorStore,
    ChatMemory chatMemory
) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(
            new QuestionAnswerAdvisor(vectorStore),
            new MessageChatMemoryAdvisor(chatMemory)
        )
        .build();
}

Template Variables

public String chatWithTemplate(String name, String topic) {
    return chatClient.prompt()
        .user("Tell {name} about {topic}")
        .advisors(advisor -> advisor
            .param("name", name)
            .param("topic", topic))
        .call()
        .content();
}

Loading Prompts from Resources

import org.springframework.core.io.Resource;
import org.springframework.beans.factory.annotation.Value;

@Value("classpath:prompts/system-message.txt")
private Resource systemPrompt;

@Value("classpath:prompts/user-template.txt")
private Resource userPrompt;

public String chatWithResources() {
    return chatClient.prompt()
        .system(systemPrompt)
        .user(userPrompt)
        .call()
        .content();
}

Tool/Function Calling

import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Description;

// Define a tool function
@Bean
@Description("Get the current weather for a location")
public FunctionCallback weatherFunction() {
    return FunctionCallback.builder()
        .function("getCurrentWeather", this::getCurrentWeather)
        .description("Get the current weather for a location")
        .inputType(WeatherRequest.class)
        .build();
}

record WeatherRequest(String location, String unit) {}
record WeatherResponse(double temperature, String conditions) {}

private WeatherResponse getCurrentWeather(WeatherRequest request) {
    // Implementation
    return new WeatherResponse(72.0, "sunny");
}

// Use tool in ChatClient
public String chatWithTools(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .tools(weatherFunction())
        .call()
        .content();
}

// Or specify tool names from registered beans
public String chatWithToolNames(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .toolNames("getCurrentWeather", "getStockPrice")
        .call()
        .content();
}

Advanced User Message Configuration

import org.springframework.util.MimeType;

public String chatWithMedia(String question, Resource imageResource) {
    return chatClient.prompt()
        .user(userSpec -> userSpec
            .text("Describe this image: {question}")
            .param("question", question)
            .media(MimeType.valueOf("image/jpeg"), imageResource)
            .metadata("source", "user-upload"))
        .call()
        .content();
}

Advanced System Message Configuration

public String chatWithSystemParams(String role, String expertise) {
    return chatClient.prompt()
        .system(systemSpec -> systemSpec
            .text("You are a {role} with expertise in {field}.")
            .param("role", role)
            .param("field", expertise)
            .metadata("version", "2.0"))
        .user("Help me with my problem")
        .call()
        .content();
}

Consumer-based Advisor Configuration

public String chatWithAdvisorParams(String conversationId, String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .advisors(advisorSpec -> advisorSpec
            .param("conversationId", conversationId)
            .param("maxMessages", 10))
        .call()
        .content();
}

Using Static Factory Methods

import org.springframework.ai.chat.model.ChatModel;
import io.micrometer.observation.ObservationRegistry;

@Autowired
private ChatModel chatModel;

@Autowired
private ObservationRegistry observationRegistry;

public void createChatClients() {
    // Create with just ChatModel
    ChatClient simpleClient = ChatClient.create(chatModel);

    // Create with ObservationRegistry
    ChatClient observableClient = ChatClient.create(chatModel, observationRegistry);

    // Create with builder
    ChatClient customClient = ChatClient.builder(chatModel)
        .defaultSystem("You are a helpful assistant")
        .build();
}

Mutating ChatClient

public class ChatClientManager {
    private final ChatClient baseChatClient;

    public ChatClientManager(ChatClient baseChatClient) {
        this.baseChatClient = baseChatClient;
    }

    public ChatClient createSpecializedClient() {
        return baseChatClient.mutate()
            .defaultSystem("You are a specialized technical expert")
            .defaultAdvisors(new SimpleLoggerAdvisor())
            .build();
    }
}

Getting ChatClientResponse

import org.springframework.ai.chat.client.ChatClientResponse;

public void analyzeChatClientResponse(String userMessage) {
    ChatClientResponse response = chatClient.prompt()
        .user(userMessage)
        .call()
        .chatClientResponse();

    String content = response.content();
    Map<String, Object> metadata = response.metadata();
    AssistantMessage message = response.assistantMessage();

    System.out.println("Content: " + content);
    System.out.println("Metadata: " + metadata);
}

Getting ResponseEntity

import org.springframework.ai.chat.client.ResponseEntity;
import org.springframework.ai.chat.model.ChatResponse;

public void analyzeResponseEntity(String userMessage) {
    ResponseEntity<ChatResponse> responseEntity = chatClient.prompt()
        .user(userMessage)
        .call()
        .responseEntity();

    ChatResponse body = responseEntity.getBody();
    int statusCode = responseEntity.getStatusCode().value();
    Map<String, List<String>> headers = responseEntity.getHeaders();

    System.out.println("Status: " + statusCode);
}

// Get content as ResponseEntity
public void getContentEntity(String userMessage) {
    ResponseEntity<String> contentEntity = chatClient.prompt()
        .user(userMessage)
        .call()
        .contentEntity();

    String content = contentEntity.getBody();
    int statusCode = contentEntity.getStatusCode().value();
}

Structured Output with Custom Converter

import org.springframework.ai.converter.StructuredOutputConverter;

public class CustomOutputConverter implements StructuredOutputConverter<Person> {
    @Override
    public Person convert(String text) {
        // Custom parsing logic
        return parsePersonFromText(text);
    }

    @Override
    public String getFormat() {
        return "name:string, age:integer, occupation:string";
    }
}

public Person extractPersonWithConverter(String text) {
    return chatClient.prompt()
        .user("Extract person information from: " + text)
        .call()
        .entity(new CustomOutputConverter());
}

Working with Messages

import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.messages.SystemMessage;
import java.util.List;

public String chatWithMessages(List<Message> conversationHistory, String newMessage) {
    return chatClient.prompt()
        .messages(conversationHistory)
        .messages(new UserMessage(newMessage))
        .call()
        .content();
}

// Or use varargs
public String chatWithMultipleMessages(String systemText, String userText) {
    return chatClient.prompt()
        .messages(
            new SystemMessage(systemText),
            new UserMessage(userText)
        )
        .call()
        .content();
}

Using Tool Context

import java.util.Map;

public String chatWithToolContext(String userMessage, Map<String, Object> context) {
    return chatClient.prompt()
        .user(userMessage)
        .toolNames("databaseQuery", "apiCall")
        .toolContext(Map.of(
            "databaseConnection", context.get("dbConn"),
            "apiKey", context.get("apiKey"),
            "environment", "production"
        ))
        .call()
        .content();
}

Default Tool Configuration

import org.springframework.ai.model.function.FunctionCallback;

@Autowired
public ChatClientService(
    ChatClient.Builder chatClientBuilder,
    List<FunctionCallback> functionCallbacks
) {
    this.chatClient = chatClientBuilder
        .defaultToolCallbacks(functionCallbacks)
        .defaultToolContext(Map.of("environment", "production"))
        .build();
}

SafeGuard Advisor

import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.ai.openai.OpenAiModerationModel;

@Autowired
public ChatClientService(
    ChatClient.Builder chatClientBuilder,
    OpenAiModerationModel moderationModel
) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(new SafeGuardAdvisor(moderationModel))
        .build();
}

public String moderatedChat(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .call()
        .content();
}

Structured Output Validation Advisor

import org.springframework.ai.chat.client.advisor.StructuredOutputValidationAdvisor;

@Autowired
public ChatClientService(ChatClient.Builder chatClientBuilder) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(new StructuredOutputValidationAdvisor())
        .build();
}

public Person getValidatedPerson(String text) {
    return chatClient.prompt()
        .user("Extract person information from: " + text)
        .call()
        .entity(Person.class);
}

Tool Call Advisor

import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;

@Autowired
public ChatClientService(ChatClient.Builder chatClientBuilder) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(new ToolCallAdvisor())
        .build();
}

public String chatWithToolCallHandling(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .toolNames("getCurrentWeather", "getStockPrice")
        .call()
        .content();
}

Streaming ChatClientResponse

import reactor.core.publisher.Flux;

public Flux<ChatClientResponse> streamChatClientResponse(String userMessage) {
    return chatClient.prompt()
        .user(userMessage)
        .stream()
        .chatClientResponse();
}

public void streamWithMetadata(String userMessage) {
    streamChatClientResponse(userMessage)
        .doOnNext(response -> {
            System.out.println("Content: " + response.content());
            System.out.println("Metadata: " + response.metadata());
        })
        .blockLast();
}

Prompt with Prompt Object

import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.openai.OpenAiChatOptions;

public String chatWithPromptObject(String userMessage) {
    Prompt prompt = new Prompt(
        List.of(new UserMessage(userMessage)),
        OpenAiChatOptions.builder()
            .model("gpt-4o")
            .temperature(0.7)
            .build()
    );

    return chatClient.prompt(prompt)
        .call()
        .content();
}

Error Handling

import org.springframework.ai.openai.api.OpenAiApiException;

public String safeChat(String userMessage) {
    try {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    } catch (OpenAiApiException e) {
        return "Error: " + e.getMessage();
    }
}

Conversation Context

import java.util.ArrayList;
import java.util.List;

public class ConversationManager {
    private final ChatClient chatClient;
    private final List<String> conversationHistory = new ArrayList<>();

    public ConversationManager(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    public String chat(String userMessage) {
        String response = chatClient.prompt()
            .user(buildConversationPrompt(userMessage))
            .call()
            .content();

        conversationHistory.add("User: " + userMessage);
        conversationHistory.add("Assistant: " + response);

        return response;
    }

    private String buildConversationPrompt(String newMessage) {
        StringBuilder prompt = new StringBuilder();
        for (String message : conversationHistory) {
            prompt.append(message).append("\n");
        }
        prompt.append("User: ").append(newMessage);
        return prompt.toString();
    }
}

Configuration

Configure ChatClient via application.properties:

# Enable/disable ChatClient
spring.ai.chat.client.enabled=true

# Observation logging
spring.ai.chat.client.observations.log-prompt=false
spring.ai.chat.client.observations.log-completion=false

Observation Support

ChatClient integrates with Micrometer for observability:

# Enable observation logging
spring.ai.chat.client.observations.log-prompt=true
spring.ai.chat.client.observations.log-completion=true

This logs:

  • Prompt content (if enabled)
  • Completion content (if enabled)
  • Token usage
  • Model information
  • Timing metrics

Advisors

Advisors enhance ChatClient with additional capabilities:

Available Advisors

  • MessageChatMemoryAdvisor (org.springframework.ai.chat.client.advisor) - Adds conversation memory
  • QuestionAnswerAdvisor (org.springframework.ai.rag.preretrieval.query.expansion.advisor) - RAG with vector store
  • VectorStoreChatMemoryAdvisor (org.springframework.ai.rag.preretrieval.query.expansion.advisor) - Vector-based memory
  • PromptChatMemoryAdvisor (org.springframework.ai.chat.client.advisor) - Prompt-based memory
  • SimpleLoggerAdvisor (org.springframework.ai.chat.client.advisor) - Logging advisor
  • SafeGuardAdvisor (org.springframework.ai.chat.client.advisor) - Content safety and moderation
  • StructuredOutputValidationAdvisor (org.springframework.ai.chat.client.advisor) - Validates structured outputs
  • ToolCallAdvisor (org.springframework.ai.chat.client.advisor) - Handles tool/function calling

Custom Advisors

Implement the CallAroundAdvisor or RequestResponseAdvisor interface to create custom advisors:

package org.springframework.ai.chat.client.advisor.api;

import org.springframework.ai.chat.model.ChatResponse;
import reactor.core.publisher.Flux;

// Call-around advisor interface (wraps entire call)
public interface CallAroundAdvisor extends Advisor {
    ChatResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
    Flux<ChatResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}

// Request/response advisor interface (modifies request/response)
public interface RequestResponseAdvisor extends Advisor {
    AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context);
    ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context);
}

// Base Advisor interface
public interface Advisor {
    String getName();
    int getOrder();
}

Types

Advisor

package org.springframework.ai.chat.client.advisor.api;

import org.springframework.ai.chat.model.ChatResponse;
import reactor.core.publisher.Flux;
import java.util.Map;

// Base Advisor interface
public interface Advisor {
    String getName();
    int getOrder();
}

// Call-around advisor
public interface CallAroundAdvisor extends Advisor {
    ChatResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
    Flux<ChatResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}

// Request/response advisor
public interface RequestResponseAdvisor extends Advisor {
    AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context);
    ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context);
}

AdvisedRequest

package org.springframework.ai.chat.client.advisor.api;

import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.model.ChatOptions;
import java.util.Map;

public class AdvisedRequest {
    public Prompt prompt();
    public Map<String, Object> adviseContext();
    public Map<String, Object> userParams();
    public Map<String, Object> systemParams();
    public String systemText();
    public String userText();
    public ChatOptions chatOptions();
    public List<FunctionCallback> functionCallbacks();
    public Set<String> functionNames();
    public List<Message> messages();
}

ChatClientResponse

package org.springframework.ai.chat.client;

import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.messages.AssistantMessage;
import java.util.Map;

public class ChatClientResponse {
    public ChatResponse chatResponse();
    public AssistantMessage assistantMessage();
    public String content();
    public Map<String, Object> metadata();
}

ResponseEntity

package org.springframework.ai.chat.client;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;

public class ResponseEntity<T> {
    public T getBody();
    public HttpHeaders getHeaders();
    public HttpStatusCode getStatusCode();
}

Message

package org.springframework.ai.chat.messages;

import java.util.Map;

public interface Message {
    String getContent();
    MessageType getMessageType();
    Map<String, Object> getMetadata();
}

Prototype Scope

The ChatClient.Builder bean is registered with prototype scope, meaning each injection creates a new instance. This allows for different configurations in different services:

@Service
public class TechnicalChatService {
    private final ChatClient chatClient;

    @Autowired
    public TechnicalChatService(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultSystem("You are a technical expert.")
            .build();
    }
}

@Service
public class CreativeChatService {
    private final ChatClient chatClient;

    @Autowired
    public CreativeChatService(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultSystem("You are a creative writer.")
            .build();
    }
}

Best Practices

  1. Use defaultSystem: Set system prompts at build time for consistent behavior
  2. Leverage advisors: Use built-in advisors for common patterns (memory, RAG)
  3. Stream for long responses: Use streaming for better user experience
  4. Entity mapping for structured data: Use entity() for typed responses
  5. Reuse ChatClient instances: Build once, use many times
  6. Handle errors gracefully: Wrap calls in try-catch for production code
  7. Use observation: Enable for production monitoring and debugging
  8. Prototype scope awareness: Each builder injection is independent
tessl i tessl/maven-org-springframework-ai--spring-ai-starter-model-openai@1.1.1

docs

index.md

tile.json