Advisor System
The Advisor System integrates with Spring AI's CallAdvisor and StreamAdvisor interfaces to intercept and enrich every LLM request and response. The AdvisorRegistry manages advisor lifecycle, domain organization, and enable/disable state. The UnifiedLLMOrchestrator automatically applies all enabled advisors to every ChatClient it creates.
Overview
Advisors form a chain-of-responsibility around every LLM call. Each advisor can modify the request before it reaches the LLM, inspect or transform the response after, and add context metadata for downstream advisors. Advisors are organized by domain (e.g., "SECURITY", "AUDIT") and ordered by priority.
The system provides:
- Domain-based organization — Advisors are grouped by domain, enabling bulk enable/disable operations.
- Priority ordering — Advisors execute in order of their
getOrder()value (lower runs first). - Hot reload — Advisors can be registered, unregistered, enabled, and disabled at runtime.
- Error isolation — Non-blocking advisor errors skip the advisor and continue the chain; blocking errors halt execution.
AdvisorRegistry
Central registry for managing advisor lifecycle. Thread-safe with internal caching of the enabled advisor list.
public class AdvisorRegistry
BaseAdvisor, it is validated and indexed by domain. Invalidates the enabled cache.domain.name).BaseAdvisor.RegistryStats is a static nested class exposing the public fields totalAdvisors (int), totalDomains (int), and advisorsPerDomain (Map<String, Integer>).BaseAdvisor
Abstract base class implementing both CallAdvisor and StreamAdvisor. Provides the template method pattern with lifecycle hooks, error handling, and context enrichment.
public abstract class BaseAdvisor implements CallAdvisor, StreamAdvisor
Constructor
domain.name.Abstract Methods (Must Implement)
Overridable Hooks
beforeStream(ChatClientRequest request)— Defaults to callingbeforeCall(request). Override for stream-specific pre-processing.afterStream(ChatClientResponse response, ChatClientRequest request)— No-op by default. Invoked on every streamedChatClientResponse. Override for stream-specific post-processing.enrichContext(Map<String, Object> context)— Addsadvisor.domain,advisor.name, andadvisor.timestampto the request context.handleBlockingError(AdvisorException e, ChatClientRequest request)— Setsadvisor.error,advisor.error.message,advisor.error.domain, andadvisor.blocked.byon the request context and rethrows the exception.
Lifecycle & State
getName()— Returns the full advisor name formatted asdomain.name.getOrder()— Returns the constructorordervalue. Lower values execute first.isEnabled()— Returns whether the advisor is currently enabled. Override to consult external configuration.setEnabled(boolean enabled)— Toggles the advisor at runtime. Called byAdvisorRegistry.enableDomain(...),disableDomain(...),enableAll(), anddisableAll().validate()— Invoked by the registry duringregister(...). Returnsfalsewhendomainornameis empty, causing registration to be rolled back.
Error Handling
AdvisorException is created through two static factory methods and supports two modes:
AdvisorException.blocking(domain, advisorName, message)— Halts the advisor chain and prevents the LLM call. Used for security enforcement.AdvisorException.nonBlocking(domain, advisorName, message)— Logs the error and continues the chain withchain.nextCall(request). Used for optional enrichment.
Any other Exception thrown from beforeCall/afterCall is treated as non-blocking: the failure is recorded as {advisorName}.error on the request context and the chain continues.
SecurityContextAdvisor
Injects security context (user identity, session, authentication state, authorities) into every LLM request. Supports async security context recovery through AsyncSecurityContextProvider implementations, including Redis-backed providers in distributed deployments.
public class SecurityContextAdvisor extends BaseAdvisor
Injected Context Keys
| Key | Description |
|---|---|
user.id | Authenticated user identifier. |
session.id | Session identifier. |
authenticated | Boolean authentication status. |
authorities | String representation of granted authorities. |
principal.type | Type of the authentication principal. |
async.context | Always true, indicating async context mode. |
timestamp | Request timestamp in milliseconds. |
Authentication Enforcement: When contexa.advisor.security.require-authentication is true, unauthenticated requests throw a blocking AdvisorException that prevents the LLM call.
Code Examples
Implementing a Custom Advisor
@Component
public class RateLimitAdvisor extends BaseAdvisor {
private final RateLimiter rateLimiter;
public RateLimitAdvisor(RateLimiter rateLimiter) {
super("INFRASTRUCTURE", "rate-limit", 10);
this.rateLimiter = rateLimiter;
}
@Override
protected ChatClientRequest beforeCall(
ChatClientRequest request) {
String userId = (String) request.context()
.get("user.id");
if (!rateLimiter.tryAcquire(userId)) {
throw AdvisorException.blocking(
getDomain(), getName(),
"Rate limit exceeded for user: " + userId);
}
return request;
}
@Override
protected ChatClientResponse afterCall(
ChatClientResponse response,
ChatClientRequest request) {
return response;
}
}
Managing Advisors at Runtime
// Disable all security advisors during maintenance
advisorRegistry.disableDomain("SECURITY");
// Re-enable after maintenance
advisorRegistry.enableDomain("SECURITY");
// Check registry health
AdvisorRegistry.RegistryStats stats =
advisorRegistry.getStats();
// stats.totalAdvisors, stats.totalDomains,
// stats.advisorsPerDomain
Related
For advisor
application.yml properties (enabled advisors, ordering, security context propagation), see AI Configuration — covers ContexaAdvisorProperties.