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

real-world-scenarios.mddocs/examples/

Real-World Scenarios

Production-ready patterns and complete applications.

Multi-User Chat Service

@Service
public class ChatService {
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;

    public ChatService(ChatModel chatModel) {
        this.chatMemory = new RedisChatMemory(redisTemplate);
        
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("You are a helpful assistant")
            .defaultAdvisors(
                SafeGuardAdvisor.builder()
                    .sensitiveWords(List.of("password", "secret"))
                    .order(100)
                    .build(),
                MessageChatMemoryAdvisor.builder(chatMemory)
                    .order(1001)
                    .build(),
                SimpleLoggerAdvisor.builder()
                    .order(Ordered.LOWEST_PRECEDENCE)
                    .build()
            )
            .defaultOptions(ChatOptionsBuilder.builder()
                .withTemperature(0.7)
                .withMaxTokens(1000)
                .build())
            .observationRegistry(observationRegistry)
            .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();
    }
}

@RestController
@RequestMapping("/api/chat")
public class ChatController {
    private final ChatService chatService;

    @PostMapping
    public ResponseEntity<String> chat(
        @RequestHeader("User-Id") String userId,
        @RequestBody ChatRequest request
    ) {
        String response = chatService.chat(userId, request.message());
        return ResponseEntity.ok(response);
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> chatStream(
        @RequestHeader("User-Id") String userId,
        @RequestParam String message
    ) {
        return chatService.chatStream(userId, message)
            .map(chunk -> ServerSentEvent.builder(chunk).build());
    }
}

RAG Application

@Service
public class RAGService {
    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public RAGService(ChatModel chatModel, VectorStore vectorStore) {
        this.vectorStore = vectorStore;
        
        ToolCallback searchTool = ToolCallback.builder()
            .function("searchKnowledge", this::searchKnowledge)
            .description("Search knowledge base for relevant information")
            .inputType(SearchRequest.class)
            .build();
        
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("""
                You are a helpful assistant with access to a knowledge base.
                Use the searchKnowledge tool to find relevant information.
                Always cite sources when using retrieved information.
                """)
            .defaultAdvisors(
                ToolCallAdvisor.builder().build(),
                SimpleLoggerAdvisor.builder().build()
            )
            .defaultTools(searchTool)
            .build();
    }

    public SearchResult searchKnowledge(SearchRequest request) {
        List<Document> docs = vectorStore.similaritySearch(
            SearchRequest.query(request.query())
                .withTopK(5)
                .withSimilarityThreshold(0.7)
        );
        
        return new SearchResult(
            docs.stream()
                .map(Document::getContent)
                .collect(Collectors.toList())
        );
    }

    public String query(String question) {
        return chatClient
            .prompt(question)
            .call()
            .content();
    }
}

record SearchRequest(String query) {}
record SearchResult(List<String> documents) {}

Content Generation with Validation

@Service
public class ContentGenerator {
    private final ChatClient chatClient;

    public ContentGenerator(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("You are a professional content writer")
            .defaultAdvisors(
                StructuredOutputValidationAdvisor.builder()
                    .outputType(Article.class)
                    .maxRepeatAttempts(3)
                    .build()
            )
            .defaultOptions(ChatOptionsBuilder.builder()
                .withTemperature(0.8)  // More creative
                .withMaxTokens(2000)
                .build())
            .build();
    }

    public Article generateArticle(String topic, String style) {
        Article article = chatClient
            .prompt()
            .system(spec -> spec
                .text("Write in {style} style")
                .param("style", style)
            )
            .user(spec -> spec
                .text("Write an article about {topic}")
                .param("topic", topic)
            )
            .call()
            .entity(Article.class);
        
        // Validate
        if (article == null || article.content().length() < 100) {
            throw new IllegalStateException("Generated article too short");
        }
        
        return article;
    }
}

record Article(
    String title,
    String content,
    List<String> tags,
    String summary
) {}

Customer Support Bot

@Service
public class SupportBot {
    private final ChatClient chatClient;
    private final TicketService ticketService;

    public SupportBot(ChatModel chatModel, TicketService ticketService) {
        this.ticketService = ticketService;
        
        ToolCallback createTicket = ToolCallback.builder()
            .function("createTicket", this::createTicket)
            .description("Create a support ticket")
            .inputType(TicketRequest.class)
            .build();
        
        ToolCallback searchFAQ = ToolCallback.builder()
            .function("searchFAQ", this::searchFAQ)
            .description("Search FAQ database")
            .inputType(FAQSearchRequest.class)
            .build();
        
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("""
                You are a customer support assistant.
                - First try to answer from FAQ
                - If issue needs human support, create a ticket
                - Be empathetic and professional
                """)
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build(),
                ToolCallAdvisor.builder().build()
            )
            .defaultTools(createTicket, searchFAQ)
            .build();
    }

