Spring AI Custom CallAdvisor & StreamAdvisor Example

Spring AI CallAdvisor and StreamAdvisor provide hooks to wrap around LLM invocations, both for synchronous and streaming scenarios.

Spring AI

Spring AI 1.0.0 introduced advisor interfaces—specifically CallAdvisor and StreamAdvisor to help developers intercept and enrich model interactions without altering core business logic. These advisors act similarly to Spring AOP (Aspect-Oriented Programming) patterns in Spring Framework.

Internally, CallAdvisor and StreamAdvisor provide hooks to wrap around LLM invocations, both for synchronous and streaming scenarios. This article provides a complete working example demonstrating how to implement these interfaces and how to use the overridden methods adviseCall() and adviseStream().

1. CallAdvisor and StreamAdvisor API

The CallAdvisor allows us to intercept blocking/synchronous model calls to an LLM before and after execution. Inside the advisecall() method, we can modify input, log data, handle errors, and manipulate output.

public interface CallAdvisor extends Advisor {

  ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}

Similar modifications can be done when streaming outputs from an LLM model using StreamAdvisor interface.

public interface StreamAdvisor extends Advisor {

  Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}

2. Advisor Lifecycle & Composition

Spring AI supports composable chains of advisors. This means you can register multiple advisors and they will be invoked in order. It follows a decorator pattern, where each advisor wraps the next.

The chain looks like:

Client -> Advisor1 -> Advisor2 -> … -> ModelCall

Spring AI API provides several built-in advisors, as well as we can create custom advisors using CallAdvisor and StreamAdvisor. And then we can use these advisors as follows:

String responseContent = chatClient.prompt()
      .user("... message...")
      .advisors(advisor1)
      .advisors(advisor2)
      .advisors(customAdvisor)
      .advisors(advisor3)
      .call()
      .content();

3. Creating a Custom CallAdvisor and StreamAdvisor

Create a class and implement the CallAdvisor and StreamAdvisor interfaces. Then, implement the methods similar to Spring AOP style.

In the following example, we are modifying the prompt and instructing the LLM to respond as if it is talking to a 16-year-old child.

import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;

public class CustomAdvisor implements CallAdvisor, StreamAdvisor {

  private final static Logger logger = LoggerFactory.getLogger(CustomAdvisor.class);

  @Override
  public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
    Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");

    ChatClientRequest formattedChatClientRequest = augmentWithCustomInstructions(chatClientRequest);
    return callAdvisorChain.nextCall(chatClientRequest);
  }

  @Override
  public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
    Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");

    ChatClientRequest formattedChatClientRequest = augmentWithCustomInstructions(chatClientRequest);
    return streamAdvisorChain.nextStream(chatClientRequest);
  }


  private static ChatClientRequest augmentWithCustomInstructions(ChatClientRequest chatClientRequest) {

    String customInstructions = "Please respond as you are explaining it to a 16-year-old child. " +
      "Use simple words and short sentences. " +
      "If you don't know the answer, say 'I don't know'.";

    Prompt augmentedPrompt = chatClientRequest.prompt()
      .augmentUserMessage(userMessage -> userMessage.mutate()
        .text(userMessage.getText() + System.lineSeparator() + customInstructions)
        .build());

    return ChatClientRequest.builder()
      .prompt(augmentedPrompt)
      .context(Map.copyOf(chatClientRequest.context()))
      .build();
  }

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

  @Override
  public int getOrder() {
    return Integer.MAX_VALUE;
  }
}

Avoid any kind of blocking calls in StreamAdvisor.

To invoke the advisor, we must pass the advisor reference to the ChatClient instance.

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CustomAdvisorExample implements CommandLineRunner {

  @Value("${spring.ai.openai.api-key}")
  String apiKey;

  public static void main(String[] args) {
    SpringApplication.run(CustomAdvisorExample.class, args);
  }

  @Override
  public void run(String... args) {

    OpenAiApi openAiApi = OpenAiApi.builder().apiKey(apiKey).build();
    OpenAiChatModel chatModel = OpenAiChatModel.builder().openAiApi(openAiApi).build();

    ChatClient chatClient = ChatClient
      .builder(chatModel)
      .build();

    String responseContent = chatClient.prompt()
      .user("My name is: Lokesh. Say my name in french and explain its meaning.")
      .advisors(new CustomAdvisor())
      .call()
      .content();

    System.out.printf("response: %s\n", responseContent);
  }
}

4. Conclusion

Spring AI’s CallAdvisor and StreamAdvisor are very handy tools that make it easy to extend your AI pipeline. Whether you’re building a chatbot, knowledge assistant, or a prompt orchestration service, advisors let you hook into the model lifecycle in clean, reusable ways.

Happy Learning !!

Source Code Download

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.