1. 概述

Advisors APISpring AI 提供的核心扩展机制,本质是「AI 交互的拦截器 / 增强器」,它能在 ChatClient 与大模型(LLM)的交互全链路中(调用前 / 调用后)插入自定义逻辑,实现对请求 / 响应的拦截、修改、增强,最终让开发者无需重复编写通用 AI 逻辑,快速构建复杂、可复用、易维护的 AI 组件。

2. 核心组件

Advisors 相关接口位于包 org.springframework.ai.chat.client.advisor.api

在这里插入图片描述
Advisors API 包含用于非流式场景的 CallAdvisorCallAdvisorChain,以及用于流式场景的 StreamAdvisorStreamAdvisorChain。同时还包含用于表示待封装提示词请求的 ChatClientRequest,以及用于承载对话补全响应的 ChatClientResponse。二者均持有一个增强上下文(advise-context),用于在增强器链中共享状态。

在这里插入图片描述

2.1 Advisor

Advisor 是所有增强器的顶层父接口,定义了增强器的最基础能力:

public interface Advisor extends Ordered {
    String getName();
}

能力介绍:

  • 继承 Ordered 接口:表示所有增强器都具备 “排序能力”,可通过排序规则(如优先级)决定多个增强器的执行顺序。
  • 核心方法 getName():获取增强器的唯一名称,用于标识增强器、日志打印、异常排查。

Advisor 有两个子接口:

  • CallAdvisor
  • StreamAdvisor

2.2 CallAdvisor

用于同步、非流式的对话增强,适用于不需要实时流式返回、对响应完整性要求高的场景。

接口定义:

// 同步(非流式)增强器
public interface CallAdvisor extends Advisor {
    ChatClientResponse adviseCall(
        ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}

核心方法:

  • adviseCall(...) 增强同步对话的核心方法,接收两个参数、返回增强后的响应:
  • ChatClientRequest:输入参数,代表原始对话请求。
  • CallAdvisorChain:输入参数,增强器链用于调用下一个增强器,若有多个增强器,通过该链条按优先级执行。
  • ChatClientResponse:返回值,增强后的对话响应。

所以子接口和子类:

在这里插入图片描述

2.3 StreamAdvisor

用于响应式、流式的对话增强,适用于需要实时反馈、响应内容较长的场景。其方法能力和CallAdvisor 一致。

接口定义:

// 响应式(流式)增强器
public interface StreamAdvisor extends Advisor {
    Flux<ChatClientResponse> adviseStream(
        ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}

所以子接口和子类:

在这里插入图片描述

2.4 AdvisorChain

AdvisorChain 是增强器执行链的核心载体,设计理念完全类比 Spring AOP 切面链、Servlet 过滤器链,负责按优先级串联执行所有增强器,并最终调用大模型接口。

ChatClient 同步调用的核心方法中,会先构建增强器链,再将请求交由链条处理:

		@Override
		public CallResponseSpec call() {
			BaseAdvisorChain advisorChain = buildAdvisorChain();
			return new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain,
					this.observationRegistry, this.chatClientObservationConvention);
		}

增强器链的顶层父接口,提供观测注册能力:

public interface AdvisorChain {
    default ObservationRegistry getObservationRegistry() {
        return ObservationRegistry.NOOP;
    }
}
public interface CallAdvisorChain extends AdvisorChain {
    ChatClientResponse nextCall(ChatClientRequest chatClientRequest);

    List<CallAdvisor> getCallAdvisors();

    CallAdvisorChain copy(CallAdvisor after);
}

Spring AI 框架提供了多个内置增强器实现:

在这里插入图片描述

管理对话记忆存储中的对话历史:

  • MessageChatMemoryAdvisor:检索记忆并以消息集合形式添加到提示词(保留对话历史结构,部分 AI 模型不支持)。
  • PromptChatMemoryAdvisor:检索记忆并整合到提示词的系统文本中。
  • VectorStoreChatMemoryAdvisor:从向量库检索记忆并添加到提示词的系统文本中(适用于从海量数据中高效检索相关信息)。

问答增强器:

  • QuestionAnswerAdvisor:基于向量库实现问答能力,采用朴素 RAG(检索增强生成)模式。
  • RetrievalAugmentationAdvisor:基于 org.springframework.ai.rag 包的构建块,遵循模块化 RAG 架构,实现通用 RAG 流程。

推理增强器:

