Spring AI实战:SpringBoot项目结合Spring AI开发——增强器Advisor详解与实战
在前面Spring AI的核心知识介绍时,有提到过结构化输出器和聊天记忆都用到了Advisor组件,但是前面基本都是一笔带过,今天就来系统的学习一下Advisor的相关知识。Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。这里我们来自己实现一个增强类。

🪁🍁 希望本文能给您带来帮助,如果有任何问题,欢迎批评指正!🐅🐾🍁🐥
文章目录
- 一、前言
- 二、Advisor简介
- 三、Advisor源码及核心原理
-
- 3.1 Advisor
-
- 3.1.1 CallAdvisor
- 3.1.2 StreamAdvisor
- 3.1.3 BaseAdvisor
- 3.1.4 BaseChatMemoryAdvisor
- 3.1.5 MessageChatMemoryAdvisor
- 3.1.6 PromptChatMemoryAdvisor
- 3.1.7 ChatModelCallAdvisor
- 3.1.8 ChatModelStreamAdvisor
- 3.1.9 SafeGuardAdvisor
- 3.1.10 SimpleLoggerAdvisor
- 3.1.11 StructuredOutputValidationAdvisor
- 3.1.12 ToolCallAdvisor
- 3.2 AdvisorChain
- 四、Spring AI中Advisor实战使用
- 五、总结
- 六、参考资料
导航参见:
Spring AI实战:SpringBoot项目结合Spring AI开发——ChatClient API详解
Spring AI实战:SpringBoot项目结合Spring AI开发——提示词(Prompt)技术与工程实战详解
Spring AI实战:SpringBoot项目结合Spring AI开发——模型参数及ChatOptions API详解
Spring AI实战:SpringBoot项目结合Spring AI开发——结构化输出(StructuredOutputConverter)
Spring AI实战:SpringBoot项目结合Spring AI开发——聊天记忆(ChatMemory)源码及实战详解
Spring AI实战:SpringBoot项目结合Spring AI开发——增强器Advisor详解与实战
一、前言
在前面Spring AI的核心知识介绍时,有提到过结构化输出器和聊天记忆都用到了Advisor组件,但是前面基本都是一笔带过,今天就来系统的学习一下Advisor的相关知识。
二、Advisor简介
Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。
2.1 Advisor核心概念
Advisor 是 Spring AI 中负责动态干预聊天请求和响应流程的组件,通过链式结构(Chain of Responsibility 模式)串联多个处理单元。每个 Advisor 可以修改请求参数、增强数据、拦截敏感操作,甚至中断请求传递。Advisor 功能类似于 Spring AOP(面向切面编程)中的切面(Aspect)概念,但更专注于 AI 交互场景。例如,通过 BaseAdvisor 接口实现请求前后的增强逻辑。
为了帮助理解这一流程,下面我们先来看一下官方提供的流程图。这张图详细展示了各个Advisor如何在请求链中进行交互,以及它们如何协同工作以增强整体功能。

