CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-autoconfigure-model-chat-memory

Spring Boot auto-configuration for chat memory functionality in Spring AI applications

Overview
Eval results
Files

real-world-scenarios.mddocs/examples/

Real-World Scenarios

Production-ready examples for common use cases.

Scenario 1: Multi-User Chat Application

A web application where multiple users have independent conversations with an AI assistant.

@Service
public class MultiUserChatService {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private AIService aiService;

    public ChatResponse handleUserMessage(String userId, String message) {
        String conversationId = "user:" + userId;
        
        // Add user message
        chatMemory.add(conversationId, new UserMessage(message));
        
        // Get conversation history
        List<Message> history = chatMemory.get(conversationId);
        
        // Generate AI response with context
        String aiResponse = aiService.generateResponse(history);
        
        // Store AI response
        chatMemory.add(conversationId, new AssistantMessage(aiResponse));
        
        return new ChatResponse(aiResponse, conversationId);
    }
    
    public void initializeUserConversation(String userId, String userRole) {
        String conversationId = "user:" + userId;
        SystemMessage systemMessage = new SystemMessage(
            "You are assisting a " + userRole + " with technical questions. " +
            "Provide clear, concise answers."
        );
        chatMemory.add(conversationId, systemMessage);
    }
    
    public List<Message> getUserHistory(String userId) {
        return chatMemory.get("user:" + userId);
    }
    
    public void clearUserHistory(String userId) {
        chatMemory.clear("user:" + userId);
    }
}

Scenario 2: Customer Support Bot

A customer support system that maintains context across multiple support sessions.

@Service
public class CustomerSupportService {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private CustomerRepository customerRepository;
    
    @Autowired
    private TicketService ticketService;

    public SupportResponse handleSupportMessage(String ticketId, String message) {
        String conversationId = "ticket:" + ticketId;
        
        // Initialize conversation with customer context if new
        if (chatMemory.get(conversationId).isEmpty()) {
            initializeSupportConversation(ticketId);
        }
        
        // Add customer message
        chatMemory.add(conversationId, new UserMessage(message));
        
        // Get full conversation history
        List<Message> history = chatMemory.get(conversationId);
        
        // Generate contextual response
        String response = generateSupportResponse(history, ticketId);
        
        // Store assistant response
        chatMemory.add(conversationId, new AssistantMessage(response));
        
        // Update ticket
        ticketService.updateTicket(ticketId, message, response);
        
        return new SupportResponse(response, ticketId);
    }
    
    private void initializeSupportConversation(String ticketId) {
        Ticket ticket = ticketService.getTicket(ticketId);
        Customer customer = customerRepository.findById(ticket.getCustomerId());
        
        String systemContext = String.format(
            "You are a customer support agent. " +
            "Customer: %s (Account: %s, Tier: %s). " +
            "Issue Category: %s. " +
            "Be professional, empathetic, and solution-focused.",
            customer.getName(),
            customer.getAccountNumber(),
            customer.getTier(),
            ticket.getCategory()
        );
        
        String conversationId = "ticket:" + ticketId;
        chatMemory.add(conversationId, new SystemMessage(systemContext));
        
        // Add ticket history if exists
        if (ticket.getPreviousMessages() != null) {
            List<Message> previousMessages = ticket.getPreviousMessages();
            chatMemory.add(conversationId, previousMessages);
        }
    }
    
    public void archiveResolvedTicket(String ticketId) {
        String conversationId = "ticket:" + ticketId;
        List<Message> messages = chatMemory.get(conversationId);
        
        // Archive to long-term storage
        ticketService.archiveConversation(ticketId, messages);
        
        // Clear from active memory
        chatMemory.clear(conversationId);
    }
}

Scenario 3: Code Review Assistant

An AI assistant that helps developers with code reviews, maintaining context across multiple review sessions.

@Service
public class CodeReviewAssistant {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private CodeAnalysisService codeAnalysisService;