  • ReReadingAdvisor:实现 RE2 重读策略,提升大语言模型在输入阶段的理解能力。
    内容安全增强器:
  • SafeGuardAdvisor:简单的增强器,用于防止模型生成有害或不当内容。

2.5 ChatClientRequest

Java record 定义的不可变数据载体类,专门用于封装聊天客户端的请求数据,核心包含 prompt(提示词)和 context(增强上下文)两部分信息。

核心代码:

public record ChatClientRequest(Prompt prompt, Map<String, Object> context) {
    public ChatClientRequest(Prompt prompt, Map<String, Object> context) {
        Assert.notNull(prompt, "prompt cannot be null");
        Assert.notNull(context, "context cannot be null");
        Assert.noNullElements(context.keySet(), "context keys cannot be null");
        this.prompt = prompt;
        this.context = context;
    }

    public ChatClientRequest copy() {
        return new ChatClientRequest(this.prompt.copy(), new HashMap(this.context));
    }

    public Builder mutate() {
        return (new Builder()).prompt(this.prompt.copy()).context(new HashMap(this.context));
    }

    public static Builder builder() {
        return new Builder();
    }

    public Prompt prompt() {
        return this.prompt;
    }

    public Map<String, Object> context() {
        return this.context;
    }
    //.......
}

提示:增强上下文用于在增强器链中共享状态。

2.6 ChatClientResponse

Java record 定义的不可变数据载体类,专门用于封装聊天请求的返回结果,核心包含 ChatResponse(核心响应内容)和 context(上下文信息)两部分信息。

核心代码:

public record ChatClientResponse(ChatResponse chatResponse, Map<String, Object> context) {
    public ChatClientResponse(@Nullable ChatResponse chatResponse, Map<String, Object> context) {
        Assert.notNull(context, "context cannot be null");
        Assert.noNullElements(context.keySet(), "context keys cannot be null");
        this.chatResponse = chatResponse;
        this.context = context;
    }

    public ChatClientResponse copy() {
        return new ChatClientResponse(this.chatResponse, new HashMap(this.context));
    }

    public Builder mutate() {
        return (new Builder()).chatResponse(this.chatResponse).context(new HashMap(this.context));
    }

    public static Builder builder() {
        return new Builder();
    }

    @Nullable
    public ChatResponse chatResponse() {
        return this.chatResponse;
    }

    public Map<String, Object> context() {
        return this.context;
    }

3. 执行流程

以下流程图展示了增强器链与聊天模型的交互逻辑:

在这里插入图片描述

流程相关说明:

  1. Spring AI 框架根据用户的 Prompt 创建 ChatClientRequest,并初始化空的增强上下文对象。
  2. 链中的每个增强器处理请求(可能修改请求),也可选择不调用下一个组件以阻塞请求(此时需由该增强器填充响应)。
  3. 框架提供的最终增强器将请求发送至聊天模型。
  4. 聊天模型的响应沿增强器链回传,并转换为包含共享增强上下文的ChatClientResponse
  5. 每个增强器可处理或修改响应。
  6. 提取 ChatCompletion 后,最终的ChatClientResponse 返回给客户端。

增强器在链中的执行顺序由 getOrder() 方法决定,核心规则:

  • 值越小,执行优先级越高(先执行);值越大,优先级越低。
  • 若多个增强器 order 值相同,执行顺序不保证。

增强器链遵循栈式逻辑:

  • 链中第一个增强器最先处理请求。
  • 最后处理响应。

控制执行顺序的方式:

  • 设置 order 接近 Ordered.HIGHEST_PRECEDENCE:确保增强器最先执行(请求处理先执行,响应处理最后执行);
  • 设置 order 接近 Ordered.LOWEST_PRECEDENCE:确保增强器最后执行(请求处理最后执行,响应处理最先执行);

Spring Ordered 接口语义参考:

public interface Ordered {
    /** 最高优先级常量 */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    /** 最低优先级常量 */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
    /**
     * 获取对象的顺序值
     * <p>值越大,优先级越低;值越小,优先级越高(类似 Servlet 的 load-on-startup)
     * <p>相同值会导致对象排序位置随机
     * @return 顺序值
     */
    int getOrder();
}

4. 案例演示

4.1 请求、响应日志打印

4.1.1 日志增强器

Spring AI 默认已经提供了一个日志打印实现,实现了 CallAdvisor, StreamAdvisor 接口,说明同时支持同步和流式场景:

public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

	public static final Function<ChatClientRequest, String> DEFAULT_REQUEST_TO_STRING = ChatClientRequest::toString;

	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;
	}

	@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);
	}

	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()));
	}

4.1.2 配置

SimpleLoggerAdvisor 使用的是 logger.debug 所以还需要配置以下日志级别:

# 日志配置
logging:
  level:
    org.springframework.ai.chat.client.advisor: DEBUG

