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

memory-advisors.mddocs/reference/

Memory Management Advisors

Spring AI Chat Client provides built-in advisors for managing conversation history and memory. These advisors automatically store and retrieve messages from previous interactions, enabling multi-turn conversations with context awareness.

Imports

import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.prompt.PromptTemplate;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

ChatMemory Interface

Both memory advisors require a ChatMemory implementation to store conversation history.

Common Implementation:

import org.springframework.ai.chat.memory.InMemoryChatMemory;

ChatMemory chatMemory = new InMemoryChatMemory();

For production use, implement ChatMemory with persistent storage (database, Redis, etc.).

MessageChatMemoryAdvisor

Injects conversation history by adding previous messages directly to the prompt's message list.

class MessageChatMemoryAdvisor implements BaseChatMemoryAdvisor {
    static Builder builder(ChatMemory chatMemory);
}

Builder

interface Builder {
    Builder conversationId(String conversationId);
    Builder order(int order);
    Builder scheduler(Scheduler scheduler);
    MessageChatMemoryAdvisor build();
}

Builder Methods:

  • conversationId() - Set conversation ID for grouping messages (default: "default")
  • order() - Set advisor execution order (default: Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
  • scheduler() - Set reactive scheduler (default: Schedulers.boundedElastic())

Basic Usage

import org.springframework.ai.chat.memory.InMemoryChatMemory;

ChatMemory chatMemory = new InMemoryChatMemory();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

// First message
String response1 = client
    .prompt("My name is Alice")
    .call()
    .content();

// Second message - memory automatically includes previous exchange
String response2 = client
    .prompt("What is my name?")
    .call()
    .content();
// Response will reference "Alice" using conversation history

With Conversation ID

Use conversation IDs to maintain separate conversations for different users or sessions.

MessageChatMemoryAdvisor memoryAdvisor =
    MessageChatMemoryAdvisor.builder(chatMemory)
        .conversationId("user-123")
        .build();

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

Per-Request Conversation ID

Override conversation ID for specific requests using advisor parameters.

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

// User 1's conversation
String response1 = client
    .prompt()
    .user("I like Java")
    .advisors(spec -> spec.param("conversationId", "user-1"))
    .call()
    .content();

// User 2's conversation (separate history)
String response2 = client
    .prompt()
    .user("I like Python")
    .advisors(spec -> spec.param("conversationId", "user-2"))
    .call()
    .content();

// User 1's follow-up (remembers Java)
String response3 = client
    .prompt()
    .user("What language do I like?")
    .advisors(spec -> spec.param("conversationId", "user-1"))
    .call()
    .content();

Custom Order

MessageChatMemoryAdvisor memoryAdvisor =
    MessageChatMemoryAdvisor.builder(chatMemory)
        .order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
        .build();

With Streaming

Memory advisors work seamlessly with streaming responses.

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory)
            .conversationId("stream-user")
            .build()
    )
    .build();

Flux<String> stream = client
    .prompt("Continue our story")
    .stream()
    .content();

stream.subscribe(System.out::print);

PromptChatMemoryAdvisor

Injects conversation history by inserting it into the system prompt using a template. This approach keeps message history as text rather than separate message objects.

class PromptChatMemoryAdvisor implements BaseChatMemoryAdvisor {
    static Builder builder(ChatMemory chatMemory);
}

Builder

interface Builder {
    Builder systemPromptTemplate(PromptTemplate systemPromptTemplate);
    Builder conversationId(String conversationId);
    Builder order(int order);
    Builder scheduler(Scheduler scheduler);
    PromptChatMemoryAdvisor build();
}

Builder Methods:

  • systemPromptTemplate() - Set template with {conversation_history} placeholder
  • conversationId() - Set conversation ID (default: "default")
  • order() - Set advisor execution order (default: Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
  • scheduler() - Set reactive scheduler (default: Schedulers.boundedElastic())

Basic Usage

PromptChatMemoryAdvisor memoryAdvisor =
    PromptChatMemoryAdvisor.builder(chatMemory)
        .systemPromptTemplate("""
            You are a helpful assistant.

            Conversation history:
            {conversation_history}
            """)
        .build();

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

// Conversation with history injected into system prompt
String response = client
    .prompt("What did we discuss earlier?")
    .call()
    .content();

Custom Template

String template = """
    You are a coding assistant.

    Previous conversation:
    {conversation_history}

    Use the conversation history to provide context-aware responses.
    """;

PromptChatMemoryAdvisor memoryAdvisor =
    PromptChatMemoryAdvisor.builder(chatMemory)
        .systemPromptTemplate(template)
        .conversationId("code-assistant")
        .build();

With Per-Request Conversation ID

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        PromptChatMemoryAdvisor.builder(chatMemory)
            .systemPromptTemplate("""
                Context: {conversation_history}
                """)
            .build()
    )
    .build();