    public String handleCustomerQuery(String customerId, String query) {
        return chatClient
            .prompt()
            .user(query)
            .advisors(spec -> spec
                .param("conversationId", customerId)
            )
            .toolContext(Map.of(
                "customerId", customerId,
                "timestamp", Instant.now()
            ))
            .call()
            .content();
    }

    private TicketResponse createTicket(TicketRequest request) {
        return ticketService.create(request);
    }

    private FAQResult searchFAQ(FAQSearchRequest request) {
        return faqService.search(request.query());
    }
}

Code Review Assistant

@Service
public class CodeReviewAssistant {
    private final ChatClient chatClient;

    public CodeReviewAssistant(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("""
                You are a senior code reviewer.
                Focus on:
                - Security vulnerabilities
                - Performance issues
                - Best practices
                - Code maintainability
                Provide specific, actionable feedback.
                """)
            .defaultOptions(ChatOptionsBuilder.builder()
                .withTemperature(0.3)  // More deterministic
                .build())
            .build();
    }

    public CodeReview reviewCode(String code, String language) {
        return chatClient
            .prompt()
            .system(spec -> spec
                .text("Review {language} code")
                .param("language", language)
            )
            .user(spec -> spec
                .text("""
                    Review this code:
                    
                    ```{language}
                    {code}
                    ```
                    """)
                .param("language", language)
                .param("code", code)
            )
            .call()
            .entity(CodeReview.class);
    }
}

record CodeReview(
    String summary,
    List<Issue> issues,
    List<String> suggestions,
    int overallScore
) {}

record Issue(
    String severity,
    String category,
    String description,
    String location
) {}

Data Analysis Service

@Service
public class DataAnalysisService {
    private final ChatClient chatClient;

    public DataAnalysisService(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
            .defaultSystem("You are a data analyst. Provide insights from data.")
            .defaultAdvisors(
                StructuredOutputValidationAdvisor.builder()
                    .outputType(Analysis.class)
                    .maxRepeatAttempts(3)
                    .build()
            )
            .build();
    }

    public Analysis analyzeData(String dataset, String question) {
        ResponseEntity<ChatResponse, Analysis> result = chatClient
            .prompt()
            .user(spec -> spec
                .text("""
                    Analyze this data and answer: {question}
                    
                    Data:
                    {dataset}
                    """)
                .param("question", question)
                .param("dataset", dataset)
            )
            .call()
            .responseEntity(Analysis.class);
        
        // Log token usage
        ChatResponse response = result.getResponse();
        if (response != null) {
            var usage = response.getMetadata().getUsage();
            log.info("Analysis used {} tokens", usage.getTotalTokens());
        }
        
        return result.getEntity();
    }
}

record Analysis(
    String summary,
    List<String> keyFindings,
    Map<String, Double> metrics,
    List<String> recommendations
) {}

Error Handling Patterns

@Service
public class ResilientChatService {
    private final ChatClient chatClient;
    private final RetryTemplate retryTemplate;

    public ResilientChatService(ChatModel chatModel) {
        this.chatClient = ChatClient.create(chatModel);
        
        this.retryTemplate = RetryTemplate.builder()
            .maxAttempts(3)
            .exponentialBackoff(1000, 2.0, 10000)
            .retryOn(ChatModelException.class)
            .build();
    }

    public String chatWithRetry(String message) {
        return retryTemplate.execute(context -> {
            try {
                String response = chatClient
                    .prompt(message)
                    .call()
                    .content();
                
                if (response == null || response.isBlank()) {
                    throw new IllegalStateException("Empty response");
                }
                
                return response;
                
            } catch (Exception e) {
                log.warn("Attempt {} failed: {}", 
                    context.getRetryCount() + 1, 
                    e.getMessage()
                );
                throw e;
            }
        });
    }

    public String chatWithFallback(String message) {
        try {
            return chatClient.prompt(message).call().content();
        } catch (Exception e) {
            log.error("Chat failed, using fallback", e);
            return "I apologize, but I'm having trouble processing your request. Please try again later.";
        }
    }
}

Next Steps

  • Edge Cases - Advanced scenarios
  • Reference Documentation - Complete API details

Install with Tessl CLI

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

docs

examples

edge-cases.md

real-world-scenarios.md

index.md

tile.json