使用 Spring AI 递归顾问创建自我改进的 AI 代理

工程 | Christian Tzolov | 2025 年 11 月 4 日 | ...

Spring AI ChatClient 提供了一个流畅的 API,用于与 AI 模型进行通信。流畅的 API 提供了构建提示的组成部分的方法,这些组成部分作为输入传递给 AI 模型。

顾问是流畅 API 的关键组成部分,它们拦截、修改和增强 AI 驱动的交互。其主要优点包括封装常见的生成式 AI 模式、转换发送到和来自大型语言模型 (LLM) 的数据,以及为各种模型和用例提供可移植性。

顾问会处理 ChatClientRequestChatClientResponse 对象。框架会根据顾问的 getOrder() 值(值越低越先执行)将顾问串联起来,最后一个顾问会调用 LLM。

Spring AI 提供内置顾问,用于对话记忆检索增强生成 (RAG)、日志记录和护栏。开发人员还可以创建自定义顾问。

典型顾问结构

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);
    }
}

递归顾问

从版本 1.1.0-M4 开始,Spring AI 引入了递归顾问,支持多次循环执行顾问链,以支持迭代工作流

  • 工具调用循环:按顺序执行多个工具,每个工具的输出都会为下一个决策提供信息
  • 输出验证:验证结构化响应,并在验证失败时通过反馈重试
  • 重试逻辑:根据响应质量或外部标准优化请求
  • 评估流程:在交付前评估和修改响应
  • 智能体循环:构建自主智能体,通过分析结果并确定下一步行动来规划、执行和迭代任务,直到目标实现

传统的单次执行顾问模式无法充分处理这些场景。

递归顾问会多次循环执行下游顾问链,重复调用 LLM,直到满足某个条件。

CallAdvisorChain.copy(CallAdvisor after) 方法会创建一个只包含下游顾问的子链,从而在保持适当的顺序和可观测性的同时实现受控迭代,并防止上游顾问重复执行。

该图说明了递归顾问如何通过允许流程多次循环返回顾问链来启用迭代处理。与传统的单次执行不同,递归顾问可以根据响应条件重新访问先前的顾问,从而在客户端、顾问和 LLM 之间创建复杂的迭代工作流。

以下是实现递归顾问的基本模式

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;
    }
}

与非递归顾问的关键区别在于使用 chain.copy(this).nextCall(...) 而不是 chain.nextCall(...) 来迭代内部链的副本。这确保了每次迭代都通过完整的下游链,允许其他顾问观察和拦截,同时保持适当的可观测性。

⚠️ 重要提示

递归顾问是 Spring AI 1.1.0-M4 中一个新的实验性功能。它们仅支持非流式传输,需要仔细的顾问排序,并且由于多次 LLM 调用而可能增加成本。

对于维护外部状态的内部顾问要特别小心——它们可能需要额外关注以在迭代过程中保持正确性。

始终设置终止条件和重试限制,以防止无限循环。

考虑您的用例是更受益于递归顾问,还是更受益于在您的应用程序代码中围绕 ChatClient 调用实现显式 while 循环。

内置递归顾问

Spring AI 1.1.0-M4 提供两个递归顾问

ToolCallAdvisor

默认的工具调用支持

默认情况下,Spring AI 工具执行在每个 ChatModel 实现内部使用 ToolCallingManager 实现。这意味着工具调用请求和响应流程对于 ChatClient 顾问是不透明的,因为它发生在顾问执行链之外。

ToolCallAdvisor

ToolCallAdvisor 利用用户控制的工具执行,在顾问链中实现工具调用循环,提供对工具执行的显式控制,而不是委托给模型的内部工具执行。

  • 循环直到不再有工具调用为止
  • 使其他顾问(例如 Advisor ABC)能够拦截和更改每个工具调用请求和响应
  • 支持“直接返回”功能
  • 禁用聊天模型的内部工具执行

示例用法

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();

注意:默认情况下,ToolCallAdvisor 的顺序设置为接近 Ordered.HIGHEST_PRECEDENCE,以确保顾问在链中首先执行(请求处理时首先执行,响应处理时最后执行),从而允许内部顾问拦截和处理工具请求和响应消息。

当工具执行具有 returnDirect=true 时,顾问会执行该工具,检测到该标志,中断循环,并直接将输出返回给客户端,而无需将其发送给 LLM。当工具的输出是最终答案时,这可以减少延迟。

💡 演示项目:请参阅 递归顾问演示项目中的 ToolCallAdvisor 的完整工作示例。

StructuredOutputValidationAdvisor

StructuredOutputValidationAdvisor 会根据生成的模式验证结构化 JSON 输出,并在验证失败时重试

  • 从预期的输出类型自动生成 JSON 模式
  • 根据模式验证 LLM 响应,并在出现验证错误消息时重试
  • 可配置的最大重试次数
  • 支持用于 JSON 处理的自定义 ObjectMapper

示例用法

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);

当验证失败时,顾问会使用错误详细信息增强提示,并重试,直到达到配置的最大尝试次数。

注意:默认情况下,StructuredOutputValidationAdvisor 的顺序设置为接近 Ordered.LOWEST_PRECEDENCE,以确保顾问在链的末尾执行(但在模型调用之前),这意味着它在请求处理时最后执行,在响应处理时首先执行。

最佳实践

  • 设置明确的终止条件:确保循环具有明确的退出条件,以防止无限循环。
  • 使用适当的顺序:将递归顾问放置在链的早期,以允许其他顾问观察迭代;或放置在后期,以防止观察。
  • 提供反馈:在重试请求中添加有关重试原因的信息,以帮助 LLM 改进。
  • 限制迭代次数:设置最大尝试次数限制,以防止失控执行。
  • 监控执行:使用 Spring AI 的可观测性功能来跟踪迭代次数和性能。
  • 选择正确的方法:评估递归顾问或围绕 ChatClient 调用显式 while 循环是否更适合您的特定用例和架构。

性能考量

递归顾问会增加 LLM 调用的次数,从而影响:

  • 成本:更多的 API 调用会增加成本
  • 延迟:多次迭代会增加处理时间
  • 令牌使用量:每次迭代都会消耗额外的令牌

为了优化,请设置合理的重试限制并监控迭代次数。尽可能缓存中间结果。

资源

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有