在这里插入图片描述

🪁🍁 希望本文能给您带来帮助,如果有任何问题,欢迎批评指正!🐅🐾🍁🐥


导航参见:
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核心特点

  1. 模块化封装
  • 重复任务封装:将生成式 AI 的通用模式(如上下文记忆、敏感词过滤)抽象为可复用的组件。
  • 数据转换:优化发送至语言模型(LLM)的输入数据格式,并处理返回的响应(如结构化输出转换)。
  • 可移植性:同一 Advisor 可适配不同模型(如 OpenAI、HuggingFace)和用例,提升代码灵活性。
  1. 链式处理机制
    多个 Advisor 按顺序执行,每个环节可修改请求或响应,并决定是否继续传递。某些 Advisor(如 SafeGuardAdvisor)可能直接中断链式流程。
  2. 内置与扩展性
    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 对话过程中,当模型需要调用外部工具时,这个顾问会:

  1. 检测模型返回的工具调用请求

  2. 执行相应的工具

  3. 将工具执行结果返回给模型继续处理

  4. 支持多次工具调用的循环(直到不再需要调用工具)

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的关键是“分工明确、顺序得当、监控到位”。


六、参考资料


创作不易,如果有帮助到你的话请给点个赞吧!我是Wasteland,下期文章再见!

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