Spring AI - Advisors & Middleware Pattern
Description
Complete guide for Advisors in Spring AI. Advisors implement the chain of responsibility pattern, providing middleware-like interceptors for request/response processing. Covers memory, RAG, tool calling, authentication, and custom advisor implementation.
When to Use
- Adding cross-cutting concerns to chat operations
- Implementing chat memory management
- Injecting context (RAG, tools) automatically
- Request/response filtering and validation
- Security and authorization checks
- Observability and monitoring
- Response post-processing
- Multi-advisor chains
Topics Covered
1. Advisor Architecture
- Chain of Responsibility Pattern: Sequential processing
- Request/Response Interception: Both directions
- Advisor Stack: Multiple advisors in sequence
- Short-circuit Logic: Early termination conditions
- Context Propagation: Sharing state across chain
2. Built-in Advisors
ChatMemoryAdvisor
- Automatic chat history management
- Context window optimization
- Token-aware message windowing
- Repository-based storage
- Thread-safe operations
RAGAdvisor (Recursive)
- Automatic document retrieval
- Query transformation on demand
- Context augmentation
- Document chunking integration
- Configurable retrieval strategy
ToolsAdvisor
- Automatic tool discovery and registration
- Function call handling
- Tool execution with error recovery
- OpenAI-compatible format
- Multi-tool orchestration
ParallelToolsAdvisor
- Execute multiple tools concurrently
- Result aggregation
- Timeout management
- Partial failure handling
ReasoningAdvisor
- Step-by-step reasoning support
- Thought process tracking
- Multi-hop reasoning
- Uncertainty quantification
SecurityAdvisor
- Input validation and sanitization
- Output filtering
- Role-based access control
- Audit logging
- Threat detection
MonitoringAdvisor
- Performance metrics
- Token usage tracking
- Cost analysis
- Error rate monitoring
- Latency analysis
3. Custom Advisor Implementation
- Extending ChatClientRequestAdvisor
- Before/after processing hooks
- Error handling in advisors
- State management
- Testing custom advisors
4. Advisor Configuration
- Registration order (chain sequence)
- Conditional activation
- Dynamic advisor loading
- Advisor parameters
- Runtime modification
5. Recursive Advisor Execution
- Two-stage process: collect, then execute
- Advisor interaction
- Nested advisor calls
- Termination conditions
- Performance optimization
Code Patterns
Basic Advisor Chain
@Configuration
public class AdvisorConfiguration {
@Bean
public ChatClientRequestAdvisor chatMemoryAdvisor(
ChatMemoryParticipant chatMemory) {
return new ChatMemoryAdvisor(chatMemory);
}
@Bean
public ChatClientRequestAdvisor ragAdvisor(
VectorStore vectorStore) {
return new RAGAdvisor(vectorStore);
}
@Bean
public ChatClientRequestAdvisor toolsAdvisor(
ToolRegistry toolRegistry) {
return new ToolsAdvisor(toolRegistry);
}
}
Using Advisors with ChatClient
@Service
public class ConversationService {
@Autowired
private ChatClient chatClient;
@Autowired
private List<ChatClientRequestAdvisor> advisors;
public String chat(String userMessage, String conversationId) {
// Advisors are automatically applied in order
return chatClient.prompt()
.user(userMessage)
.advisors(advisors) // Chain of advisors
.call()
.content();
}
}
ChatMemoryAdvisor
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemoryParticipant chatMemory(
ChatMemoryStore store) {
return new InMemoryChatMemoryStore()
.withMaxMessages(50)
.withTokenLimit(2000);
}
@Bean
public ChatClientRequestAdvisor memoryAdvisor(
ChatMemoryParticipant chatMemory) {
return ChatMemoryAdvisor.builder()
.chatMemory(chatMemory)
.userIdResolver(request -> getCurrentUserId())
.conversationIdResolver(request -> getCurrentConversationId())
.build();
}
}
RAGAdvisor (Recursive)
@Configuration
public class RAGAdvisorConfig {
@Bean
public ChatClientRequestAdvisor ragAdvisor(
VectorStore vectorStore) {
return new RAGAdvisor(
vectorStore,
new DefaultQueryTransformer(),
DefaultRetrievalStrategy.TOP_K_5,
DefaultContextAssembly.RANKED
);
}
}
Combined Advisors (Memory + RAG + Tools)
@Service
public class SmartAssistant {
@Autowired
private ChatClient chatClient;
@Autowired
private ChatClientRequestAdvisor memoryAdvisor;
@Autowired
private ChatClientRequestAdvisor ragAdvisor;
@Autowired
private ChatClientRequestAdvisor toolsAdvisor;
public String ask(String question, String conversationId) {
return chatClient.prompt()
.system("You are a helpful assistant")
.user(question)
.advisors(List.of(
memoryAdvisor, // First: load chat history
ragAdvisor, // Second: augment with docs
toolsAdvisor // Third: enable tool use
))
.call()
.content();
}
}
Custom Advisor Implementation
public class CustomValidationAdvisor
extends ChatClientRequestAdvisor {
@Override
public ChatClientRequestAdvisor.ChatClientRequestAdvisorContext
before(ChatClientRequestAdvisor.ChatClientRequestAdvisorContext context) {
// Validate user input
String userMessage = extractUserMessage(context);
if (containsProhibitedContent(userMessage)) {
throw new SecurityException("Invalid content detected");
}
return context;
}
@Override
public ChatClientRequestAdvisor.ChatClientRequestAdvisorContext
after(ChatClientRequestAdvisor.ChatClientRequestAdvisorContext context) {
// Post-process response
String response = context.getResponse().getResult().getOutput().getContent();
String sanitized = sanitizeResponse(response);
return context.withResponse(sanitized);
}
}
Conditional Advisor Activation
@Configuration
public class ConditionalAdvisorConfig {
@Bean
@ConditionalOnProperty(name = "ai.rag.enabled", havingValue = "true")
public ChatClientRequestAdvisor ragAdvisor(VectorStore vectorStore) {
return new RAGAdvisor(vectorStore);
}
@Bean
@ConditionalOnProperty(name = "ai.security.enabled", havingValue = "true")
public ChatClientRequestAdvisor securityAdvisor() {
return new SecurityAdvisor();
}
}
Advisor with Metrics
public class MetricsAdvisor extends ChatClientRequestAdvisor {
private final MeterRegistry meterRegistry;
@Override
public ChatClientRequestAdvisor.ChatClientRequestAdvisorContext
before(ChatClientRequestAdvisor.ChatClientRequestAdvisorContext context) {
context.setAttribute("startTime", System.currentTimeMillis());
return context;
}
@Override
public ChatClientRequestAdvisor.ChatClientRequestAdvisorContext
after(ChatClientRequestAdvisor.ChatClientRequestAdvisorContext context) {
long duration = System.currentTimeMillis() -
(long) context.getAttribute("startTime");
meterRegistry.timer("ai.request.duration").record(duration, TimeUnit.MILLISECONDS);
meterRegistry.counter("ai.request.success").increment();
return context;
}
}
Configuration
Advisor Order
@Configuration
public class AdvisorOrderConfig {
@Bean("advisors")
public List<ChatClientRequestAdvisor> advisors(
ChatClientRequestAdvisor memoryAdvisor,
ChatClientRequestAdvisor ragAdvisor,
ChatClientRequestAdvisor toolsAdvisor,
ChatClientRequestAdvisor securityAdvisor) {
return List.of(
securityAdvisor, // 1. Validate early
memoryAdvisor, // 2. Load context
ragAdvisor, // 3. Augment with docs
toolsAdvisor // 4. Enable tools
// Add custom advisors here
);
}
}
Properties
spring.ai.advisor.memory.enabled=true
spring.ai.advisor.memory.max-messages=50
spring.ai.advisor.rag.enabled=true
spring.ai.advisor.rag.top-k=5
spring.ai.advisor.tools.enabled=true
spring.ai.advisor.recursive.enabled=true
Best Practices
- Keep advisor logic focused and composable
- Order advisors thoughtfully (security early)
- Handle exceptions gracefully
- Implement proper resource cleanup
- Test advisors in isolation
- Monitor advisor performance
- Document advisor interactions
- Use type safety for advisor configuration
Related Skills
chat-memory/SKILL.md- Chat memory storagerag-retrieval/SKILL.md- RAG integrationtools-agents/SKILL.md- Tool callingchat-models/SKILL.md- Chat models
References
- API:
/pages/api/advisors.adoc - Recursive:
/pages/api/advisors-recursive.adoc - Examples: Provider-specific advisor usage