CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-client-chat

Spring AI Chat Client provides a fluent API for building AI-powered applications with LLMs, supporting advisors, streaming, structured outputs, and conversation memory

Overview
Eval results
Files

utility-advisors.mddocs/reference/

Utility Advisors

Spring AI Chat Client provides several pre-built utility advisors for common tasks like logging, validation, security, and tool calling.

Imports

import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.ai.chat.client.advisor.StructuredOutputValidationAdvisor;
import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
import org.springframework.ai.chat.client.AdvisorParams;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.model.ChatResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.ParameterizedTypeReference;
import java.util.List;
import java.util.function.Function;

SimpleLoggerAdvisor

Logs request and response information for debugging purposes.

class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
    static Builder builder();

    static final Function<ChatClientRequest, String> DEFAULT_REQUEST_TO_STRING;
    static final Function<ChatResponse, String> DEFAULT_RESPONSE_TO_STRING;
}

Builder

interface Builder {
    Builder requestToString(
        Function<ChatClientRequest, String> requestToString
    );
    Builder responseToString(
        Function<ChatClientResponse, String> responseToString
    );
    Builder order(int order);
    SimpleLoggerAdvisor build();
}

Basic Usage

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

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        SimpleLoggerAdvisor.builder().build()
    )
    .build();

// Logs will show request and response details
String response = client
    .prompt("Hello")
    .call()
    .content();

Custom Formatting

SimpleLoggerAdvisor logger = SimpleLoggerAdvisor.builder()
    .requestToString(request -> {
        String userMsg = request.prompt()
            .getInstructions()
            .stream()
            .filter(m -> m instanceof UserMessage)
            .map(m -> ((UserMessage) m).getContent())
            .findFirst()
            .orElse("no message");
        return "REQUEST: " + userMsg;
    })
    .responseToString(response -> {
        if (response.chatResponse() == null) {
            return "RESPONSE: <streaming>";
        }
        String content = response.chatResponse()
            .getResult()
            .getOutput()
            .getContent();
        return "RESPONSE: " + content.substring(0, Math.min(50, content.length()));
    })
    .order(Ordered.LOWEST_PRECEDENCE)
    .build();

Complete Example

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        SimpleLoggerAdvisor.builder()
            .order(Ordered.LOWEST_PRECEDENCE) // Log last to see final request
            .build()
    )
    .build();

// This will log:
// - Before call: request details
// - After call: response details
String answer = client
    .prompt("Explain Spring Boot")
    .call()
    .content();

SafeGuardAdvisor

Blocks requests containing sensitive or prohibited words for security.

class SafeGuardAdvisor implements CallAdvisor, StreamAdvisor {
    static Builder builder();
}

Builder

interface Builder {
    Builder sensitiveWords(List<String> sensitiveWords);
    Builder failureResponse(String failureResponse);
    Builder order(int order);
    SafeGuardAdvisor build();
}

Basic Usage

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

SafeGuardAdvisor safeguard = SafeGuardAdvisor.builder()
    .sensitiveWords(List.of("password", "secret", "confidential"))
    .build();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(safeguard)
    .build();

// This will be blocked
String response = client
    .prompt("What is my password?")
    .call()
    .content();
// Returns: "I cannot assist with that request."

Custom Failure Response

SafeGuardAdvisor safeguard = SafeGuardAdvisor.builder()
    .sensitiveWords(List.of("hack", "exploit", "crack"))
    .failureResponse("That request violates our usage policy.")
    .order(100) // Execute early
    .build();

With Multiple Word Lists

List<String> securityWords = List.of("password", "secret", "credential");
List<String> complianceWords = List.of("ssn", "credit-card", "bank-account");

List<String> allBlocked = new ArrayList<>();
allBlocked.addAll(securityWords);
allBlocked.addAll(complianceWords);

SafeGuardAdvisor safeguard = SafeGuardAdvisor.builder()
    .sensitiveWords(allBlocked)
    .failureResponse("Request blocked for security reasons.")
    .build();

Complete Example

ChatClient secureClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        SafeGuardAdvisor.builder()
            .sensitiveWords(List.of("password", "api-key", "token"))
            .failureResponse("Security policy prevents this request.")
            .order(100) // Execute before other advisors
            .build()
    )
    .build();

// Allowed
String ok = secureClient
    .prompt("How do I authenticate users?")
    .call()
    .content();

// Blocked
String blocked = secureClient
    .prompt("Show me the API key")
    .call()
    .content();