client
    .prompt()
    .user("Remember: I work at Acme Corp")
    .advisors(spec -> spec.param("conversationId", "user-123"))
    .call()
    .content();

client
    .prompt()
    .user("Where do I work?")
    .advisors(spec -> spec.param("conversationId", "user-123"))
    .call()
    .content();

BaseChatMemoryAdvisor Interface

Both memory advisors implement this interface.

interface BaseChatMemoryAdvisor extends BaseAdvisor {
    String getConversationId(
        Map<String, Object> context,
        String defaultConversationId
    );
}

Method:

  • getConversationId() - Extract conversation ID from context or use default

This allows conversation ID to be passed via advisor parameters at request time.

Choosing Between Memory Advisors

MessageChatMemoryAdvisor

Use when:

  • You want complete message objects with roles preserved
  • The AI model works better with structured message history
  • You need to maintain exact message boundaries

Pros:

  • Preserves message structure and metadata
  • More accurate representation of conversation
  • Better for models that understand message roles

Cons:

  • Uses more tokens (each message has role prefix)
  • May hit token limits faster with long conversations

PromptChatMemoryAdvisor

Use when:

  • You want more control over history formatting
  • Token efficiency is important
  • You need to customize how history is presented

Pros:

  • More flexible formatting
  • Can be more token-efficient
  • Easier to customize presentation

Cons:

  • Loses message structure
  • Requires template management
  • May be less clear to model

Complete Examples

Multi-User Chat Application

import org.springframework.ai.chat.memory.InMemoryChatMemory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ChatService {
    private final ChatClient chatClient;
    private final ChatMemory chatMemory = new InMemoryChatMemory();

    public ChatService(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("You are a helpful assistant")
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build()
            )
            .build();
    }

    public String chat(String userId, String message) {
        return chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param("conversationId", userId))
            .call()
            .content();
    }

    public Flux<String> chatStream(String userId, String message) {
        return chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param("conversationId", userId))
            .stream()
            .content();
    }
}

Context-Aware Assistant

String systemPrompt = """
    You are a technical support assistant for our product.

    Previous conversation with this customer:
    {conversation_history}

    Use the conversation history to:
    - Remember previously discussed issues
    - Avoid repeating questions
    - Provide personalized assistance
    """;

PromptChatMemoryAdvisor memoryAdvisor =
    PromptChatMemoryAdvisor.builder(chatMemory)
        .systemPromptTemplate(systemPrompt)
        .build();

ChatClient supportBot = ChatClient.builder(chatModel)
    .defaultAdvisors(memoryAdvisor)
    .build();

// Customer interaction
String response1 = supportBot
    .prompt()
    .user("I'm having issues with login")
    .advisors(spec -> spec.param("conversationId", "ticket-789"))
    .call()
    .content();

// Follow-up in same conversation
String response2 = supportBot
    .prompt()
    .user("The error is 'Invalid credentials'")
    .advisors(spec -> spec.param("conversationId", "ticket-789"))
    .call()
    .content();

Combining Memory with Other Advisors

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

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        // Memory advisor runs early to inject history
        MessageChatMemoryAdvisor.builder(chatMemory)
            .order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
            .build(),
        // Logger runs last to see complete request
        SimpleLoggerAdvisor.builder()
            .order(Ordered.LOWEST_PRECEDENCE)
            .build()
    )
    .build();

Session-Based Conversations

public class ConversationSession {
    private final String sessionId;
    private final ChatClient chatClient;

    public ConversationSession(
        String sessionId,
        ChatClient chatClient
    ) {
        this.sessionId = sessionId;
        this.chatClient = chatClient;
    }

    public String send(String message) {
        return chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param("conversationId", sessionId))
            .call()
            .content();
    }
}

// Usage
ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

ConversationSession session1 = new ConversationSession("session-1", client);
ConversationSession session2 = new ConversationSession("session-2", client);

session1.send("I like cats");
session2.send("I like dogs");

session1.send("What animal do I like?"); // Returns "cats"
session2.send("What animal do I like?"); // Returns "dogs"

Install with Tessl CLI

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

docs

index.md

tile.json