    public ReviewResponse reviewCode(String pullRequestId, String code, String question) {
        String conversationId = "pr:" + pullRequestId;
        
        // Initialize with PR context if new
        if (chatMemory.get(conversationId).isEmpty()) {
            initializeReviewContext(pullRequestId);
        }
        
        // Add code snippet as user message
        UserMessage codeMessage = UserMessage.builder()
            .text("Review this code:\n```\n" + code + "\n```\n\nQuestion: " + question)
            .metadata(Map.of("codeLength", code.length(), "language", detectLanguage(code)))
            .build();
        
        chatMemory.add(conversationId, codeMessage);
        
        // Analyze code
        CodeAnalysis analysis = codeAnalysisService.analyze(code);
        
        // Generate review with context
        List<Message> history = chatMemory.get(conversationId);
        String review = generateReview(history, analysis);
        
        // Store review
        chatMemory.add(conversationId, new AssistantMessage(review));
        
        return new ReviewResponse(review, analysis.getIssues());
    }
    
    private void initializeReviewContext(String pullRequestId) {
        PullRequest pr = getPullRequest(pullRequestId);
        
        String systemContext = String.format(
            "You are a senior code reviewer. " +
            "PR: %s - %s. " +
            "Language: %s. " +
            "Focus on: code quality, security, performance, and best practices. " +
            "Be constructive and provide specific suggestions.",
            pr.getTitle(),
            pr.getDescription(),
            pr.getLanguage()
        );
        
        chatMemory.add("pr:" + pullRequestId, new SystemMessage(systemContext));
    }
    
    public void finalizePullRequest(String pullRequestId, boolean approved) {
        String conversationId = "pr:" + pullRequestId;
        List<Message> reviewHistory = chatMemory.get(conversationId);
        
        // Save review history to PR
        savePullRequestReview(pullRequestId, reviewHistory, approved);
        
        // Clear active memory
        chatMemory.clear(conversationId);
    }
}

Scenario 4: Educational Tutor Bot

An AI tutor that adapts to student learning pace and maintains learning context.

@Service
public class EducationalTutorService {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private StudentProgressService progressService;

    public TutorResponse handleStudentQuestion(String studentId, String subject, String question) {
        String conversationId = "student:" + studentId + ":subject:" + subject;
        
        // Initialize learning session if new
        if (chatMemory.get(conversationId).isEmpty()) {
            initializeLearningSession(studentId, subject);
        }
        
        // Add student question
        chatMemory.add(conversationId, new UserMessage(question));
        
        // Get learning history
        List<Message> history = chatMemory.get(conversationId);
        
        // Generate adaptive response
        StudentProgress progress = progressService.getProgress(studentId, subject);
        String response = generateAdaptiveResponse(history, progress);
        
        // Store tutor response
        chatMemory.add(conversationId, new AssistantMessage(response));
        
        // Update progress
        progressService.updateProgress(studentId, subject, question, response);
        
        return new TutorResponse(response, progress.getLevel());
    }
    
    private void initializeLearningSession(String studentId, String subject) {
        Student student = getStudent(studentId);
        StudentProgress progress = progressService.getProgress(studentId, subject);
        
        String systemContext = String.format(
            "You are a patient and encouraging tutor for %s. " +
            "Student level: %s. " +
            "Learning style: %s. " +
            "Adapt explanations to their level. Use examples and analogies. " +
            "Encourage questions and provide positive reinforcement.",
            subject,
            progress.getLevel(),
            student.getLearningStyle()
        );
        
        String conversationId = "student:" + studentId + ":subject:" + subject;
        chatMemory.add(conversationId, new SystemMessage(systemContext));
    }
    
    public void endLearningSession(String studentId, String subject) {
        String conversationId = "student:" + studentId + ":subject:" + subject;
        List<Message> sessionHistory = chatMemory.get(conversationId);
        
        // Analyze session for insights
        progressService.analyzeSession(studentId, subject, sessionHistory);
        
        // Keep session for reference (don't clear immediately)
        // TTL or manual cleanup can handle old sessions
    }
}

