Create Self-Improving AI Agents Using Spring AI Recursive Advisors

Engineering | Christian Tzolov | November 04, 2025 | ...

The Spring AI ChatClient offers a fluent API for communicating with an AI model. The fluent API provides methods for building the constituent parts of a prompt that gets passed to the AI model as input.

Advisors are a key part of the fluent API that intercept, modify, and enhance AI-driven interactions. The key benefits include encapsulating common Generative AI patterns, transforming data sent to and from Large Language Models (LLMs), and providing portability across various models and use cases.

Advisors process ChatClientRequest and ChatClientResponse objects. The framework chains advisors by their getOrder() values (lower values execute first), with the final advisor calling the LLM.

Spring AI provides built-in advisors for Conversation Memory, Retrieval Augmented Generation (RAG), Logging, and Guardrails. Developers can also create custom advisors.

Typical advisor structure:

public class MyAdvisor implements CallAdvisor {
    
    // 1. Sets the advisor orders
    public int getOrder() { return MY_ADVISOR_ORDER; } 

    // 2. Sets the advisor name
    public String getName() { return MY_ADVISOR_NAME; } 

    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 3. Pre-process the request (modify, validate, log, etc.)
        request = preProcess(request);
        // 4. Call the next advisor in the chain
        ChatClientResponse response = chain.nextCall(request);
        // 5. Post-process the response (modify, validate, log, etc.)
        return postProcess(response);
    }
}

Recursive Advisors

Starting with version 1.1.0-M4, Spring AI introduces Recursive Advisors, enabling looping through the advisor chain multiple times to support iterative workflows:

  • Tool calling loops: Executing multiple tools in sequence, where each tool's output informs the next decision
  • Output validation: Validating structured responses and retrying with feedback when validation fails
  • Retry logic: Refining requests based on response quality or external criteria
  • Evaluation flows: Evaluating and modifying responses before delivery
  • Agentic loops: Building autonomous agents that plan, execute, and iterate on tasks by analyzing results and determining next actions until a goal is achieved

Traditional single-pass advisor patterns cannot adequately handle these scenarios.

Recursive advisors loop through the downstream advisor chain multiple times, repeatedly calling the LLM until a condition is met.

The CallAdvisorChain.copy(CallAdvisor after) method creates a sub-chain containing only downstream advisors, enabling controlled iteration while maintaining proper ordering and observability, and preventing re-execution of upstream advisors.

The diagram illustrates how recursive advisors enable iterative processing by allowing the flow to loop back through the advisor chain multiple times. Unlike traditional single-pass execution, recursive advisors can revisit previous advisors based on response conditions, creating sophisticated iterative workflows between the client, advisors, and LLM.

Here's the basic pattern for implementing a recursive advisor:

public class MyRecursiveAdvisor implements CallAdvisor {
    
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        
        // Call the chain initially
        ChatClientResponse response = chain.nextCall(request);
        
        // Check if we need to retry
        while (!isConditionMet(response)) {
            // Modify the request based on the response
            ChatClientRequest modifiedRequest = modifyRequest(request, response);
            
            // Create a sub-chain and recurse
            response = chain.copy(this).nextCall(modifiedRequest);
        }
        
        return response;
    }
}

The key difference from non-recursive advisors is using chain.copy(this).nextCall(...) instead of chain.nextCall(...) to iterate on a copy of the inner chain. This ensures each iteration goes through the complete downstream chain, allowing other advisors to observe and intercept while maintaining proper observability.

⚠️ Important Note

Recursive Advisors are a new experimental feature in Spring AI 1.1.0-M4. They are non-streaming only, requires careful advisor ordering, can increase costs due to multiple LLM calls.

Be especially careful with inner advisors that maintain external state - they may require extra attention to maintain correctness across iterations.

Always set termination conditions and retry limits to prevent infinite loops.

Consider whether your use case benefits more from recursive advisors or from implementing an explicit while loop around ChatClient calls in your application code.