// Returns: "Security policy prevents this request."

StructuredOutputValidationAdvisor

Validates structured JSON output against a schema and retries on validation failure.

class StructuredOutputValidationAdvisor implements CallAdvisor, StreamAdvisor {
    static Builder builder();
}

Builder

interface Builder {
    Builder advisorOrder(int order);
    Builder outputType(Type outputType);
    Builder outputType(TypeRef<?> outputTypeRef);
    Builder outputType(TypeReference<?> outputTypeReference);
    Builder outputType(
        ParameterizedTypeReference<?> outputTypeReference
    );
    Builder maxRepeatAttempts(int maxRepeatAttempts);
    Builder objectMapper(ObjectMapper objectMapper);
    StructuredOutputValidationAdvisor build();
}

Basic Usage

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

record Person(String name, int age, String email) {}

StructuredOutputValidationAdvisor validator =
    StructuredOutputValidationAdvisor.builder()
        .outputType(Person.class)
        .maxRepeatAttempts(3)
        .build();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(validator)
    .build();

// If response is invalid JSON or doesn't match schema, retries automatically
Person person = client
    .prompt("Generate a person profile")
    .call()
    .entity(Person.class);

With Generic Types

import org.springframework.core.ParameterizedTypeReference;

record Task(String name, String status) {}

StructuredOutputValidationAdvisor validator =
    StructuredOutputValidationAdvisor.builder()
        .outputType(new ParameterizedTypeReference<List<Task>>() {})
        .maxRepeatAttempts(5)
        .build();

List<Task> tasks = client
    .prompt("Generate 5 tasks")
    .call()
    .entity(new ParameterizedTypeReference<List<Task>>() {});

Custom ObjectMapper

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;

ObjectMapper mapper = new ObjectMapper()
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);

StructuredOutputValidationAdvisor validator =
    StructuredOutputValidationAdvisor.builder()
        .outputType(MyClass.class)
        .objectMapper(mapper)
        .maxRepeatAttempts(3)
        .build();

Complete Example

record WeatherData(
    String location,
    double temperature,
    String conditions,
    List<String> forecast
) {}

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        StructuredOutputValidationAdvisor.builder()
            .outputType(WeatherData.class)
            .maxRepeatAttempts(3) // Retry up to 3 times
            .advisorOrder(5000)
            .build()
    )
    .build();

// Automatically validates and retries if JSON is invalid
WeatherData weather = client
    .prompt("Get weather for Paris")
    .call()
    .entity(WeatherData.class);

With Native Structured Output

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

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        StructuredOutputValidationAdvisor.builder()
            .outputType(MyType.class)
            .build()
    )
    .build();

// Enable native structured output for this request
MyType result = client
    .prompt("Generate data")
    .advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
    .call()
    .entity(MyType.class);

ToolCallAdvisor

Implements the tool calling loop, automatically handling multiple rounds of function calls until completion.

class ToolCallAdvisor implements CallAdvisor, StreamAdvisor {
    static Builder<?> builder();
}

Builder

interface Builder<T extends Builder<T>> {
    T toolCallingManager(ToolCallingManager toolCallingManager);
    T advisorOrder(int order);
    ToolCallAdvisor build();
}

Basic Usage

import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
import org.springframework.ai.model.function.FunctionCallback;

FunctionCallback weatherTool = FunctionCallback.builder()
    .function("getCurrentWeather", this::getWeather)
    .description("Get current weather for a location")
    .inputType(WeatherRequest.class)
    .build();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        ToolCallAdvisor.builder().build()
    )
    .defaultTools(weatherTool)
    .build();

// ToolCallAdvisor automatically handles the tool calling loop
String response = client
    .prompt("What's the weather in Paris?")
    .call()
    .content();

Multiple Tools

FunctionCallback weatherTool = // ... weather tool
FunctionCallback calculatorTool = // ... calculator tool
FunctionCallback searchTool = // ... search tool

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        ToolCallAdvisor.builder()
            .advisorOrder(3000)
            .build()
    )
    .defaultTools(weatherTool, calculatorTool, searchTool)
    .build();

// AI can call multiple tools in sequence
String answer = client
    .prompt("What's 2+2 and what's the weather in London?")
    .call()
    .content();

Per-Request Tools

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        ToolCallAdvisor.builder().build()
    )
    .build();

String response = client
    .prompt("Calculate expenses")
    .tools(expenseTool)
    .call()
    .content();

