Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 d9a4ae22 更新于 2026-10-16

递归顾问

什么是递归顾问?

递归顾问是一种特殊类型的顾问,能够对下游顾问链循环多次。 当你需要反复调用 LLM 直到满足某个条件时,这种模式很有用,例如:

  • 循环执行工具调用,直到没有更多工具需要被调用
  • 校验结构化输出,若校验失败则重试
  • 通过修改请求来实现评估逻辑
  • 通过修改请求来实现重试逻辑

CallAdvisorChain.copy(CallAdvisor after) 方法是使能递归顾问模式的关键工具。 它创建一个只包含原链中指定顾问之后那些顾问的新顾问链, 并让递归顾问按需调用这个子链。 这种方式确保:

  • 递归顾问能循环经过链中其余的顾问
  • 链中其他顾问可以观察并拦截每一次迭代
  • 顾问链保持正确的顺序与可观测性
  • 递归顾问不会重复执行在它之前已经过的顾问

内置的递归顾问

Spring AI 内置了两个展示此模式的递归顾问。

ToolCallingAdvisor

ToolCallingAdvisor 把工具调用循环实现为顾问链的一部分,而非依赖每个 ChatModel 的内部执行。只要存在工具,它就会被 DefaultChatClient 自动注册,是 ChatClient 驱动工具增强型对话的默认机制。

要点:

  • 循环经过顾问链,直到 ToolExecutionEligibilityChecker 报告没有更多工具调用。
  • 使用 callAdvisorChain.copy(this) 为递归调用创建子链——其他顾问可以观察并拦截每一次迭代。
  • 支持 return-direct:当某个工具的结果带有 returnDirect = true 时,顾问会跳出循环并把工具结果返回给调用方,而不把它再发回 LLM。
  • 实现 ToolAdvisor 标记接口,DefaultChatClient 借此强制“单个工具顾问”的不变式——自定义子类会作为替换被透明地注册。
  • 在循环的每一次迭代间累计令牌用量,因此最终的 ChatResponse 报告所有模型调用的累计用量而非仅最后一次(参见 多步流程中的累计用量)。

简短示例:

java
var toolCallingAdvisor = ToolCallingAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

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

关于完整的构建器 API、钩子方法、配置选项、与记忆顾问的排序交互、用户控制执行模式,以及自定义子类扩展模式,参见 ToolCallingAdvisor。

关于工具循环如何融入更广泛的工具调用架构的概念性概览,参见 工具调用:工具调用循环。

关于一个利用 ToolCallingAdvisor 扩展钩子实现渐进式工具披露的具体子类,参见 工具搜索工具。

StructuredOutputValidationAdvisor

StructuredOutputValidationAdvisor 依据 JSON 模式校验结构化的 JSON 输出,若校验失败则重试调用,最多重试可配置的次数。

主要特性:

  • 从期望的输出类型派生一个 JSON 模式,或接受一个预先给定的模式字符串。
  • 依据模式校验 LLM 响应。
  • 校验失败时重试调用(默认:最多 3 次尝试)。
  • 在重试尝试时用验证错误消息增强提示词,以帮助模型自我纠正。
  • 使用 callAdvisorChain.copy(this) 为递归调用创建子链。
  • 在每次校验尝试间累计令牌用量,因此返回的 ChatResponse 报告所有重试的累计用量而非仅最后一次(参见 多步流程中的累计用量)。
  • 可选地支持自定义 JsonMapper。

该顾问可通过 outputType(自动派生模式)或 outputJsonSchema(预先给定的模式字符串)来配置;这两种选项互斥。

使用 outputType 的示例:

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

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

使用预先给定 JSON 模式的示例:

java
var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputJsonSchema(myConverter.getJsonSchema())
    .build();

或者,通过 EntityParamSpec 消费者,直接在某个 entity() 调用上启用模式校验而无需手动配置该顾问:

java
ActorFilms actorFilms = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorFilms.class, spec -> spec.validateSchema());

关于高层用法,参见 模式校验与自我纠正。

在自定义递归顾问中累计令牌用量

一个递归顾问每次调用会进行多次模型调用,因此它必须累计每次调用的令牌用量以报告正确的累计总量;否则调用方只能看到最后一次调用的用量。 Spring AI 提供了 org.springframework.ai.chat.client.advisor.UsageAccumulator 让这件事变得简单。 每次 adviseCall 调用创建一个实例(或在流式订阅中、Flux.defer 内创建,以保持订阅局部性),用 addRoundResponse(...) 累入每一轮的响应,并用 applyAccumulatedUsage(...) 把累计总量盖到最终响应上:

java
UsageAccumulator usage = new UsageAccumulator();
ChatClientResponse response;
do {
    response = callAdvisorChain.copy(this).nextCall(request);
    usage.addRoundResponse(response.chatResponse());
    // ... decide whether to loop again ...
}
while (loopAgain);
return usage.applyAccumulatedUsage(response);

底层的令牌运算位于 org.springframework.ai.support.UsageCalculator(accumulateResponseUsage 与 withUsage),UsageAccumulator 对其做了封装。

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。