ChatClient 中配置增强器:

    @Bean("zhiPuAiChatClient")
    public ChatClient zhiPuAiChatClient(ZhiPuAiChatModel zhiPuAiChatModel) {
        return ChatClient.builder(zhiPuAiChatModel)
                .defaultAdvisors(
                        SimpleLoggerAdvisor.builder().build()
                )
                .build();
    }

4.1.3 测试

运行测试类,并查看控制台:

    @Test
    public void zhiPuAiChatClientTest() {
        String content = zhiPuAiChatClient
                .prompt("今天周几")
                .call()
                .content();
        System.out.println(content);
}

在这里插入图片描述

4.2 自定义增强器

创建增强器需实现 CallAdvisorStreamAdvisor(或两者),核心步骤:

  • 实现 nextCall()(非流式)或 nextStream()(流式)方法。
  • 为增强器提供唯一名称。
  • 通过 order 值控制执行顺序(值越小越先执行)。

Spring AI 还提供了 MessageAggregator 工具类,可将 Flux 响应聚合为单个 ChatClientResponse ,适用于观测完整响应的场景,仅只读操作,无法修改响应。

4.2.1 Re‑Reading(Re2)

Re2 是论文 《Re‑Reading Improves Reasoning in Large Language Models》 里提出的一个超级简单但非常有效的技巧,用来提升大模型的推理、思考、解题能力。

核心思想:让模型把问题 “再读一遍”,推理正确率会明显变高。

原始输入:

1+1等于几?

Re2 之后的输入:

1+1等于几?
Read the question again: 1+1等于几?

4.2.2 Re2Advisor

实现 Re2Advisor 处理逻辑:

/**
 * Re2(Re-Reading)顾问器:自动扩充用户Prompt,提升LLM推理能力
 */
public class Re2Advisor implements CallAdvisor, StreamAdvisor, Ordered {

    // Re2提示词模板(核心:原问题 + 再读一遍原问题)
    private static final String DEFAULT_RE2_TEMPLATE = """
            {user_query}
            Read the question again: {user_query}
            """;

    private final PromptTemplate re2Template;
    // 执行顺序(数值越小,越先执行,保证Re2最先修改Prompt)
    private int order = Ordered.HIGHEST_PRECEDENCE + 10;

    // 无参构造器:使用默认模板
    public Re2Advisor() {
        this.re2Template = new PromptTemplate(DEFAULT_RE2_TEMPLATE);
    }

    // 自定义模板构造器(支持个性化Re2提示)
    public Re2Advisor(String customTemplate) {
        this.re2Template = new PromptTemplate(customTemplate);
    }

    @Override
    public String getName() {
        return "Re2Advisor"; // 唯一名称,便于监控/日志识别
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    // 自定义执行顺序
    public Re2Advisor withOrder(int order) {
        this.order = order;
        return this;
    }

    // ========== 非流式调用(同步) ==========
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain chain) {
        // 1. 应用Re2规则,生成增强后的Prompt
        ChatClientRequest enhancedRequest = enhancePromptWithRe2(chatClientRequest);
        // 2. 调用链的下一个组件(最终调用LLM)
        return chain.nextCall(enhancedRequest);
    }

    // ========== 流式调用(异步) ==========
    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        // 1. 应用Re2规则,生成增强后的Prompt
        ChatClientRequest enhancedRequest = enhancePromptWithRe2(request);
        // 2. 调用链的下一个组件,返回流式响应
        return chain.nextStream(enhancedRequest);
    }

    // ========== 核心:Re2 Prompt增强逻辑 ==========
    private ChatClientRequest enhancePromptWithRe2(ChatClientRequest originalRequest) {
        // 1. 获取用户原始提问内容
        String originalUserText = originalRequest.prompt().getUserMessage().getText();

        // 2. 跳过空内容(防御性编程)
        if (originalUserText == null || originalUserText.isBlank()) {
            return originalRequest;
        }

        // 3. 应用Re2模板,生成增强后的提问内容
        String enhancedUserText = re2Template.create(Map.of("user_query", originalUserText)).getContents();

        // 4. 构建新的请求
        return originalRequest.mutate()
                .prompt(originalRequest.prompt().augmentUserMessage(enhancedUserText))
                .build();
    }
}

4.2.3 测试

运行测试类,并查看控制台:

    @Test
    public void zhiPuAiChatClientTest() {
        String reasoningQuestion = "有3个苹果,小明吃了1个,妈妈又买了2个,现在一共有几个苹果?";

        // 调用ChatClient,Re2Advisor 会自动增强 Prompt
        String response = zhiPuAiChatClient.prompt()
                .user(reasoningQuestion)
                .call()
                .content();

        System.out.println(response);
    }

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