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

advisors.mddocs/reference/

Advisor System

The Spring AI Chat Client uses an advisor pattern (interceptor chain) to modify requests and responses. Advisors provide a powerful extension point for implementing cross-cutting concerns like logging, memory management, validation, and security.

Imports

import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.core.Ordered;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;

Core Advisor Interfaces

Advisor

The base interface for all advisors.

interface Advisor extends Ordered {
    String getName();
    int getOrder();

    int DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER =
        Ordered.HIGHEST_PRECEDENCE + 1000;
}

Methods:

  • getName() - Returns the advisor name for identification and logging
  • getOrder() - Returns the execution order (lower values execute first)

Constants:

  • DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER - Recommended order for memory advisors (1001)

Example:

class MyAdvisor implements CallAdvisor {
    @Override
    public String getName() {
        return "MyCustomAdvisor";
    }

    @Override
    public int getOrder() {
        return 100; // Execute early in chain
    }

    @Override
    public ChatClientResponse adviseCall(
        ChatClientRequest request,
        CallAdvisorChain chain
    ) {
        // Implementation
    }
}

CallAdvisor

Advisor interface for synchronous (blocking) call flows.

interface CallAdvisor extends Advisor {
    ChatClientResponse adviseCall(
        ChatClientRequest request,
        CallAdvisorChain chain
    );
}

Parameters:

  • request - The current request being processed
  • chain - The chain to invoke the next advisor

Returns: Modified or original ChatClientResponse

Example:

class LoggingCallAdvisor implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(
        ChatClientRequest request,
        CallAdvisorChain chain
    ) {
        System.out.println("Before call: " + request);

        ChatClientResponse response = chain.nextCall(request);

        System.out.println("After call: " + response);
        return response;
    }

    @Override
    public String getName() {
        return "LoggingCallAdvisor";
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

StreamAdvisor

Advisor interface for streaming (reactive) flows.

interface StreamAdvisor extends Advisor {
    Flux<ChatClientResponse> adviseStream(
        ChatClientRequest request,
        StreamAdvisorChain chain
    );
}

Parameters:

  • request - The current request being processed
  • chain - The chain to invoke the next advisor

Returns: Flux<ChatClientResponse> - Reactive stream of responses

Example:

class LoggingStreamAdvisor implements StreamAdvisor {
    @Override
    public Flux<ChatClientResponse> adviseStream(
        ChatClientRequest request,
        StreamAdvisorChain chain
    ) {
        System.out.println("Before stream: " + request);

        return chain.nextStream(request)
            .doOnNext(response ->
                System.out.println("Stream chunk: " + response)
            )
            .doOnComplete(() ->
                System.out.println("Stream complete")
            );
    }

    @Override
    public String getName() {
        return "LoggingStreamAdvisor";
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

BaseAdvisor

Unified interface implementing both CallAdvisor and StreamAdvisor with template methods.

interface BaseAdvisor extends CallAdvisor, StreamAdvisor {
    // Template methods
    ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    );

    ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    );

    Scheduler getScheduler();

    Scheduler DEFAULT_SCHEDULER = Schedulers.boundedElastic();
}

Template Methods:

  • before() - Pre-processing logic, modify request before execution
  • after() - Post-processing logic, modify response after execution
  • getScheduler() - Get scheduler for streaming operations (default: boundedElastic)

Default Implementations: The interface provides default implementations of adviseCall() and adviseStream() that call the template methods.

Example:

class TimingAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        // Store start time in context
        request.context().put("startTime", System.currentTimeMillis());
        return request;
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        long startTime = (long) response.context().get("startTime");
        long duration = System.currentTimeMillis() - startTime;
        System.out.println("Request took: " + duration + "ms");
        return response;
    }

    @Override
    public String getName() {
        return "TimingAdvisor";
    }

    @Override
    public int getOrder() {
        return 0; // Execute first
    }
}

Advisor Chain Interfaces

AdvisorChain

Base chain interface providing observability support.

interface AdvisorChain {
    ObservationRegistry getObservationRegistry();
}

Methods:

  • getObservationRegistry() - Get observation registry for metrics (default: NOOP)

CallAdvisorChain

Chain interface for synchronous advisor execution.

interface CallAdvisorChain extends AdvisorChain {
    ChatClientResponse nextCall(ChatClientRequest request);
    List<CallAdvisor> getCallAdvisors();
    CallAdvisorChain copy(CallAdvisor nextAdvisor);
}

Methods:

  • nextCall() - Invoke next advisor in chain
  • getCallAdvisors() - Get all call advisors in chain
  • copy() - Create new chain starting after specified advisor

StreamAdvisorChain

Chain interface for streaming advisor execution.

interface StreamAdvisorChain extends AdvisorChain {
    Flux<ChatClientResponse> nextStream(ChatClientRequest request);
    List<StreamAdvisor> getStreamAdvisors();
}

Methods:

  • nextStream() - Invoke next advisor in chain (returns Flux)
  • getStreamAdvisors() - Get all stream advisors in chain

BaseAdvisorChain

Combined chain interface supporting both call and stream advisors.

interface BaseAdvisorChain extends CallAdvisorChain, StreamAdvisorChain {
}

Advisor Ordering

Advisors execute in order determined by their getOrder() value. Lower values execute first.

