Advisors
Spring AI Advisors API 提供了一种灵活且强大的方式,用于拦截、修改和增强 Spring 应用程序中由 AI 驱动的交互。借助Advisors API,开发人员能够构建更复杂、可复用且易于维护的 AI 组件.
总结: AI 请求的中间件 / 拦截器(类似 Servlet Filter、Spring AOP 环绕通知)
每次调用大模型,请求会有序穿过一条 Advisor 责任链
- 前置阶段:请求发给 LLM 之前,修改 Prompt、追加上下文、校验输入、限流、检索 RAG 数据
- 核心:调用大模型(或递归循环,例如工具调用循环)
- 后置阶段:LLM 返回结果之后,修改响应、日志记录、校验结构化输出、脱敏、统计 Token
- 支持递归循环(典型:ToolCallingAdvisor 工具调用循环)

流程:
- Spring AI 框架会根据用户的提示词创建一个聊天客户端请求,同时生成一个空的Advisors上下文对象。
- 链路中的每个Advisors都会处理该请求,并可对其进行修改。此外,Advisors也可以选择不调用下一个处理单元,以此拦截请求。在这种情况下,需要由Advisors自行组装返回结果。
- 由框架提供的最后一个Advisors会将请求发送至聊天模型。
- 聊天模型返回的响应会沿着Advisors链路反向传递,并转换为聊天客户端响应,其中包含共用的Advisors上下文实例。
- 每个Advisors都可以处理或修改响应内容。
- 最终将通过提取聊天补全结果,把聊天客户端响应返回给调用方。
核心组件