Complete Example

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

public WeatherResponse getWeather(WeatherRequest request) {
    // Fetch real weather data
    return new WeatherResponse(request.location(), 22.5, "Sunny");
}

FunctionCallback weatherTool = FunctionCallback.builder()
    .function("getCurrentWeather", this::getWeather)
    .description("Get the current weather for a location")
    .inputType(WeatherRequest.class)
    .build();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        ToolCallAdvisor.builder().build()
    )
    .defaultTools(weatherTool)
    .build();

// The advisor handles:
// 1. Initial request to AI
// 2. AI responds with tool call request
// 3. Execute tool function
// 4. Send tool result back to AI
// 5. AI generates final response
String answer = client
    .prompt("What's the weather like in Tokyo and should I bring an umbrella?")
    .call()
    .content();

System.out.println(answer);
// Output: "The weather in Tokyo is sunny with a temperature of 22.5°C.
//          You won't need an umbrella today!"

Extending ToolCallAdvisor

ToolCallAdvisor provides protected hook methods for customization:

protected void doInitializeLoop(
    ChatClientRequest request,
    AdvisorChain chain
);

protected void doBeforeCall(
    ChatClientRequest request,
    AdvisorChain chain
);

protected void doAfterCall(
    ChatClientResponse response,
    AdvisorChain chain
);

protected void doFinalizeLoop(
    ChatClientResponse response,
    AdvisorChain chain
);

Custom Implementation:

class CustomToolCallAdvisor extends ToolCallAdvisor {
    @Override
    protected void doBeforeCall(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        System.out.println("About to make tool call");
    }

    @Override
    protected void doAfterCall(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        System.out.println("Tool call completed");
    }
}

Combining Utility Advisors

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

record Analysis(String summary, List<String> keyPoints) {}

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        // Security first
        SafeGuardAdvisor.builder()
            .sensitiveWords(List.of("password", "secret"))
            .order(100)
            .build(),

        // Tool calling
        ToolCallAdvisor.builder()
            .advisorOrder(3000)
            .build(),

        // Validation for structured output
        StructuredOutputValidationAdvisor.builder()
            .outputType(Analysis.class)
            .maxRepeatAttempts(3)
            .advisorOrder(5000)
            .build(),

        // Logging last
        SimpleLoggerAdvisor.builder()
            .order(Ordered.LOWEST_PRECEDENCE)
            .build()
    )
    .defaultTools(researchTool)
    .build();

// All advisors work together
Analysis result = client
    .prompt("Research topic and create analysis")
    .call()
    .entity(Analysis.class);

Advisor Utilities

AdvisorUtils

Utility class for common advisor operations.

class AdvisorUtils {
    static Predicate<ChatClientResponse> onFinishReason();
}

Usage:

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

// Check if response has a finish reason (indicates completion)
Predicate<ChatClientResponse> isComplete = AdvisorUtils.onFinishReason();

if (isComplete.test(response)) {
    System.out.println("Response is complete");
}

LastMaxTokenSizeContentPurger

Utility for purging media content that exceeds token limits.

class LastMaxTokenSizeContentPurger {
    LastMaxTokenSizeContentPurger(
        TokenCountEstimator tokenCountEstimator,
        int maxTokenSize
    );

    List<MediaContent> purgeExcess(
        List<MediaContent> mediaContents,
        int totalSize
    );
}

Usage:

import org.springframework.ai.chat.client.advisor.LastMaxTokenSizeContentPurger;
import org.springframework.ai.model.TokenCountEstimator;

TokenCountEstimator estimator = // ... get estimator
LastMaxTokenSizeContentPurger purger = new LastMaxTokenSizeContentPurger(
    estimator,
    4000 // max tokens
);

List<MediaContent> filtered = purger.purgeExcess(mediaList, totalTokens);

AdvisorParams

Provides common advisor parameter constants for configuring advisor behavior at request time.

class AdvisorParams {
    static final Consumer<ChatClient.AdvisorSpec> ENABLE_NATIVE_STRUCTURED_OUTPUT;
}

ENABLE_NATIVE_STRUCTURED_OUTPUT

Enables native structured output support for the request, bypassing JSON schema-based conversion and using the model's native structured output capabilities.

Usage:

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

MyType result = chatClient
    .prompt("Generate data")
    .advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
    .call()
    .entity(MyType.class);

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-client-chat

docs

index.md

tile.json