Built-in Recursive Advisors

Spring AI 1.1.0-M4 provides two recursive advisors:

ToolCallAdvisor

Default ToolCalling support

By default, the Spring AI Tool Execution is implemented inside each ChatModel implementation using a ToolCallingManager. This means that the tool calling requests and response flow is opaque for the ChatClient Advisors as it happens outside the advisor execution chain.

ToolCallAdvisor

Leveraging the User-Controlled tool execution, the ToolCallAdvisor implements the tool calling loop within the advisor chain, providing explicit control over tool execution rather than delegating to the model's internal tool execution

  • Loops until no more tool calls are present
  • Enables other advisors (such as Advisor ABC) to intercept and alter each tool call request and response
  • Supports "return direct" functionality
  • Disables the chat-model's internal tool execution

Example Usage:

var toolCallAdvisor = ToolCallAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();
        
public record Request(String location) {}

var weatherTool = FunctionToolCallback.builder("getWeather", (Request request) -> "15.0°C")
        .description("Gets the weather for a location")
        .inputType(Request.class)
        .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultToolCallbacks(weatherTool) // Tools registration
    .defaultAdvisors(toolCallAdvisor) // Tool Call Execution as Advisor
    .build();

String response = chatClient.prompt()
    .user("What's the weather in Paris and Amsterdam and convert the temperature to Fahrenheit?")
    .call()
    .content();

Note: By default, the ToolCallAdvisor's order is set close to Ordered.HIGHEST_PRECEDENCE to ensure the advisor executes first in the chain (first for request processing, last for response processing), allowing inner advisors to intercept and process tool request and response messages.

When a tool execution has returnDirect=true, the advisor executes the tool, detects the flag, breaks the loop, and returns the output directly to the client without sending it to the LLM. This reduces latency when the tool's output is the final answer.

💡 Demo Project: See a complete working example of the ToolCallAdvisor in the Recursive Advisor Demo project.

StructuredOutputValidationAdvisor

The StructuredOutputValidationAdvisor validates structured JSON output against a generated schema and retries if validation fails:

  • Automatically generates JSON schemas from expected output types
  • Validates LLM responses againsts the schema and retries with validation error messages
  • Configurable maximum retry attempts
  • Supports custom ObjectMapper for JSON processing

Example Usage:

public record ActorFilms(String actor, List<String> movies) {}

var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputType(ActorFilms.class)
    .maxRepeatAttempts(3)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(validationAdvisor)
    .build();

ActorFilms actorFilms = chatClient.prompt()
    .user("Generate the filmography for Tom Hanks")
    .call()
    .entity(ActorFilms.class);

When validation fails, the advisor augments the prompt with error details and retries up to the configured maximum attempts.

Note: By default, the StructuredOutputValidationAdvisor's order is set close to Ordered.LOWEST_PRECEDENCE to ensure the advisor executes toward the end of the chain (but before the model call), meaning it's last for request processing and first for response processing.

Best Practices

  • Set Clear Termination Conditions: Ensure loops have definite exit conditions to prevent infinite loops
  • Use Appropriate Ordering: Place recursive advisors early in the chain to allow other advisors to observe iterations, or late to prevent observation
  • Provide Feedback: Augment retry requests with information about why the retry is needed to help the LLM improve
  • Limit Iterations: Set maximum attempt limits to prevent runaway execution
  • Monitor Execution: Use Spring AI's observability features to track iteration counts and performance
  • Choose the Right Approach: Evaluate whether recursive advisors or explicit while loops around ChatClient calls better suit your specific use case and architecture

Performance Considerations

Recursive advisors increase the number of LLM calls, affecting:

  • Cost: More API calls increase costs
  • Latency: Multiple iterations add processing time
  • Token Usage: Each iteration consumes additional tokens

To optimize set reasonable retry limits and monitor iteration counts. Cache intermediate results when possible

Resources

Get the Spring newsletter

Stay connected with the Spring newsletter

Subscribe

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all