skip to content

What are Advisors in Spring AI's ChatClient, and what does the advisor chain let you do?

level: seniorimportance: should knowfreq 50%

answer

  1. interceptor / around-advice / filter chain
  2. advisors(...) vs defaultAdvisors(...)
  3. MessageChatMemoryAdvisor + conversationId
  4. QuestionAnswerAdvisor = RAG over VectorStore
  5. getOrder() lower = earlier

basics

~20 s

Advisors are interceptors in ChatClient that wrap each request and response, letting you inject behavior around the model call — like adding conversation memory, retrieving documents for RAG, logging, or guarding content — without changing your prompt code. They run as an ordered chain, similar to a filter chain.

solid answer

~40 s

Advisors are Spring AI's interception mechanism for ChatClient — the AI analog of a servlet filter or AOP around-advice. Each advisor sits in an ordered chain and can modify the outgoing request (add messages, context, parameters) before the model call and inspect/transform the response after. You attach them per-request with .advisors(...) or globally on the builder with defaultAdvisors(...). Built-in advisors cover common cross-cutting concerns: MessageChatMemoryAdvisor / PromptChatMemoryAdvisor inject conversation history (using a conversationId param and a ChatMemory store); QuestionAnswerAdvisor and RetrievalAugmentationAdvisor implement RAG by pulling relevant chunks from a VectorStore into the prompt; SimpleLoggerAdvisor logs requests/responses; SafeGuardAdvisor blocks disallowed terms. You can write custom advisors by implementing CallAdvisor / StreamAdvisor, using getOrder() to place them (lower runs earlier). The chain keeps prompt-augmentation logic reusable and out of business code.

code

java · 13 lines
java
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),   // conversation history
        new QuestionAnswerAdvisor(vectorStore),                  // RAG from a VectorStore
        new SimpleLoggerAdvisor())                               // logging
    .build();

String reply = chatClient.prompt()
    .user("What did I ask you a moment ago?")
    // per-request advisor parameter: which conversation's memory to use
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
    .call()
    .content();

go deeper

for a junior

Know advisors are plug-ins that add behavior like memory or logging around a model call.

for a middle

Name the common built-ins (memory, QuestionAnswer/RAG, logger) and how to attach them per-request vs as defaults.

for a senior

Explain the ordered chain, conversationId-keyed ChatMemory, writing custom CallAdvisor/StreamAdvisor, and ordering pitfalls.

for a principal

Design advisor stacks for cost/latency/observability trade-offs, ensure call/stream parity, and decide advisor vs inline prompt boundaries.

**What an Advisor is.** An **Advisor** is an interceptor in the ChatClient pipeline. Conceptually it is *around-advice* (like Spring AOP) or a *filter* (like a servlet filter / WebFlux WebFilter) for the model call: it can act on the request **before** it reaches the model and on the response **after**. This lets you factor recurring prompt-engineering concerns (memory, retrieval, logging, guardrails) out of your business code into reusable, composable units. **The chain.** Advisors form an **ordered chain**. When you call `call()`/`stream()`, the request flows through each advisor in order, reaches the model, and the response flows back out. Ordering is controlled by `getOrder()` (lower = earlier/outermost). Order matters: e.g. a memory advisor that adds history should run before a RAG advisor that appends retrieved context, and both before the model. **How you attach them.** - Per request: `chatClient.prompt().advisors(new SimpleLoggerAdvisor()).user(...).call()` - Globally on the client: `ChatClient.builder(model).defaultAdvisors(memoryAdvisor, ragAdvisor).build()` — applied to every request from that client. - Advisor parameters (dynamic, per-request values such as the conversation id) are passed via a lambda: `.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))`. **Built-in advisors (the ones interviewers expect):** - **MessageChatMemoryAdvisor / PromptChatMemoryAdvisor** — add prior turns of the conversation. They read/write a **ChatMemory** store (e.g. `MessageWindowChatMemory` keeping the last N messages) keyed by a **conversationId**. MessageChatMemoryAdvisor injects history as structured messages; PromptChatMemoryAdvisor folds it into the prompt text. - **QuestionAnswerAdvisor** — classic **RAG (Retrieval-Augmented Generation)**: takes the user question, queries a **VectorStore** for semantically similar document chunks, and injects them as context so the model answers from your data. `RetrievalAugmentationAdvisor` is the newer, more configurable modular-RAG variant. - **SimpleLoggerAdvisor** — logs the request and response (enable DEBUG logging) for observability/debugging. - **SafeGuardAdvisor** — blocks requests containing configured sensitive/forbidden terms. **Writing a custom advisor.** Implement `CallAdvisor` (for `call()`) and/or `StreamAdvisor` (for `stream()`). You receive the request object, optionally mutate it, delegate to the next link in the chain (`chain.nextCall(...)`), then optionally transform the response. Override `getName()` and `getOrder()`. (In Spring AI 1.0 GA the request/response types are `ChatClientRequest`/`ChatClientResponse` and the chain types are `CallAdvisorChain`/`StreamAdvisorChain`; earlier milestones used `AdvisedRequest`/`AdvisedResponse` with `CallAroundAdvisor` — know the concept regardless of exact names.) **Gotchas / when to use.** - **Order bugs** are the classic pitfall: getting memory/RAG ordering wrong yields missing context or context added in the wrong place. - **Memory needs a conversationId.** Forgetting to pass the conversation id param means every request looks like a brand-new conversation (no memory) or, worse, everyone shares one memory bucket. - **Call vs stream parity.** A custom advisor used on both paths must implement both `CallAdvisor` and `StreamAdvisor`; streaming advice must handle a Flux, which is harder than the blocking path. - **Cost/latency.** RAG and memory advisors enlarge the prompt (more tokens = more cost/latency); window your memory and cap retrieved chunks. - **When to use advisors vs. plain prompt code:** use advisors for cross-cutting, reusable concerns applied consistently; keep one-off prompt shaping inline.

  • How does a chat-memory advisor know which conversation's history to load?
    By a conversationId passed as an advisor parameter (e.g. .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))). The advisor uses it as the key into the ChatMemory store; without it you get no memory or a shared/global bucket.
  • Two advisors add context and the order matters — how do you control it?
    Each advisor's getOrder() sets its position (lower = earlier/outermost in the chain). Built-ins expose an order or a builder to set it; for custom advisors override getOrder().
  • How would you implement RAG with an advisor?
    Add a QuestionAnswerAdvisor (or RetrievalAugmentationAdvisor) built over a VectorStore. It embeds the user query, retrieves similar document chunks, and injects them into the prompt as context so the model grounds its answer on your documents.

saying these in an interview costs you the question

  • Thinking advisors only run after the model, not before
  • Believing chat memory works without a conversationId
  • Assuming a call() advisor automatically handles stream()
  • Confusing QuestionAnswerAdvisor (RAG) with a memory advisor

context