Standard Order Values:

  • Ordered.HIGHEST_PRECEDENCE (-2147483648) - Absolute first
  • Security/validation advisors: 100-500
  • Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER (1001) - Memory advisors
  • Business logic advisors: 5000-10000
  • Logging/monitoring advisors: Ordered.LOWEST_PRECEDENCE (2147483647) - Absolute last

Example:

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        SafeGuardAdvisor.builder()
            .order(100) // Execute first
            .build(),
        MessageChatMemoryAdvisor.builder(chatMemory)
            .order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
            .build(),
        SimpleLoggerAdvisor.builder()
            .order(Ordered.LOWEST_PRECEDENCE) // Execute last
            .build()
    )
    .build();

Request and Response Context

Advisors can share data through the request/response context maps.

class ContextSharingAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        // Add data to context
        request.context().put("userId", "user-123");
        request.context().put("timestamp", System.currentTimeMillis());
        return request;
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        // Read data from context
        String userId = (String) response.context().get("userId");
        long timestamp = (long) response.context().get("timestamp");

        // Context flows through the chain
        return response;
    }

    @Override
    public String getName() {
        return "ContextSharingAdvisor";
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Modifying Requests

Advisors can modify requests before execution.

class RequestModifierAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        // Get existing prompt
        Prompt originalPrompt = request.prompt();

        // Add system message
        List<Message> messages = new ArrayList<>(
            originalPrompt.getInstructions()
        );
        messages.add(0, new SystemMessage("Be concise"));

        // Create modified prompt
        Prompt modifiedPrompt = new Prompt(
            messages,
            originalPrompt.getOptions()
        );

        // Return modified request
        return request.mutate()
            .prompt(modifiedPrompt)
            .build();
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        return response;
    }

    @Override
    public String getName() {
        return "RequestModifierAdvisor";
    }

    @Override
    public int getOrder() {
        return 500;
    }
}

Modifying Responses

Advisors can modify responses after execution.

class ResponseModifierAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        return request;
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        ChatResponse chatResponse = response.chatResponse();
        if (chatResponse == null) {
            return response;
        }

        // Modify response content (example: uppercase)
        Generation generation = chatResponse.getResult();
        String content = generation.getOutput().getContent();
        String modified = content.toUpperCase();

        // Create modified response
        // (simplified - actual implementation more complex)
        return response; // Return modified version
    }

    @Override
    public String getName() {
        return "ResponseModifierAdvisor";
    }

    @Override
    public int getOrder() {
        return 10000;
    }
}

Conditional Execution

Advisors can conditionally execute based on request properties.

class ConditionalAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        // Check if this advisor should execute
        boolean enabled = (boolean) request.context()
            .getOrDefault("enableConditional", false);

        if (!enabled) {
            return request; // Skip processing
        }

        // Execute logic
        System.out.println("Conditional advisor executing");
        return request;
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        return response;
    }

    @Override
    public String getName() {
        return "ConditionalAdvisor";
    }

    @Override
    public int getOrder() {
        return 5000;
    }
}

// Usage
chatClient
    .prompt()
    .user("Hello")
    .advisors(spec -> spec
        .advisors(new ConditionalAdvisor())
        .param("enableConditional", true)
    )
    .call()
    .content();

Terminal Advisors

Terminal advisors actually execute the ChatModel call. They are typically last in the chain.

ChatModelCallAdvisor

class ChatModelCallAdvisor implements CallAdvisor {
    static Builder builder();

    interface Builder {
        Builder chatModel(ChatModel chatModel);
        ChatModelCallAdvisor build();
    }
}

Executes ChatModel.call() for synchronous requests.

ChatModelStreamAdvisor

class ChatModelStreamAdvisor implements StreamAdvisor {
    static Builder builder();

    interface Builder {
        Builder chatModel(ChatModel chatModel);
        ChatModelStreamAdvisor build();
    }
}

Executes ChatModel.stream() for streaming requests.

Note: These terminal advisors are automatically added by the ChatClient and typically don't need to be manually configured.

Complete Example

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.*;
import org.springframework.core.Ordered;

// Custom advisor
class AuditAdvisor implements BaseAdvisor {
    @Override
    public ChatClientRequest before(
        ChatClientRequest request,
        AdvisorChain chain
    ) {
        System.out.println("Audit: Request started");
        request.context().put("auditId", UUID.randomUUID().toString());
        return request;
    }

    @Override
    public ChatClientResponse after(
        ChatClientResponse response,
        AdvisorChain chain
    ) {
        String auditId = (String) response.context().get("auditId");
        System.out.println("Audit: Request completed - " + auditId);
        return response;
    }

    @Override
    public String getName() {
        return "AuditAdvisor";
    }

    @Override
    public int getOrder() {
        return 200;
    }
}

// Configure client with multiple advisors
ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        SafeGuardAdvisor.builder()
            .sensitiveWords(List.of("secret"))
            .order(100)
            .build(),
        new AuditAdvisor(), // order: 200
        MessageChatMemoryAdvisor.builder(chatMemory)
            .order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER) // 1001
            .build(),
        SimpleLoggerAdvisor.builder()
            .order(Ordered.LOWEST_PRECEDENCE)
            .build()
    )
    .build();

// Use client
String response = client
    .prompt("Hello")
    .call()
    .content();

Install with Tessl CLI

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

docs

index.md

tile.json