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

conversation-memory.mddocs/guides/

Conversation Memory

Enable multi-turn conversations with automatic history management.

MessageChatMemoryAdvisor

Stores messages as structured Message objects.

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

ChatMemory chatMemory = new InMemoryChatMemory();

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

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

// Second message - remembers previous
client.prompt("What is my name?").call().content();
// Response: "Your name is Alice"

Per-User Conversations

Use conversation IDs to separate user conversations:

// User 1
client.prompt()
    .user("I like Java")
    .advisors(spec -> spec.param("conversationId", "user-1"))
    .call()
    .content();

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

// User 1 follow-up
client.prompt()
    .user("What language do I like?")
    .advisors(spec -> spec.param("conversationId", "user-1"))
    .call()
    .content();
// Response: "You like Java"

PromptChatMemoryAdvisor

Injects history as text in system prompt (more token-efficient).

import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.prompt.PromptTemplate;

String template = """
    You are a helpful assistant.
    
    Conversation history:
    {conversation_history}
    """;

PromptChatMemoryAdvisor memoryAdvisor =
    PromptChatMemoryAdvisor.builder(chatMemory)
        .systemPromptTemplate(new PromptTemplate(template))
        .build();

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

Choosing Memory Advisor

MessageChatMemoryAdvisor:

  • ✅ Preserves message structure
  • ✅ Better for models that understand roles
  • ✅ Supports multimodal content
  • ❌ Uses more tokens

PromptChatMemoryAdvisor:

  • ✅ More token-efficient
  • ✅ Flexible formatting
  • ✅ Custom templates
  • ❌ Loses message structure

Multi-User Chat Service

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

    public ChatService(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
            .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();
    }
    
    public void clearHistory(String userId) {
        chatMemory.clear(userId);
    }
}

Custom ChatMemory

For production, implement persistent storage:

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

public class RedisChatMemory implements ChatMemory {
    private final RedisTemplate<String, List<Message>> redis;
    
    @Override
    public void add(String conversationId, List<Message> messages) {
        String key = "chat:" + conversationId;
        redis.opsForValue().set(key, messages, Duration.ofDays(7));
    }
    
    @Override
    public List<Message> get(String conversationId, int lastN) {
        String key = "chat:" + conversationId;
        List<Message> messages = redis.opsForValue().get(key);
        if (messages == null) return Collections.emptyList();
        
        int start = Math.max(0, messages.size() - lastN);
        return messages.subList(start, messages.size());
    }
    
    @Override
    public void clear(String conversationId) {
        redis.delete("chat:" + conversationId);
    }
}

Memory with Size Limits

public class LimitedChatMemory implements ChatMemory {
    private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>();
    private final int maxMessages;
    
    public LimitedChatMemory(int maxMessages) {
        this.maxMessages = maxMessages;
    }
    
    @Override
    public void add(String conversationId, List<Message> messages) {
        conversations.compute(conversationId, (id, existing) -> {
            List<Message> all = existing != null ? 
                new ArrayList<>(existing) : new ArrayList<>();
            all.addAll(messages);
            
            // Keep only last N messages
            if (all.size() > maxMessages) {
                return all.subList(all.size() - maxMessages, all.size());
            }
            return all;
        });
    }
    
    // ... other methods
}

Streaming with Memory

Memory works automatically with streaming:

Flux<String> stream = client
    .prompt("Continue our story")
    .advisors(spec -> spec.param("conversationId", "user-123"))
    .stream()
    .content();

stream.subscribe(System.out::print);
// Memory saved after stream completes

Next Steps

  • Memory Advisors Reference - Complete API
  • Working with Advisors - Advisor patterns
  • Real-World Scenarios - Production examples

Install with Tessl CLI

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

docs

index.md

tile.json