主题
递归顾问
什么是递归顾问?
递归顾问是一种特殊类型的顾问,能够对下游顾问链循环多次。 当你需要反复调用 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 对其做了封装。