Lzh on GitHub

什么是递归式 Advisor?

递归式 Advisor 是一种特殊类型的 Advisor,它可以多次循环调用下游的 Advisor 链。这种模式在需要不断调用 LLM 直到满足某个条件时非常有用,例如:

  • 反复执行工具调用(tool calls),直到不再需要继续调用工具
  • 验证结构化输出,如果验证失败则重试
  • 在请求需要调整时实现 “评估(Evaluation)” 逻辑
  • 在请求需要修改时实现重试逻辑

实现递归式 Advisor 的关键是 CallAdvisorChain.copy(CallAdvisor after) 方法。 该方法会基于原始链创建一个新的 Advisor 链,其中只包含指定 Advisor 之后的所有 Advisor,从而让递归式 Advisor 可以根据需要多次调用这个“子链”。

这种方式保证了:

  • 递归式 Advisor 可以循环处理链中剩余的 Advisor
  • 链中的其他 Advisor 仍然能够观察并拦截每一次循环
  • Advisor 链的顺序性与可观察性被完整保留
  • 递归式 Advisor 不会再次执行位于它之前的 Advisor

内置的递归式 Advisor

Spring AI 提供了两个内置的递归式 Advisor,用来示范这种模式:

ToolCallAdvisor

ToolCallAdvisor 实现了在 Advisor 链中循环调用工具的机制,而不是依赖模型内部的工具执行。这使得链中的其他 Advisor 能够拦截并观察工具调用的过程。

主要特性:

  • 通过设置 setInternalToolExecutionEnabled(false) 禁用模型内部的工具执行。
  • 循环遍历 Advisor 链,直到没有更多工具调用为止。
  • 支持 “return direct” 功能——当工具执行的 returnDirect=true 时,它会中断工具调用循环,并将工具执行结果直接返回给客户端应用,而不是再次发送给 LLM。
  • 使用 callAdvisorChain.copy(this) 创建递归调用的子链。
  • 包含空安全检查,以处理聊天响应可能为 null 的情况。

示例用法:

var toolCallAdvisor = ToolCallAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

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

Return Direct 功能

“return direct” 功能允许工具绕过 LLM,直接将结果返回给客户端应用。适用场景包括:

  • 工具的输出就是最终答案,无需 LLM 处理。
  • 希望通过避免额外的 LLM 调用来降低延迟。
  • 工具结果应原样返回,无需解释。

当工具执行的 returnDirect=true 时,ToolCallAdvisor 会:

  1. 按正常流程执行工具调用。
  2. 检测 ToolExecutionResult 中的 returnDirect 标志。
  3. 中断工具调用循环。
  4. 将工具执行结果直接作为 ChatResponse 返回给客户端应用,生成内容为工具输出。

StructuredOutputValidationAdvisor

StructuredOutputValidationAdvisor 会根据生成的 JSON schema 验证结构化 JSON 输出,如果验证失败,则会重试调用,最多达到指定的尝试次数。

主要特性:

  • 根据期望的输出类型自动生成 JSON schema
  • 验证 LLM 的响应是否符合 schema
  • 如果验证失败,会重试调用,最多重试次数可配置
  • 在重试时,将验证错误信息附加到 prompt 中,帮助 LLM 修正输出
  • 使用 callAdvisorChain.copy(this) 创建递归调用的子链
  • 可选地支持自定义 ObjectMapper 进行 JSON 处理

示例用法:

var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputType(MyResponseType.class)
    .maxRepeatAttempts(3)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 1000)
    .build();

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