2.2 Advisor核心特点
- 模块化封装
- 重复任务封装:将生成式 AI 的通用模式(如上下文记忆、敏感词过滤)抽象为可复用的组件。
- 数据转换:优化发送至语言模型(LLM)的输入数据格式,并处理返回的响应(如结构化输出转换)。
- 可移植性:同一 Advisor 可适配不同模型(如 OpenAI、HuggingFace)和用例,提升代码灵活性。
- 链式处理机制
多个 Advisor 按顺序执行,每个环节可修改请求或响应,并决定是否继续传递。某些 Advisor(如 SafeGuardAdvisor)可能直接中断链式流程。 - 内置与扩展性
Spring AI 提供多种内置 Advisor,同时也支持开发者自定义扩展,满足个性化需求。
三、Advisor源码及核心原理
3.1 Advisor
Advisor是一个基础接口,定义 getName() 方法标识 Advisor 名称,其还继承了Spring 的Ordered 接口,用于控制 Advisor 的执行顺序。
public interface Advisor extends Ordered {
int DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER = -2147482648;
String getName();
}
小order先管请求、后管响应,大order反之;同值顺序随机。
3.1.1 CallAdvisor
CallAdvisor是同步调用增强器,通过 adviseCall() 方法拦截请求并返回响应。
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
3.1.2 StreamAdvisor
StreamAdvisor是流式调用增强器,通过 adviseStream() 方法处理流式响应(返回 Flux)
public interface StreamAdvisor extends Advisor {
Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
3.1.3 BaseAdvisor
BaseAdvisor定义了聊天客户端调用的建议器(Advisor)基础结构。
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);
// 调用责任链的下一个节点 nextCall()
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 chatClientRequest, AdvisorChain advisorChain);
// 后置处理,留给实现类去实现
ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain);
default Scheduler getScheduler() {
return DEFAULT_SCHEDULER;
}
}
设计特点
- 责任链模式
- 通过 AdvisorChain 实现多个 Advisor 的链式调用,支持动态扩展增强逻辑
- 模板方法模式
- adviseCall方法提供了标准化的处理流程,子类只需实现 before() 和 after() 的具体逻辑
- 开闭原则
- adviseCall方法对修改封闭,算法骨架固定, before() 和 after() 方法对扩展开放,具体处理逻辑可扩展
- 同步与流式分离
- CallAdvisor专注同步阻塞调用,StreamAdvisor专注异步流式调用,BaseAdvisor聚合两种能力,提供统一入口
- 空安全
- 使用Spring的Assert进行参数校验
3.1.4 BaseChatMemoryAdvisor
BaseChatMemoryAdvisor 是一个专门为对话记忆场景设计的 Advisor 基础接口,主要职责是管理对话的上下文记忆,确保 AI 模型能够"记住"之前的对话内容。它相比BaseAdvisor 提供了getConversationId 方法,优先使用上下文中的 ID,如果上下文中没有传递,则使用默认的默认 ID。
public interface BaseChatMemoryAdvisor extends BaseAdvisor {
default String getConversationId(Map<String, Object> context, String defaultConversationId) {
Assert.notNull(context, "context cannot be null");
Assert.noNullElements(context.keySet().toArray(), "context cannot contain null keys");
Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty");
return context.containsKey("chat_memory_conversation_id") ? context.get("chat_memory_conversation_id").toString() : defaultConversationId;
}
}
3.1.5 MessageChatMemoryAdvisor
MessageChatMemoryAdvisor负责维护对话的上下文记忆,将用户的问题与模型的回答保存到内存中,确保多轮对话的连贯性。例如,在连续对话中,历史记录会被自动附加到新请求中,帮助模型理解上下文。MessageChatMemoryAdvisor的源码在前文中已经有介绍过,这里就不再赘述。
3.1.6 PromptChatMemoryAdvisor
PromptChatMemoryAdvisor 是 Spring AI 中另一个实现聊天记忆功能的顾问类,与 MessageChatMemoryAdvisor 直接将历史消息添加到消息列表不同,它采用了一种更智能、更可控的方式:将对话历史作为系统提示词的一部分注入到请求中。PromptChatMemoryAdvisor的源码在前文中已经有介绍过,这里就不再赘述。
3.1.7 ChatModelCallAdvisor
ChatModelCallAdvisor 是 Spring AI 中一个极其重要的 Advisor,它作为整个 Advisor 链的终端处理器,负责实际调用底层的 AI 模型。
ChatModelCallAdvisor 是 Advisor 链中的最后一个环节,它的主要职责是:
- 终止链式调用:不再传递给下一个 Advisor
- 调用 AI 模型:实际执行 ChatModel.call() 方法
- 输出格式处理:增强提示词以支持结构化输出
public final class ChatModelCallAdvisor implements CallAdvisor {
private final ChatModel chatModel;
private ChatModelCallAdvisor(ChatModel chatModel) {
Assert.notNull(chatModel, "chatModel cannot be null");
this.chatModel = chatModel;
}
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
// 1. 增强请求:添加输出格式指令
ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);
// 2. 实际调用 AI 模型(链的终点!)
// 关键点:这个方法没有调用 callAdvisorChain.nextCall(),这意味着它是链的终点!
ChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());
// 3. 构建响应
return ChatClientResponse.builder().chatResponse(chatResponse).context(Map.copyOf(formattedChatClientRequest.context())).build();
}
private static ChatClientRequest augmentWithFormatInstructions(ChatClientRequest chatClientRequest) {
String outputFormat = (String)chatClientRequest.context().get(ChatClientAttributes.OUTPUT_FORMAT.getKey());
if (!StringUtils.hasText(outputFormat)) {
return chatClientRequest;// 没有输出格式要求,直接返回
} else {
// 增强用户消息:添加格式指令
Prompt augmentedPrompt = chatClientRequest.prompt().augmentUserMessage((userMessage) -> {
UserMessage.Builder var10000 = userMessage.mutate();
String var10001 = userMessage.getText();
return var10000.text(var10001 + System.lineSeparator() + outputFormat).build();
});
return ChatClientRequest.builder().prompt(augmentedPrompt).context(Map.copyOf(chatClientRequest.context())).build();
}
}
public String getName() {
return "call";
}
public int getOrder() {
return Integer.MAX_VALUE;// 确保最后执行
}
......
}
3.1.8 ChatModelStreamAdvisor
ChatModelStreamAdvisor 是 Spring AI 中专门处理流式调用的终端 Advisor,它负责将流式请求最终传递给底层的 AI 模型。
ChatModelStreamAdvisor 是流式 Advisor 链中的最后一个环节,主要职责是:
- 终止流式调用链:不再传递给下一个 StreamAdvisor
- 调用流式 AI 模型:执行 ChatModel.stream() 方法
- 响应流转换:将 AI 模型的响应流转换为统一的 ChatClientResponse 流
public final class ChatModelStreamAdvisor implements StreamAdvisor {
private final ChatModel chatModel;
private ChatModelStreamAdvisor(ChatModel chatModel) {
Assert.notNull(chatModel, "chatModel cannot be null");
this.chatModel = chatModel;
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
// 1. 直接调用 AI 模型的流式接口(链的终点!)
// 2. 将每个 ChatResponse 转换为 ChatClientResponse
// 3. 切换到有界弹性调度器
return this.chatModel.stream(chatClientRequest.prompt()).map((chatResponse) ->
ChatClientResponse.builder().chatResponse(chatResponse).context(Map.copyOf(chatClientRequest.context())).build()).publishOn(Schedulers.boundedElastic());
}
public String getName() {
return "stream";
}
// 都是终端 Advisor,不继续传递链
public int getOrder() {
return Integer.MAX_VALUE;// 都是最后执行
}
......
}
为什么使用有界弹性调度器:
- 防止阻塞:将流处理移到弹性线程池
- 背压支持:适应不同的消费速度
- 资源管理:限制并发线程数,防止资源耗尽
其实有个疑问在这,我们在使用DefaultChatClientBuilder#defaultAdvisors方法时每次都只构建了自定义的Advisors类,并没有手动加入ChatModelCallAdvisor类,但是前面又提到ChatModelCallAdvisor类作为 Advisor 链的终端处理器,负责实际调用底层的 AI 模型,那它是什么时候加入AdvisorChain的呢?我们看下面代码,Spring AI源码帮我们自动的在DefaultChatClient#buildAdvisorChain方法里进行构建了ChatModelCallAdvisor类,并且将其放入AdvisorChain中。