Scenario 5: Document Analysis Assistant

An assistant that helps users analyze and query documents with conversational context.

@Service
public class DocumentAnalysisService {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private DocumentProcessor documentProcessor;
    
    @Autowired
    private VectorStore vectorStore;

    public AnalysisResponse analyzeDocument(String sessionId, MultipartFile document, String query) {
        String conversationId = "doc-session:" + sessionId;
        
        // Process document on first upload
        if (chatMemory.get(conversationId).isEmpty()) {
            initializeDocumentSession(sessionId, document);
        }
        
        // Add user query
        chatMemory.add(conversationId, new UserMessage(query));
        
        // Retrieve relevant document sections
        List<String> relevantSections = vectorStore.similaritySearch(query, 5);
        
        // Get conversation history
        List<Message> history = chatMemory.get(conversationId);
        
        // Generate analysis with context
        String analysis = generateDocumentAnalysis(history, relevantSections);
        
        // Store analysis
        chatMemory.add(conversationId, new AssistantMessage(analysis));
        
        return new AnalysisResponse(analysis, relevantSections);
    }
    
    private void initializeDocumentSession(String sessionId, MultipartFile document) {
        // Process document
        DocumentMetadata metadata = documentProcessor.extractMetadata(document);
        String documentSummary = documentProcessor.generateSummary(document);
        
        // Store in vector database
        vectorStore.addDocument(sessionId, document);
        
        // Initialize conversation with document context
        String systemContext = String.format(
            "You are analyzing a document: '%s' (%s, %d pages). " +
            "Summary: %s. " +
            "Answer questions based on the document content. " +
            "Cite specific sections when possible.",
            metadata.getTitle(),
            metadata.getType(),
            metadata.getPageCount(),
            documentSummary
        );
        
        String conversationId = "doc-session:" + sessionId;
        chatMemory.add(conversationId, new SystemMessage(systemContext));
    }
    
    public void closeDocumentSession(String sessionId) {
        String conversationId = "doc-session:" + sessionId;
        List<Message> sessionHistory = chatMemory.get(conversationId);
        
        // Archive session
        archiveSession(sessionId, sessionHistory);
        
        // Cleanup
        chatMemory.clear(conversationId);
        vectorStore.deleteDocument(sessionId);
    }
}

Scenario 6: Batch Conversation Processing

Process multiple conversations efficiently in batch operations.

@Service
public class BatchConversationProcessor {

    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private ChatMemoryRepository repository;

    public void processBatchMessages(Map<String, List<Message>> conversationBatches) {
        conversationBatches.forEach((conversationId, messages) -> {
            // Batch add is more efficient than individual adds
            chatMemory.add(conversationId, messages);
        });
    }
    
    public Map<String, Integer> getConversationSizes() {
        List<String> conversationIds = repository.findConversationIds();
        
        return conversationIds.parallelStream()
            .collect(Collectors.toMap(
                id -> id,
                id -> repository.findByConversationId(id).size()
            ));
    }
    
    public void cleanupInactiveConversations(Duration inactivityThreshold) {
        List<String> conversationIds = repository.findConversationIds();
        Instant cutoff = Instant.now().minus(inactivityThreshold);
        
        conversationIds.parallelStream()
            .filter(id -> isInactive(id, cutoff))
            .forEach(repository::deleteByConversationId);
    }
    
    private boolean isInactive(String conversationId, Instant cutoff) {
        List<Message> messages = repository.findByConversationId(conversationId);
        if (messages.isEmpty()) return true;
        
        // Check last message timestamp from metadata
        Message lastMessage = messages.get(messages.size() - 1);
        Object timestamp = lastMessage.getMetadata().get("timestamp");
        
        if (timestamp instanceof Instant) {
            return ((Instant) timestamp).isBefore(cutoff);
        }
        
        return false;
    }
    
    public void exportConversations(List<String> conversationIds, Path exportPath) {
        conversationIds.forEach(id -> {
            List<Message> messages = chatMemory.get(id);
            exportConversation(id, messages, exportPath);
        });
    }
}