- CallAdvisor 和 CallAdvisorChain 为非流式场景
- StreamAdvisor 和 StreamAdvisorChain 为流式场景
- ChatClientRequest用于存储提示词请求, ChatClientResponse 聊天补全响应对应的聊天客户端返回结果
- advise-context在责任链之间共享状态
- adviseCall () 和 adviseStream () 是核心Advisors方法,通常执行如下操作:检查未密封的提示词数据、自定义并扩充提示词数据、调用Advisors链中的下一个实体、可选择拦截请求、检查对话补全响应,以及抛出异常以标识处理错误
- getOrder () 方法用于确定链中通知器的执行顺序,越小越先执行,而 getName () 方法提供唯一的通知器名称
Advisor Order
责任链中增强器的执行顺序由 getOrder () 方法决定:
- 优先级数值更小的增强器会优先执行。
- 增强器链以栈的形式运行:链中的第一个增强器最先处理请求。同时它也会最后处理响应。
- 如需控制执行顺序:将优先级数值设置为接近 Ordered.HIGHEST_PRECEDENCE,可保证该增强器在链中最先执行(最先处理请求,最后处理响应)。
将优先级数值设置为接近 Ordered.LOWEST_PRECEDENCE,可保证该增强器在链中最后执行(最后处理请求,最先处理响应)。- 数值越大,代表优先级越低。
- 如果多个增强器拥有相同的优先级数值,则无法保证它们的执行顺序
通知顺序与执行顺序看似矛盾,源于通知器链具备栈式特性: 优先级最高(order 值最小)的通知器会被添加至栈顶。 栈展开时,它将最先处理请求。 栈回卷时,它将最后处理响应。
接口详情
相关接口在org.springframework.ai.chat.client.advisor.api包中。
- 核心接口
java
public interface Advisor extends Ordered {
String getName();
}
- 两个子接口
java
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
java
public interface StreamAdvisor extends Advisor {
Flux<ChatClientResponse> adviseStream(
ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
- 两个责任链接口(非流式、流式)
java
public interface CallAdvisorChain extends AdvisorChain {
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
List<CallAdvisor> getCallAdvisors();
}
java
public interface StreamAdvisorChain extends AdvisorChain {
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
List<StreamAdvisor> getStreamAdvisors();
}
- 一个接口中的四个关键方法
在自定义Advisor时,根据实际情况实现adviseCall,adviseStream或者before,after两套逻辑
java
public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {
Scheduler DEFAULT_SCHEDULER = Schedulers.boundedElastic();
default ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
Assert.notNull(callAdvisorChain, "callAdvisorChain cannot be null");
ChatClientRequest processedChatClientRequest = this.before(chatClientRequest, callAdvisorChain);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(processedChatClientRequest);
return this.after(chatClientResponse, callAdvisorChain);
}
default Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
Assert.notNull(streamAdvisorChain, "streamAdvisorChain cannot be null");
Assert.notNull(this.getScheduler(), "scheduler cannot be null");
Mono var10000 = Mono.just(chatClientRequest).publishOn(this.getScheduler()).map((request) -> this.before(request, streamAdvisorChain));
Objects.requireNonNull(streamAdvisorChain);
Flux<ChatClientResponse> chatClientResponseFlux = var10000.flatMapMany(streamAdvisorChain::nextStream);
return chatClientResponseFlux.map((response) -> {
if (AdvisorUtils.onFinishReason().test(response)) {
response = this.after(response, streamAdvisorChain);
}
return response;
}).onErrorResume((error) -> Flux.error(new IllegalStateException("Stream processing failed", error)));
}
default String getName() {
return this.getClass().getSimpleName();
}
ChatClientRequest before(ChatClientRequest var1, AdvisorChain var2);
ChatClientResponse after(ChatClientResponse var1, AdvisorChain var2);
default Scheduler getScheduler() {
return DEFAULT_SCHEDULER;
}
}
实现 Advisors
要创建Advisors组件,需实现 CallAdvisor 或 StreamAdvisor(也可两者都实现)。非流式Advisors需要实现核心方法 nextCall (),流式Advisors则实现 nextStream ()。
实践案例
案例1:日志 Advisors
java
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
/** 提供一个名字*/
@Override
public String getName() {
return this.getClass().getSimpleName();
}
/** 给定执行顺序*/
@Override
public int getOrder() {
return 0;
}
/** 非流式*/
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
/** 执行前,打印请求参数*/
logRequest(chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
/** 执行后,打印相应参数*/
logResponse(chatClientResponse);
return chatClientResponse;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
StreamAdvisorChain streamAdvisorChain) {
logRequest(chatClientRequest);
Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse);
}
private void logRequest(ChatClientRequest request) {
logger.debug("request: {}", request);
}
private void logResponse(ChatClientResponse chatClientResponse) {
logger.debug("response: {}", chatClientResponse);
}
}
案例2: Re-Reading (Re2) Advisor
一种名为重读(Re2)的技术,该技术能够提升大语言模型的推理能力。Re2 技术需要按如下方式扩充输入提示词
{Input_Query}
Read the question again: {Input_Query}
java
public class ReReadingAdvisor implements BaseAdvisor {
private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
{re2_input_query}
Read the question again: {re2_input_query}
""";
private final String re2AdviseTemplate;
private int order = 0;
public ReReadingAdvisor() {
this(DEFAULT_RE2_ADVISE_TEMPLATE);
}
public ReReadingAdvisor(String re2AdviseTemplate) {
this.re2AdviseTemplate = re2AdviseTemplate;
}
/** 前置方法运用重读技术来扩充用户的输入查询*/
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
String augmentedUserText = PromptTemplate.builder()
.template(this.re2AdviseTemplate)
.variables(Map.of("re2_input_query", chatClientRequest.prompt().getUserMessage().getText()))
.build()
.render();
return chatClientRequest.mutate()
.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
.build();
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse;
}
@Override
public int getOrder() {
return this.order;
}
public ReReadingAdvisor withOrder(int order) {
this.order = order;
return this;
}
}
Spring AI 内置的 Advisors
- Chat Memory Advisors: 这些Advisors在聊天存储器中管理对话历史
- MessageChatMemoryAdvisor:检索记忆内容,并将其作为一组消息添加至提示词中。该方式可保留对话历史的结构。请注意,并非所有 AI 模型都支持此方式
- VectorStoreChatMemoryAdvisor:从向量存储中检索记忆,并将其添加至提示词的系统文本中。该Advisors模块适用于从大型数据集中高效搜索并获取相关信息。
- Question Answering Advisor: 问答相关
- QuestionAnswerAdvisor:该智能助手借助向量数据库实现问答能力,采用基础检索增强生成(Naive RAG)架构
- RetrievalAugmentationAdvisor: 基于 org.springframework.ai.rag 包中定义的基础构建模块,遵循模块化检索增强生成架构,实现通用的检索增强生成(RAG)流程
- Reasoning Advisor:推理相关
- ReReadingAdvisor: 为大语言模型推理实现一种重读策略,命名为 RE2,用于提升输入阶段的理解能力
- Tool Calling Advisor: 工具调用相关
- ToolCallingAdvisor: 作为Advisors链路的一部分处理工具调用循环。该Advisors始终由聊天客户端自动注册(除非显式禁用),因此即便未配置静态工具,也支持由其他Advisors在运行时注入的工具。它执行模型请求的工具调用并回传结果,循环执行直至不再需要工具调用。它实现了工具Advisors接口,该标记接口可避免第二个工具调用Advisors被自动注册
- Content Safety Advisor
- SafeGuardAdvisor:一种简易辅助模块,用于防止模型生成有害或不当内容
非流式 VS 流式

- 非流式:处理完整的请求与响应。
- 流式:以持续流的形式处理请求和响应,采用响应式编程理念(例如使用 Flux 处理响应)
最佳实践
- 让Advisors专注于特定任务,以实现更好的模块化。
- 必要时,使用 adviseContext 在多个Advisors之间共享状态。
- 为你的Advisors同时实现流式与非流式两种版本,以获得最大灵活性。
- 仔细考虑链路中各个Advisors的顺序,确保数据流正常流转。
相关API变更
Advisor Interfaces
- 在 1.0 M2 版本中,存在相互独立的 RequestAdvisor 和 ResponseAdvisor 接口。
- RequestAdvisor 在 ChatModel.call 和 ChatModel.stream 方法执行前调用。
- ResponseAdvisor 在上述方法执行后调用。
- 在 1.0 M3 版本中,这两组接口被替换为:CallAroundAdvisor、StreamAroundAdvisor 原本属于 ResponseAdvisor 的 StreamResponseMode 已被移除。
- 在 1.0.0 正式版中,接口再次更名替换:
- CallAroundAdvisor → CallAdvisor
- StreamAroundAdvisor → StreamAdvisor
- CallAroundAdvisorChain → CallAdvisorChain
- StreamAroundAdvisorChain → StreamAdvisorChain
同时模型类更名: - AdvisedRequest → ChatClientRequest
- AdvisedResponse → ChatClientResponse
上下文 Map 处理
- 1.0 M2:
上下文 Map 是独立的方法入参;Map 可变,在调用链中传递。 - 1.0 M3:
上下文 Map 并入 AdvisedRequest / AdvisedResponse 记录类中;Map 不可变。
如需更新上下文,请使用 updateContext 方法,该方法会生成一张全新的不可变 Map 承载更新后数据。
Recursive Advisors(递归Advisors)
什么是Recursive Advisors
递归Advisors是一类特殊的Advisors,能够多次循环执行下游的Advisor调用链.
当你需要反复调用大模型,直到满足特定条件时,该模式非常适用,典型场景举例:
- 循环执行工具调用,直至不再需要调用任何工具
- 校验结构化输出,校验失败则自动重试
- 实现评估逻辑,并按需修改请求内容
- 实现可修改请求参数的重试逻辑
CallAdvisorChain.copy(CallAdvisor after) 工具方法是实现递归Advisors模式的核心。
它会生成一条全新的Advisors子链:仅包含原调用链中指定 advisor 之后的所有 advisor。递归Advisors可以按需调用这条子链。
该机制保障了以下特性:
- 递归Advisors可以循环执行调用链中剩余的下游Advisors
- 调用链里其他Advisors能够感知、拦截每一轮循环迭代
- Advisors调用链保持正确执行顺序,具备完整可观测性
- 递归Advisors不会重复执行排在它自身前面的Advisors
案例场景:递归校验 LLM 返回结构化 JSON,格式错误就自动重试
java
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Mono;
public class JsonValidationRecursiveAdvisor implements CallAdvisor {
private static final String RETRY_COUNT_KEY = "retryCount";
private final int maxRetry;
public JsonValidationRecursiveAdvisor(int maxRetry) {
this.maxRetry = maxRetry;
}
@Override
public Mono<ChatResponse> advise(Prompt prompt, CallAdvisorChain chain) {
// 1. 获取当前重试次数,存入 context
int currentRetry = (int) prompt.context().getOrDefault(RETRY_COUNT_KEY, 0);
// 超过最大重试,直接返回,不再递归
if (currentRetry >= maxRetry) {
return chain.next(prompt);
}
// 2. 调用下游子链(chain.copy(this) 截取本advisor之后所有advisor)
CallAdvisorChain subChain = chain.copy(this);
return subChain.next(prompt)
.flatMap(response -> {
String content = response.getResult().getOutput().getText();
boolean valid = isJsonValid(content);
if (valid) {
// JSON合法,终止递归,直接返回结果
return Mono.just(response);
} else {
// JSON非法:重试,更新上下文重试次数
Prompt newPrompt = prompt.updateContext(ctx -> {
ctx.put(RETRY_COUNT_KEY, currentRetry + 1);
return ctx;
});
// ✅ 递归调用自身advise方法,继续走子链
return this.advise(newPrompt, chain);
}
});
}
// 简单JSON校验(生产建议用Jackson完整校验schema)
private boolean isJsonValid(String text) {
text = text.trim();
return (text.startsWith("{") && text.endsWith("}"))
|| (text.startsWith("[") && text.endsWith("]"));
}
@Override
public int getOrder() {
// 执行顺序,按需调整
return 0;
}
}
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new JsonValidationRecursiveAdvisor(3)) // 最多重试3次
.build();
String resp = chatClient.prompt()
.user("返回标准JSON:{name: '张三', age: 20},不要多余文字")
.call()
.content();
Spring AI 内置Recursive Advisors
ToolCallingAdvisor
ToolCallingAdvisor将工具调用循环作为Advisor链的一部分执行,而非依赖模型内部的工具执行。这使得链中的其他Advisor可以拦截并观测工具调用流程。主要特点:
- 循环执行Advisor链,直至工具执行资格检查器判定不再需要调用工具
- 支持 "直接返回" 功能:当工具执行配置 returnDirect=true 时,将终止工具调用循环,并把工具执行结果直接返回给客户端应用,而不是传回大语言模型
- 通过 callAdvisorChain.copy (this) 创建子链用于递归调用
- 可通过 conversationHistoryEnabled 配置是否启用会话历史管理
- 支持可插拔的工具执行资格检查器,用于自定义循环继续执行的判定条件
用法:
java
var toolCallingAdvisor = ToolCallingAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(toolCallingAdvisor)
.build();
历史对话管理
- ToolCallingAdvisor包含一个名为 conversationHistoryEnabled 的配置项,用于控制在多次工具调用过程中对话历史的管理方式
- 默认配置下(conversationHistoryEnabled=true),该Advisor会在多轮工具调用期间在内部保存完整对话历史。也就是说,工具调用循环中后续每一次大模型调用都会携带全部历史消息(用户消息、助手回复、工具返回结果)。
- 默认情况下,memory advisor(DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER = 最高优先级 + 200)运行在工具调用循环之外。ToolCallingAdvisor(优先级为 最高优先级 + 300)在多轮调用内部管理对话历史。memory advisor仅在循环开始前加载一次历史记录,并在循环结束后持久保存最终的用户与助手对话内容。该方案为推荐配置,因为绝大多数聊天记忆仓库实现并不支持工具调用类消息。
仅当在工具调用循环内部放置memory advisor(顺序高于 ToolCallingAdvisor.DEFAULT_ORDER)时,才可使用.disableInternalConversationHistory () 方法。此时内memory advisor会在每一轮迭代中处理对话历史。请注意:只有 InMemoryChatMemoryRepository 支持持久化工具调用消息;其他存储库应采用上述默认的循环外配置方式。
java
var toolCallingAdvisor = ToolCallingAdvisor.builder()
.toolCallingManager(toolCallingManager)
.disableInternalConversationHistory() // Memory advisor inside the loop handles history
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 400) // Inside (after) ToolCallingAdvisor
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(chatMemoryAdvisor, toolCallingAdvisor)
.build();
User-Controlled Tool Execution(自定义ToolCallingAdvisors)
默认情况下,ToolCallingAdvisor会自动注册,并在内部管理完整的工具调用流程 ------ 调用方仅能获取大模型返回的最终回答。如果你需要完全自主控制调用流程(例如,向界面流式输出中间执行进度、接入自定义可观测能力,或是在多次迭代之间增加条件判断逻辑),可以不使用自动注册的Advisor,自行驱动整个调用流程。
可通过 AdvisorParams.toolCallingAdvisorAutoRegister (false) 在单次调用维度关闭自动注册功能。
java
/** 创建工具执行管理器 ToolCallingManager:负责解析 LLM 返回的工具调用、反射执行 Java 工具方法(这里是天气查询工具 WeatherTools)*/
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
/** 注册可用工具:WeatherTools 里面定义了查询天气的函数(@Tool 注解)*/
ToolCallback[] tools = ToolCallbacks.from(new WeatherTools());
/** 配置对话参数:告诉大模型,当前会话可以调用上面注册的天气工具*/
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
/** 问题*/
String question = "What is the weather in Amsterdam and Paris?";
/** 禁用 Spring AI 内置自动递归 ToolCallingAdvisor
* 如果不关闭,Advisor 内部会自动完成全部多轮工具循环,直接返回最终回答,开发者拿不到每一轮中间工具调用记录。
* 执行第一轮对话:把用户问题发给 LLM,LLM 会返回:需要调用天气工具(toolCalls),不会直接给出自然语言答案。
*/
ChatClientResponse response = chatClient.prompt()
.user(question)
.options(chatOptions)
.advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
.call()
.chatClientResponse();
/** 初始化 prompt,承载对话历史*/
Prompt prompt = new Prompt(List.of(new UserMessage(question)), chatOptions);
/** 手动循环:只要返回里包含工具调用,就继续迭代*/
while (response.chatResponse() != null && response.chatResponse().hasToolCalls()) {
/**1. 执行LLM返回的所有工具函数(调用WeatherTools获取天气数据)*/
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response.chatResponse());
/** 2. 更新完整对话上下文:原始问答 + LLM工具调用消息 + 工具返回结果*/
prompt = new Prompt(result.conversationHistory(), chatOptions);
/** 3.把完整对话再次发给LLM,让LLM结合工具返回结果继续处理*/
response = chatClient.prompt()
.messages(result.conversationHistory())
.options(chatOptions)
.advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
.call()
.chatClientResponse();
}
观测工具调用循环(Observing the Tool-Calling Loop)
除手动驱动循环外,你还可以将自定义Advisor置入ToolCallingAdvisor循环中,只需为其设置大于 ToolCallingAdvisor.DEFAULT_ORDER 的优先级数值(例如 HIGHEST_PRECEDENCE + 400)。这类Advisor会在工具调用循环的每一次迭代中执行,而非仅在循环结束时运行一次,因此能够获取所有中间消息:
在流式模式下:可以拿到每一轮模型返回的原始分片流,包含工具调用请求分片;这些分片后续会被 ToolCallingAdvisor 从对外输出流中过滤掉,但观测 Advisor 依然能提前捕获。
在非流式下:每一轮后续请求携带的对话历史,都包含上一轮产生的工具响应消息(ToolResponseMessage),Advisor 既能观测到【LLM 发起的工具调用请求】,也能观测【工具执行后的返回结果】。
借助该模式,你可以将中间分块转发至旁路通道(服务器推送事件、WebSocket、日志),且不会干扰工具调用循环的正常执行。
java
public class ToolCallObservingAdvisor implements CallAdvisor, StreamAdvisor {
private final Consumer<ChatClientResponse> observer;
public ToolCallObservingAdvisor(Consumer<ChatClientResponse> observer) {
this.observer = observer;
}
@Override
public ChatClientResponse aroundCall(ChatClientRequest request, CallAdvisorChain chain) {
// 每一轮迭代都可以查看全部消息,后续请求会带上上一轮工具返回消息
request.prompt().getInstructions().forEach(msg -> log.debug("Message: {}", msg));
ChatClientResponse response = chain.nextCall(request);
observer.accept(response);
return response;
}
@Override
public Flux<ChatClientResponse> aroundStream(ChatClientRequest request, StreamAdvisorChain chain) {
// 观测所有分片,包含后续会被ToolCallingAdvisor屏蔽的工具调用分片
return chain.nextStream(request).doOnNext(observer);
}
@Override
public int getOrder() {
// 嵌入 ToolCallingAdvisor(order=HIGHEST_PRECEDENCE + 300)循环内部
return Ordered.HIGHEST_PRECEDENCE + 400;
}
}
注册观测 Advisor,和框架自动注册的 ToolCallingAdvisor 一起生效:
java
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new ToolCallObservingAdvisor(chunk -> forwardToSse(chunk)))
.build();
String response = chatClient.prompt()
.user("What is the weather in Amsterdam and Paris?")
.tools(new WeatherTools())
.call()
.content();
因为 ToolCallObservingAdvisor 的 order = HIGHEST_PRECEDENCE + 400,排在自动注册的 ToolCallingAdvisor(order = HIGHEST_PRECEDENCE + 300)之后,因此会参与每一轮工具调用迭代。
重要特性:ToolCallingAdvisor 依然会把工具调用相关分片从对外返回的主链路中过滤。调用方最终只能拿到完整的最终回答;所有中间工具调用分片、中间消息,由观测 Advisor 在旁路单独发送。
这套方案的优势:工具多轮循环依然交给 Spring AI 框架全权管理,同时开发者可以完整看到每一步中间过程。该方案可以和会话历史持久化、MemoryAdvisor 搭配使用。
直接返回功能(Return Direct Functionality)
"直接返回(return direct)" 特性允许工具绕过大模型,将执行结果直接返回给客户端应用。适用场景:
- 工具输出就是最终答案,不再需要大模型后续加工处理
- 希望减少耗时,省去额外一轮 LLM 调用
- 工具结果原样返回,不需要大模型做解读、润色
工具执行结果中 returnDirect=true 时,ToolCallingAdvisor 会执行以下逻辑:
- 照常执行工具调用
- 识别 ToolExecutionResult 里的 returnDirect 标记
- 终止工具多轮调用循环
- 把工具执行结果封装为 ChatResponse,工具输出作为生成内容,直接返回给客户端
StructuredOutputValidationAdvisor
StructuredOutputValidationAdvisor 用于依据 JSON Schema 校验大模型返回的结构化 JSON 输出;如果校验失败,会自动重试调用,最多可配置指定重试次数.
- 核心特点:
- 可根据预期返回的 Java 类型自动推导 JSON Schema,也支持直接传入预先定义好的 Schema 字符串
- 使用 Schema 校验 LLM 返回结果
- 校验失败自动重试,最大重试次数可配置(默认 3 次)
- 重试时会在校验提示中追加校验错误信息,辅助大模型修正输出格式
- 内部使用 callAdvisorChain.copy(this) 生成子调用链,实现递归重试(Recursive Advisor 机制)
- 可选注入自定义 JsonMapper 处理 JSON 序列化 / 反序列化
配置二选一,不能同时使用:
- outputType:传入 Java Class,自动推导 Schema
- outputJsonSchema:直接提供手写 JSON Schema 字符串
案例1 基于 Java 类型自动生成 Schema
java
var validationAdvisor = StructuredOutputValidationAdvisor.builder()
.outputType(MyResponseType.class)
.maxRepeatAttempts(3)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(validationAdvisor)
.build();
案例2 传入预先定义的 JSON Schema
java
var validationAdvisor = StructuredOutputValidationAdvisor.builder()
.outputJsonSchema(myConverter.getJsonSchema())
.build();
案例3 快捷方式:不用手动注册 Advisor,在 entity () 直接开启校验
java
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec.schemaValidation());