3.1.9 SafeGuardAdvisor
SafeGuardAdvisor 是 Spring AI 中一个重要的安全防护 Advisor,它提供了基于敏感词过滤的内容安全机制。
SafeGuardAdvisor 是一个安全拦截器,主要职责是:
- 敏感内容检测:检查用户请求是否包含敏感词汇
- 请求拦截:在敏感内容被发送到 AI 模型之前进行拦截
- 安全响应:返回预设的安全回复,避免不当内容生成
public class SafeGuardAdvisor implements CallAdvisor, StreamAdvisor {
private static final String DEFAULT_FAILURE_RESPONSE = "I'm unable to respond to that due to sensitive content. Could we rephrase or discuss something else?";
private static final int DEFAULT_ORDER = 0;
private final String failureResponse;
private final List<String> sensitiveWords;
private final int order;
public SafeGuardAdvisor(List<String> sensitiveWords) {
this(sensitiveWords, "I'm unable to respond to that due to sensitive content. Could we rephrase or discuss something else?", 0);
}
public SafeGuardAdvisor(List<String> sensitiveWords, String failureResponse, int order) {
Assert.notNull(sensitiveWords, "Sensitive words must not be null!");
Assert.notNull(failureResponse, "Failure response must not be null!");
this.sensitiveWords = sensitiveWords;
this.failureResponse = failureResponse;
this.order = order;
}
public static Builder builder() {
return new Builder();
}
public String getName() {
return this.getClass().getSimpleName();
}
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
// 拦截并返回安全响应
// 放行,继续链式调用
// 简单包含检测:使用字符串包含匹配
// 任何匹配即拦截:只要包含任一敏感词就触发拦截
return !CollectionUtils.isEmpty(this.sensitiveWords) && this.sensitiveWords.stream().anyMatch((w) -> chatClientRequest.prompt().getContents().contains(w)) ? this.createFailureResponse(chatClientRequest) : callAdvisorChain.nextCall(chatClientRequest);
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
return !CollectionUtils.isEmpty(this.sensitiveWords) && this.sensitiveWords.stream().anyMatch((w) -> chatClientRequest.prompt().getContents().contains(w)) ? Flux.just(this.createFailureResponse(chatClientRequest)) : streamAdvisorChain.nextStream(chatClientRequest);
}
private ChatClientResponse createFailureResponse(ChatClientRequest chatClientRequest) {
return ChatClientResponse.builder().chatResponse(ChatResponse.builder().generations(List.of(new Generation(new AssistantMessage(this.failureResponse)))).build()).context(Map.copyOf(chatClientRequest.context())).build();
}
public int getOrder() {
return this.order;
}
......
}
3.1.10 SimpleLoggerAdvisor
SimpleLoggerAdvisor 是 Spring AI 中一个非常实用的日志记录 Advisor,它同时支持同步和流式调用,为 AI 对话提供完整的请求/响应日志记录功能。
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
// 请求默认:使用 toString() 方法
public static final Function<ChatClientRequest, String> DEFAULT_REQUEST_TO_STRING = ChatClientRequest::toString;
// 响应默认:使用漂亮的 JSON 格式化
public static final Function<ChatResponse, String> DEFAULT_RESPONSE_TO_STRING = ModelOptionsUtils::toJsonStringPrettyPrinter;
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
private final Function<ChatClientRequest, String> requestToString;
private final Function<ChatResponse, String> responseToString;
private final int order;
public SimpleLoggerAdvisor() {
this(DEFAULT_REQUEST_TO_STRING, DEFAULT_RESPONSE_TO_STRING, 0);
}
public SimpleLoggerAdvisor(int order) {
this(DEFAULT_REQUEST_TO_STRING, DEFAULT_RESPONSE_TO_STRING, order);
}
public SimpleLoggerAdvisor(@Nullable Function<ChatClientRequest, String> requestToString, @Nullable Function<ChatResponse, String> responseToString, int order) {
this.requestToString = requestToString != null ? requestToString : DEFAULT_REQUEST_TO_STRING;
this.responseToString = responseToString != null ? responseToString : DEFAULT_RESPONSE_TO_STRING;
this.order = order;
}
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
this.logRequest(chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
this.logResponse(chatClientResponse);
return chatClientResponse;
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
this.logRequest(chatClientRequest);
Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
// 使用聚合器记录完整的流式响应
// 流式处理特点:记录请求后,等待整个流完成再记录完整响应
return (new ChatClientMessageAggregator()).aggregateChatClientResponse(chatClientResponses, this::logResponse);
}
protected void logRequest(ChatClientRequest request) {
logger.debug("request: {}", this.requestToString.apply(request));
}
protected void logResponse(ChatClientResponse chatClientResponse) {
logger.debug("response: {}", this.responseToString.apply(chatClientResponse.chatResponse()));
}
public String getName() {
return this.getClass().getSimpleName();
}
public int getOrder() {
return this.order;
}
public String toString() {
return SimpleLoggerAdvisor.class.getSimpleName();
}
....
}
3.1.11 StructuredOutputValidationAdvisor
StructuredOutputValidationAdvisor是一个 Spring AI 框架中的结构化输出验证顾问类,用于确保 AI 模型的输出符合预定义的数据结构。它的主要作用是通过 JSON Schema 验证 AI 模型的输出结构,确保返回的数据符合预期的格式和类型要求。
public final class StructuredOutputValidationAdvisor implements CallAdvisor, StreamAdvisor {
private static final Logger logger = LoggerFactory.getLogger(StructuredOutputValidationAdvisor.class);
private static final TypeRef<HashMap<String, Object>> MAP_TYPE_REF = new TypeRef<HashMap<String, Object>>() {
};
private final int advisorOrder;
private final Map<String, Object> jsonSchema;
private final DefaultJsonSchemaValidator jsonvalidator;
private final int maxRepeatAttempts;
private StructuredOutputValidationAdvisor(int advisorOrder, Type outputType, int maxRepeatAttempts, ObjectMapper objectMapper) {
Assert.notNull(advisorOrder, "advisorOrder must not be null");
Assert.notNull(outputType, "outputType must not be null");
Assert.isTrue(advisorOrder > Integer.MIN_VALUE && advisorOrder < Integer.MAX_VALUE, "advisorOrder must be between HIGHEST_PRECEDENCE and LOWEST_PRECEDENCE");
Assert.isTrue(maxRepeatAttempts >= 0, "repeatAttempts must be greater than or equal to 0");
Assert.notNull(objectMapper, "objectMapper must not be null");
this.advisorOrder = advisorOrder;
this.jsonvalidator = new DefaultJsonSchemaValidator(objectMapper);
// 1. 根据输出类型生成 JSON Schema
String jsonSchemaText = JsonSchemaGenerator.generateForType(outputType, new JsonSchemaGenerator.SchemaOption[0]);
logger.info("Generated JSON Schema:\n" + jsonSchemaText);
JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(JsonParser.getObjectMapper());
try {
// 2. 解析 Schema 为 Map
this.jsonSchema = (Map)jsonMapper.readValue(jsonSchemaText, MAP_TYPE_REF);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to parse JSON schema", e);
}
this.maxRepeatAttempts = maxRepeatAttempts;
}
public String getName() {
return "Structured Output Validation Advisor";
}
public int getOrder() {
return this.advisorOrder;
}
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(callAdvisorChain, "callAdvisorChain must not be null");
Assert.notNull(chatClientRequest, "chatClientRequest must not be null");
ChatClientResponse chatClientResponse = null;
int repeatCounter = 0;
boolean isValidationSuccess = true;
ChatClientRequest processedChatClientRequest = chatClientRequest;
do {
++repeatCounter;
// 1. 调用模型获取响应
chatClientResponse = callAdvisorChain.copy(this).nextCall(processedChatClientRequest);
if (chatClientResponse.chatResponse() == null || !chatClientResponse.chatResponse().hasToolCalls()) {
// 2. 验证输出结构
JsonSchemaValidator.ValidationResponse validationResponse = this.validateOutputSchema(chatClientResponse);
isValidationSuccess = validationResponse.valid();
// 3. 如果验证失败,添加错误信息并重试
if (!isValidationSuccess) {
logger.warn("JSON validation failed: " + String.valueOf(validationResponse));
String validationErrorMessage = "Output JSON validation failed because of: " + validationResponse.errorMessage();
Prompt augmentedPrompt = chatClientRequest.prompt().augmentUserMessage((userMessage) -> {
UserMessage.Builder var10000 = userMessage.mutate();
String var10001 = userMessage.getText();
return var10000.text(var10001 + System.lineSeparator() + validationErrorMessage).build();
});
processedChatClientRequest = chatClientRequest.mutate().prompt(augmentedPrompt).build();
}
}
} while(!isValidationSuccess && repeatCounter <= this.maxRepeatAttempts);
return chatClientResponse;
}
private JsonSchemaValidator.ValidationResponse validateOutputSchema(ChatClientResponse chatClientResponse) {
if (chatClientResponse.chatResponse() != null && chatClientResponse.chatResponse().getResult() != null && chatClientResponse.chatResponse().getResult().getOutput() != null && chatClientResponse.chatResponse().getResult().getOutput().getText() != null) {
String json = chatClientResponse.chatResponse().getResult().getOutput().getText();
logger.debug("Validating JSON output against schema. Attempts left: " + this.maxRepeatAttempts);
return this.jsonvalidator.validate(this.jsonSchema, json);
} else {
logger.warn("ChatClientResponse is missing required json output for validation.");
return ValidationResponse.asInvalid("Missing required json output for validation.");
}
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
return Flux.error(new UnsupportedOperationException("The Structured Output Validation Advisor does not support streaming."));
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private int advisorOrder = 2147481647;
private Type outputType;
private int maxRepeatAttempts = 3;
private ObjectMapper objectMapper = JsonParser.getObjectMapper();
private Builder() {
}
public Builder advisorOrder(int advisorOrder) {
this.advisorOrder = advisorOrder;
return this;
}
// 使用 Class
public Builder outputType(Type outputType) {
this.outputType = outputType;
return this;
}
// 使用 TypeRef
public <T> Builder outputType(TypeRef<T> outputType) {
this.outputType = outputType.getType();
return this;
}
// 使用 TypeRef
public <T> Builder outputType(TypeReference<T> outputType) {
this.outputType = outputType.getType();
return this;
}
// 使用 ParameterizedTypeReference
public <T> Builder outputType(ParameterizedTypeReference<T> outputType) {
this.outputType = outputType.getType();
return this;
}
public Builder maxRepeatAttempts(int repeatAttempts) {
this.maxRepeatAttempts = repeatAttempts;
return this;
}
public Builder objectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
return this;
}
public StructuredOutputValidationAdvisor build() {
if (this.outputType == null) {
throw new IllegalArgumentException("outputType must be set");
} else {
return new StructuredOutputValidationAdvisor(this.advisorOrder, this.outputType, this.maxRepeatAttempts, this.objectMapper);
}
}
}
}
3.1.12 ToolCallAdvisor
ToolCallAdvisor是一个 Spring AI 框架中的工具调用顾问类,用于处理 AI 模型与外部工具的交互。
主要作用
在 AI 对话过程中,当模型需要调用外部工具时,这个顾问会:
-
检测模型返回的工具调用请求
-
执行相应的工具
-
将工具执行结果返回给模型继续处理
-
支持多次工具调用的循环(直到不再需要调用工具)
public final class ToolCallAdvisor implements CallAdvisor, StreamAdvisor {
private final ToolCallingManager toolCallingManager;
private final int advisorOrder;
private ToolCallAdvisor(ToolCallingManager toolCallingManager, int advisorOrder) {
Assert.notNull(toolCallingManager, "toolCallingManager must not be null");
Assert.isTrue(advisorOrder > Integer.MIN_VALUE && advisorOrder < Integer.MAX_VALUE, "advisorOrder must be between HIGHEST_PRECEDENCE and LOWEST_PRECEDENCE");
this.toolCallingManager = toolCallingManager;
this.advisorOrder = advisorOrder;
}
public String getName() {
return "Tool Calling Advisor";
}
public int getOrder() {
return this.advisorOrder;
}
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(callAdvisorChain, "callAdvisorChain must not be null");
Assert.notNull(chatClientRequest, "chatClientRequest must not be null");
// 配置检查 - 确保请求使用了 ToolCallingChatOptions
if (chatClientRequest.prompt().getOptions() != null && chatClientRequest.prompt().getOptions() instanceof ToolCallingChatOptions) {
ToolCallingChatOptions optionsCopy = (ToolCallingChatOptions)chatClientRequest.prompt().getOptions().copy();
// 禁用内部执行 - 设置 internalToolExecutionEnabled(false) 让顾问接管工具执行
optionsCopy.setInternalToolExecutionEnabled(false);
List<Message> instructions = chatClientRequest.prompt().getInstructions();
ChatClientResponse chatClientResponse = null;
boolean isToolCall = false;
// 循环处理:
// 调用模型获取响应
// 检查是否有工具调用 (hasToolCalls())
// 如果有工具调用,通过 ToolCallingManager 执行工具
// 如果工具直接返回结果 (returnDirect),结束循环
// 否则将执行结果加入对话历史,继续循环
do {
ChatClientRequest processedChatClientRequest = ChatClientRequest.builder().prompt(new Prompt(instructions, optionsCopy)).context(chatClientRequest.context()).build();
chatClientResponse = callAdvisorChain.copy(this).nextCall(processedChatClientRequest);
isToolCall = chatClientResponse.chatResponse() != null && chatClientResponse.chatResponse().hasToolCalls();
if (isToolCall) {
// 负责实际执行工具调用并返回结果。
ToolExecutionResult toolExecutionResult = this.toolCallingManager.executeToolCalls(processedChatClientRequest.prompt(), chatClientResponse.chatResponse());
if (toolExecutionResult.returnDirect()) {
chatClientResponse = chatClientResponse.mutate().chatResponse(ChatResponse.builder().from(chatClientResponse.chatResponse()).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build()).build();
break;
}
instructions = toolExecutionResult.conversationHistory();
}
} while(isToolCall);
return chatClientResponse;
} else {
throw new IllegalArgumentException("ToolCall Advisor requires ToolCallingChatOptions to be set in the ChatClientRequest options.");
}
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
return Flux.error(new UnsupportedOperationException("Unimplemented method 'adviseStream'"));
}
public static Builder builder() {
return new Builder();
}
.....
}
3.2 AdvisorChain
在Spring AI中,AdvisorChain(通知器链)是一个核心机制,它通过责任链模式来组织和执行一系列Advisor,让你能够在AI模型处理请求的前后,无侵入地加入自定义逻辑,例如管理对话记忆、记录日志或实现重试机制。
public interface AdvisorChain {
default ObservationRegistry getObservationRegistry() {
return ObservationRegistry.NOOP;
}
}
3.2.1 CallAdvisorChain
CallAdvisorChain 是同步调用 Advisor 链的核心控制器,负责协调多个 CallAdvisor 按顺序处理请求和响应。
public interface CallAdvisorChain extends AdvisorChain {
// 推动链中的下一个 Advisor 执行
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
// 链状态查询:返回链中所有的 CallAdvisor,保持不可变性:通常返回不可变列表,防止外部修改链结构
List<CallAdvisor> getCallAdvisors();
// 链复制能力:创建从指定 Advisor 开始的新链
CallAdvisorChain copy(CallAdvisor after);
}
3.2.2 StreamAdvisorChain
StreamAdvisorChain 是流式调用 Advisor 链的核心控制器,负责协调多个 StreamAdvisor 按顺序处理流式请求和响应。
public interface StreamAdvisorChain extends AdvisorChain {
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
List<StreamAdvisor> getStreamAdvisors();
}
3.2.3 BaseAdvisorChain
public interface BaseAdvisorChain extends CallAdvisorChain, StreamAdvisorChain {
}
3.2.4 DefaultAroundAdvisorChain
DefaultAroundAdvisorChain 是 Spring AI 中 Advisor 链的默认实现,它巧妙地将多种设计模式结合起来,提供了一个强大而灵活的拦截器链机制。链式执行与洋葱模型:AdvisorChain 是 Advisor 的集合容器。请求 (ChatClientRequest) 和响应 (ChatClientResponse) 会依次通过链中的每一个 Advisor。这形成了一个类似洋葱模型的处理流程:请求从外到内依次穿越所有 Advisor 最终到达AI模型,响应则从内到外反向穿越所有 Advisor 后返回。这就实现了你在流程图中看到的先"Pre-Processing"后"Post-Processing"的环绕增强效果。
public class DefaultAroundAdvisorChain implements BaseAdvisorChain {
public static final AdvisorObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultAdvisorObservationConvention();
private static final ChatClientMessageAggregator CHAT_CLIENT_MESSAGE_AGGREGATOR = new ChatClientMessageAggregator();
// 原始链的不可变副本 - 用于查询和复制
private final List<CallAdvisor> originalCallAdvisors;
private final List<StreamAdvisor> originalStreamAdvisors;
// 同步调用链 - 用于阻塞式调用
private final Deque<CallAdvisor> callAdvisors;
// 流式调用链 - 用于响应式流调用
private final Deque<StreamAdvisor> streamAdvisors;
// 内置 Micrometer 观测支持,自动收集链执行的指标和追踪信息
private final ObservationRegistry observationRegistry;
private final AdvisorObservationConvention observationConvention;
DefaultAroundAdvisorChain(ObservationRegistry observationRegistry, Deque<CallAdvisor> callAdvisors, Deque<StreamAdvisor> streamAdvisors, @Nullable AdvisorObservationConvention observationConvention) {
Assert.notNull(observationRegistry, "the observationRegistry must be non-null");
Assert.notNull(callAdvisors, "the callAdvisors must be non-null");
Assert.notNull(streamAdvisors, "the streamAdvisors must be non-null");
this.observationRegistry = observationRegistry;
this.callAdvisors = callAdvisors;
this.streamAdvisors = streamAdvisors;
this.originalCallAdvisors = List.copyOf(callAdvisors);
this.originalStreamAdvisors = List.copyOf(streamAdvisors);
this.observationConvention = observationConvention != null ? observationConvention : DEFAULT_OBSERVATION_CONVENTION;
}
// 构建器模式
public static Builder builder(ObservationRegistry observationRegistry) {
return new Builder(observationRegistry);
}
// 责任链模式 (Chain of Responsibility)
public ChatClientResponse nextCall(ChatClientRequest chatClientRequest) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
if (this.callAdvisors.isEmpty()) {
throw new IllegalStateException("No CallAdvisors available to execute");
} else {
// 1. 从链中取出下一个 Advisor
CallAdvisor advisor = (CallAdvisor)this.callAdvisors.pop();
// 2. 创建观测上下文
AdvisorObservationContext observationContext = AdvisorObservationContext.builder().advisorName(advisor.getName()).chatClientRequest(chatClientRequest).order(advisor.getOrder()).build();
// 3. 在观测上下文中执行 Advisor
return (ChatClientResponse)AdvisorObservationDocumentation.AI_ADVISOR.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).observe(() -> {
// 4. 调用 Advisor,并传递当前链
ChatClientResponse chatClientResponse = advisor.adviseCall(chatClientRequest, this);// 传递链给下一个处理器
observationContext.setChatClientResponse(chatClientResponse);
return chatClientResponse;
});
}
}
public Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
return Flux.deferContextual((contextView) -> {
if (this.streamAdvisors.isEmpty()) {
return Flux.error(new IllegalStateException("No StreamAdvisors available to execute"));
} else {
StreamAdvisor advisor = (StreamAdvisor)this.streamAdvisors.pop();
AdvisorObservationContext observationContext = AdvisorObservationContext.builder().advisorName(advisor.getName()).chatClientRequest(chatClientRequest).order(advisor.getOrder()).build();
Observation observation = AdvisorObservationDocumentation.AI_ADVISOR.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
observation.parentObservation((Observation)contextView.getOrDefault("micrometer.observation", (Object)null)).start();
Flux<ChatClientResponse> chatClientResponse = Flux.defer(() -> {
Flux var10000 = advisor.adviseStream(chatClientRequest, this);
Objects.requireNonNull(observation);
return var10000.doOnError(observation::error).doFinally((s) -> observation.stop()).contextWrite((ctx) -> ctx.put("micrometer.observation", observation));
});
ChatClientMessageAggregator var10000 = CHAT_CLIENT_MESSAGE_AGGREGATOR;
Objects.requireNonNull(observationContext);
return var10000.aggregateChatClientResponse(chatClientResponse, observationContext::setChatClientResponse);
}
});
}
// 链复制能力
public CallAdvisorChain copy(CallAdvisor after) {
Assert.notNull(after, "The after call advisor must not be null");
List<CallAdvisor> callAdvisors = this.getCallAdvisors();
int afterAdvisorIndex = callAdvisors.indexOf(after);
if (afterAdvisorIndex < 0) {
throw new IllegalArgumentException("The specified advisor is not part of the chain: " + after.getName());
} else {
List<CallAdvisor> remainingCallAdvisors = callAdvisors.subList(afterAdvisorIndex + 1, callAdvisors.size());
return builder(this.getObservationRegistry()).pushAll(remainingCallAdvisors).build();
}
}
public List<CallAdvisor> getCallAdvisors() {
return this.originalCallAdvisors;
}
public List<StreamAdvisor> getStreamAdvisors() {
return this.originalStreamAdvisors;
}
public ObservationRegistry getObservationRegistry() {
return this.observationRegistry;
}
......
}
有个问题,DefaultAroundAdvisorChain的构建时机是什么时候呢?多Debug几次就会发现DefaultAroundAdvisorChain的构建在DefaultChatClient#call方法和DefaultChatClient#stream方法里。