Scenario 7: Async Conversation Handling

Handle high-throughput conversations asynchronously.

@Service
public class AsyncConversationService {

    @Autowired
    private ChatMemory chatMemory;
    
    private final ExecutorService executor = Executors.newFixedThreadPool(20);

    public CompletableFuture<String> handleMessageAsync(String conversationId, String message) {
        return CompletableFuture.supplyAsync(() -> {
            // Add user message
            chatMemory.add(conversationId, new UserMessage(message));
            
            // Get history
            List<Message> history = chatMemory.get(conversationId);
            
            // Generate response
            String response = generateResponse(history);
            
            // Store response
            chatMemory.add(conversationId, new AssistantMessage(response));
            
            return response;
        }, executor);
    }
    
    public CompletableFuture<Void> handleMultipleMessagesAsync(
            Map<String, String> conversationMessages) {
        
        List<CompletableFuture<Void>> futures = conversationMessages.entrySet()
            .stream()
            .map(entry -> handleMessageAsync(entry.getKey(), entry.getValue())
                .thenAccept(response -> logResponse(entry.getKey(), response)))
            .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
    }
    
    @PreDestroy
    public void shutdown() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

Scenario 8: Conversation Migration

Migrate conversations between different repository implementations.

@Service
public class ConversationMigrationService {

    @Autowired
    @Qualifier("sourceRepository")
    private ChatMemoryRepository sourceRepository;
    
    @Autowired
    @Qualifier("targetRepository")
    private ChatMemoryRepository targetRepository;

    public MigrationResult migrateAllConversations() {
        List<String> conversationIds = sourceRepository.findConversationIds();
        int total = conversationIds.size();
        int successful = 0;
        int failed = 0;
        
        for (String conversationId : conversationIds) {
            try {
                migrateConversation(conversationId);
                successful++;
            } catch (Exception e) {
                logger.error("Failed to migrate conversation: {}", conversationId, e);
                failed++;
            }
        }
        
        return new MigrationResult(total, successful, failed);
    }
    
    private void migrateConversation(String conversationId) {
        List<Message> messages = sourceRepository.findByConversationId(conversationId);
        
        if (!messages.isEmpty()) {
            targetRepository.saveAll(conversationId, messages);
            logger.info("Migrated conversation {} ({} messages)", 
                conversationId, messages.size());
        }
    }
    
    public void verifyMigration() {
        List<String> sourceIds = sourceRepository.findConversationIds();
        List<String> targetIds = targetRepository.findConversationIds();
        
        Set<String> missingIds = new HashSet<>(sourceIds);
        missingIds.removeAll(targetIds);
        
        if (!missingIds.isEmpty()) {
            logger.warn("Missing conversations in target: {}", missingIds);
        }
        
        // Verify message counts
        for (String id : sourceIds) {
            int sourceCount = sourceRepository.findByConversationId(id).size();
            int targetCount = targetRepository.findByConversationId(id).size();
            
            if (sourceCount != targetCount) {
                logger.warn("Message count mismatch for {}: source={}, target={}", 
                    id, sourceCount, targetCount);
            }
        }
    }
}

Best Practices from These Scenarios

  1. Use Meaningful Conversation IDs: Prefix with entity type (user:, ticket:, pr:)
  2. Initialize with Context: Set system messages with relevant context
  3. Batch Operations: Use batch adds for better performance
  4. Cleanup Strategy: Implement TTL or manual cleanup for old conversations
  5. Error Handling: Always wrap operations in try-catch blocks
  6. Async Processing: Use async for high-throughput scenarios
  7. Metadata: Add metadata to messages for filtering and analysis
  8. Archive Strategy: Move old conversations to long-term storage

Next Steps

  • Edge Cases - Handle error scenarios
  • Performance Reference - Optimization techniques
  • API Reference - Complete API documentation
tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-model-chat-memory@1.1.0

docs

examples

edge-cases.md

real-world-scenarios.md

index.md

tile.json