四、Spring AI中Advisor实战使用
由于前面介绍Spring AI的其他知识时都用到了SimpleLoggerAdvisor和MessageChatMemoryAdvisor,这里实战就只介绍一下原生SafeGuardAdvisor和自定义Advisor的实战,ToolCallAdvisor实战放在后续讲Tool Calling中。
4.1 使用原生SafeGuardAdvisor敏感词过滤实战
这里以SafeGuardAdvisor来进行敏感词过滤实践,实现代码如下:
配置类:
@Configuration
public class AdvisorConfiguration {
@Bean
ChatClient openAiChatClient(OpenAiChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new SafeGuardAdvisor(Lists.newArrayList("自杀"),"对不起,您的问题涉及敏感不合格词汇,我们无法回答你!!",0))
.build();
}
}
控制器类:
@RestController
public class AdvisorController {
@Resource(name = "openAiChatClient" )
private ChatClient chatClient;
@GetMapping("/testSafeGuardAdvisor/chat")
public String chat(@RequestParam("input") String input) {
return this.chatClient.prompt()
.user(input)
.call()
.content();
}
}
测试效果图如下:

4.2 使用自定义SimilarityCacheAdvisor相似度缓存增强实战
这里我们来自己实现一个增强类SimilarityCacheAdvisor,SimilarityCacheAdvisor的核心功能是缓存相似的对话上下文,避免重复多次调用大模型去处理相同或类似的问题,特别适合FAQ、知识库查询等场景,这个增强类能够有效的降低调用大模型带来的成本。
基于文本相似度走缓存的实现需要解决两个核心问题:如何计算相似度和如何基于相似度进行缓存查询。下面将会介绍具体设计的代码,而由于只是demo版本,很多关键点都做的比较简单,在实际工作中可以选择更加稳定和完善的方式,这里不再展开。
模型类:
@Data
public class CachedQuestion {
// 原始问题文本
private final String originalQuestion;
// 对应的正式缓存键
private final String cacheKey;
// 创建时间
private final long cacheTime;
}
@Data
public class CachedQuestion {
// 原始问题文本
private final String originalQuestion;
// 对应的正式缓存键
private final String cacheKey;
// 创建时间
private final long cacheTime;
}
public class SimilarCacheResult {
private final boolean similar;
private final CachedResponse cachedResponse;
private final double similarityScore;
private SimilarCacheResult(boolean similar, CachedResponse cachedResponse, double similarityScore) {
this.similar = similar;
this.cachedResponse = cachedResponse;
this.similarityScore = similarityScore;
}
public static SimilarCacheResult similar(CachedResponse cachedResponse, double similarityScore) {
return new SimilarCacheResult(true, cachedResponse, similarityScore);
}
public static SimilarCacheResult notSimilar() {
return new SimilarCacheResult(false, null, 0.0);
}
// Getters
public boolean isSimilar() { return similar; }
public CachedResponse getCachedResponse() { return cachedResponse; }
public double getSimilarityScore() { return similarityScore; }
}
@Data
public class SimilarityCacheStats {
private final int cachedQuestionsCount;
}
工具类:
/**
* 文本相似度计算工具类(核心:余弦相似度)
*/
public class TextSimilarityUtil {
// 定义停用词集合
private static final Set<String> STOP_WORDS = Set.of(
"的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看", "好", "自己", "这"
);
// jieba分词器
private static final JiebaSegmenter segmenter = new JiebaSegmenter();
public static double cosineSimilarity2(String text1, String text2) {
if (text1 == null || text2 == null) {
return 0.0;
}
// 改进的文本向量化
Map<String, Integer> vector1 = textToVectorImproved(text1);
Map<String, Integer> vector2 = textToVectorImproved(text2);
// 如果任一向量为空,返回0
if (vector1.isEmpty() || vector2.isEmpty()) {
return 0.0;
}
// 计算点积
double dotProduct = 0.0;
for (String key : vector1.keySet()) {
if (vector2.containsKey(key)) {
dotProduct += vector1.get(key) * vector2.get(key);
}
}
// 计算模长
double norm1 = Math.sqrt(vector1.values().stream()
.mapToDouble(val -> Math.pow(val, 2)).sum());
double norm2 = Math.sqrt(vector2.values().stream()
.mapToDouble(val -> Math.pow(val, 2)).sum());
if (norm1 == 0 || norm2 == 0) {
return 0.0;
}
return dotProduct / (norm1 * norm2);
}
private static Map<String, Integer> textToVectorImproved(String text) {
if (text == null || text.trim().isEmpty()) {
return new HashMap<>();
}
// 1. 转换为小写
String lowerText = text.toLowerCase();
// 2. 分割单词(支持中英文)
List<String> words = segmenter.sentenceProcess(lowerText);
return words.stream()
.filter(word -> !word.trim().isEmpty())
.filter(word -> word.length() > 1) // 过滤单字符
.filter(word -> !STOP_WORDS.contains(word)) // 过滤停用词
.collect(Collectors.toMap(
word -> word,
word -> 1,
Integer::sum, // 正确统计词频
HashMap::new
));
}
}
因为上面引入了jieba来更好的对中文分词,因此需要额外引入依赖:
<dependency>
<groupId>com.huaban</groupId>
<artifactId>jieba-analysis</artifactId>
<version>1.0.2</version>
</dependency>
增强器类:
/**
* 基于相似度的智能缓存Advisor
* 当新问题与缓存问题的相似度达到阈值时,直接返回缓存结果
*/
public class SimilarityCacheAdvisor implements CallAdvisor, StreamAdvisor {
// 响应存储
private final CacheManager cacheManager;
private final double similarityThreshold; // 相似度阈值
// 存储最近的问题用于相似度匹配(生产环境建议用Redis)
private final Map<String, CachedQuestion> questionCache = new ConcurrentHashMap<>();
public SimilarityCacheAdvisor(CacheManager cacheManager,
double similarityThreshold) {
this.cacheManager = cacheManager;
this.similarityThreshold = similarityThreshold;
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
String currentQuestion = chatClientRequest.prompt().getContents();
// 1. 查找相似度达到阈值的问题
SimilarCacheResult similarResult = findSimilarCachedQuestion(currentQuestion);
if (similarResult.isSimilar()) {
// 2. 相似度达到阈值,返回缓存结果
CachedResponse cachedResponse = similarResult.getCachedResponse();
return createAdvisedResponseFromCache(cachedResponse);
}
// 3. 没有找到相似问题,执行正常流程
ChatClientResponse response = callAdvisorChain.nextCall(chatClientRequest);
// 4. 缓存新问题和响应
cacheNewQuestion(currentQuestion, response);
return response;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
String currentQuestion = chatClientRequest.prompt().getContents();
// 流式请求的相似度检查
SimilarCacheResult similarResult = findSimilarCachedQuestion(currentQuestion);
if (similarResult.isSimilar()) {
// 返回缓存的完整响应作为流
CachedResponse cachedResponse = similarResult.getCachedResponse();
return Flux.just(createAdvisedResponseFromCache(cachedResponse));
}
// 执行正常流式流程并缓存结果
return new ChatClientMessageAggregator().aggregateChatClientResponse(
streamAdvisorChain.nextStream(chatClientRequest),
advisedResponse -> {
cacheNewQuestion(currentQuestion, advisedResponse);
}
);
}
@Override
public String getName() {
return "SimilarityCacheAdvisor";
}
@Override
public int getOrder() {
return 5;
}
/**
* 查找相似度达到阈值的缓存问题
*/
private SimilarCacheResult findSimilarCachedQuestion(String currentQuestion) {
if (questionCache.isEmpty()) {
return SimilarCacheResult.notSimilar();
}
// 遍历所有缓存的问题,计算相似度
for (Map.Entry<String, CachedQuestion> entry : questionCache.entrySet()) {
CachedQuestion cachedQuestion = entry.getValue();
// 检查缓存是否过期
if (isCacheExpired(cachedQuestion)) {
questionCache.remove(entry.getKey());
continue;
}
// 计算相似度
double similarity = TextSimilarityUtil.cosineSimilarity2(
currentQuestion,
cachedQuestion.getOriginalQuestion()
);
// 如果相似度达到阈值,返回缓存结果
if (similarity >= similarityThreshold) {
Cache cache = getCache();
Cache.ValueWrapper cachedValue = cache.get(cachedQuestion.getCacheKey());
if (cachedValue != null) {
CachedResponse cachedResponse = (CachedResponse) cachedValue.get();
return SimilarCacheResult.similar(cachedResponse, similarity);
} else {
// 缓存数据不存在,清理问题缓存
questionCache.remove(entry.getKey());
}
}
}
return SimilarCacheResult.notSimilar();
}
/**
* 缓存新问题和响应
*/
private void cacheNewQuestion(String question, ChatClientResponse response) {
if (!shouldCacheResponse(response)) {
return;
}
String cacheKey = "ai_cache_" + System.currentTimeMillis() + "_" +
Math.abs(question.hashCode());
// 缓存响应数据
CachedResponse cachedResponse = new CachedResponse(
response.chatResponse(),
question,
System.currentTimeMillis()
);
Cache cache = getCache();
cache.put(cacheKey, cachedResponse);
// 缓存问题用于相似度匹配
CachedQuestion cachedQuestion = new CachedQuestion(question, cacheKey, System.currentTimeMillis());
questionCache.put(cacheKey, cachedQuestion);
// 清理过期缓存(简单的LRU策略)
cleanupExpiredCache();
}
/**
* 清理过期缓存
*/
private void cleanupExpiredCache() {
long currentTime = System.currentTimeMillis();
long maxCacheSize = 1000; // 最大缓存问题数量
if (questionCache.size() > maxCacheSize) {
// 简单的LRU清理:移除最早的一半缓存
questionCache.entrySet().stream()
.sorted(Comparator.comparingLong(e -> e.getValue().getCacheTime()))
.limit(maxCacheSize / 2)
.map(Map.Entry::getKey)
.toList()
.forEach(questionCache::remove);
}
// 清理过期缓存
questionCache.entrySet().removeIf(entry ->
isCacheExpired(entry.getValue())
);
}
private boolean isCacheExpired(CachedQuestion cachedQuestion) {
long currentTime = System.currentTimeMillis();
return (currentTime - cachedQuestion.getCacheTime()) > (60 * 60 * 1000); // 1小时过期
}
private boolean shouldCacheResponse(ChatClientResponse response) {
return response != null &&
response.chatResponse() != null &&
response.chatResponse().getResult() != null;
}
private Cache getCache() {
return cacheManager.getCache("similarityCache");
}
private ChatClientResponse createAdvisedResponseFromCache(CachedResponse cachedResponse) {
// 根据你的Spring AI版本适配
return ChatClientResponse.builder()
.chatResponse(cachedResponse.getChatResponse())
.build();
}
/**
* 获取缓存统计信息
*/
public SimilarityCacheStats getStats() {
return new SimilarityCacheStats(questionCache.size());
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private CacheManager cacheManager;
private double similarityThreshold;
private Builder() {
}
public Builder cacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
return this;
}
public Builder similarityThreshold(double similarityThreshold) {
this.similarityThreshold = similarityThreshold;
return this;
}
public SimilarityCacheAdvisor build() {
return new SimilarityCacheAdvisor(this.cacheManager, this.similarityThreshold);
}
}
}
因为上面代码使用了Caffine来实现本地缓存,因此是需要额外引入依赖的:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置类:
@Configuration
@EnableCaching
public class AdvisorConfiguration {
@Bean
public SimilarityCacheAdvisor similarityCacheAdvisor(CacheManager cacheManager) {
// 设置相似度阈值为90%
return SimilarityCacheAdvisor.builder()
.cacheManager(cacheManager)
.similarityThreshold(0.7)
.build();
}
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(2, TimeUnit.HOURS) // 缓存2小时
.maximumSize(500));
cacheManager.setCacheNames(Lists.newArrayList("similarityCache"));
return cacheManager;
}
@Bean
ChatClient deepSeekChatClient(DeepSeekChatModel chatModel, SimilarityCacheAdvisor similarityCacheAdvisor) {
return ChatClient.builder(chatModel)
.defaultAdvisors(similarityCacheAdvisor)
.build();
}
}
控制器类:
@RestController
public class AdvisorController {
@Resource(name = "deepSeekChatClient" )
private ChatClient chatClient;
@GetMapping("/testSimilarityCacheAdvisor/chat")
public String chat2(@RequestParam("input") String input) {
return this.chatClient.prompt()
.user(input)
.call()
.content();
}
}
最终的测试效果如下图:


五、总结
Spring AI的Advisor就像给AI模型装上了一套“可编程插件系统”。无论是增强回答准确性、保障安全性,还是实现长期记忆,它都能让开发者灵活定制AI行为。记住:用好Advisor的关键是“分工明确、顺序得当、监控到位”。
六、参考资料

更多推荐



所有评论(0)