Spring AI开发跃迁指南(第二章:急速上手3——Advisor核心原理、源码讲解及使用实例)


1.Advisor简介

Spring AI 中的 Advisor 是一种核心机制,用于拦截和增强 AI 应用程序中的请求与响应流。其设计灵感来源于 Spring AOP(面向切面编程)中的切面(Aspect)概念,但专门针对 AI 交互场景进行了优化。

1.1.核心概念

  1. 定义与定位

Advisor 是 Spring AI 中负责动态干预聊天请求和响应流程的组件,通过链式结构(Chain of Responsibility 模式)串联多个处理单元。每个 Advisor 可以修改请求参数、增强数据、拦截敏感操作,甚至中断请求传递。

  1. 与AOP的关系

其功能类似于 Spring 的 AspectJ,但更专注于 AI 交互场景。例如,通过 AroundAdvisor 接口实现请求前后的增强逻辑。

1.2.Advisord的核心特点

  1. 模块化封装
  • 重复任务封装:将生成式 AI 的通用模式(如上下文记忆、敏感词过滤)抽象为可复用的组件。
  • 数据转换:优化发送至语言模型(LLM)的输入数据格式,并处理返回的响应(如结构化输出转换)。
  • 可移植性:同一 Advisor 可适配不同模型(如 OpenAI、HuggingFace)和用例,提升代码灵活性。
  1. 链式处理机制

多个 Advisor 按顺序执行,每个环节可修改请求或响应,并决定是否继续传递。某些 Advisor(如 SafeGuardAdvisor)可能直接中断链式流程。

  1. 内置与扩展性

Spring AI 提供多种内置 Advisor,同时也支持开发者自定义扩展,满足个性化需求。

2.Advisor源码及核心原理

2.1.关键类与关系

实现接口 实现接口 实现接口 依赖 依赖 参数 返回 参数 返回 <泛型元素> 生成 调用链 0..* 0..* 调用链 0..* 0..* <<interface>> Ordered +getOrder() : int <<interface>> Advisor +getName() : String CallAroundAdvisor +aroundCall(AdvisedRequest, CallAroundAdvisorChain) : AdvisedResponse StreamAroundAdvisor +aroundStream(AdvisedRequest, StreamAroundAdvisorChain) : Flux<AdvisedResponse> AdvisedRequest +... mutable Prompt data ... +adviseContext: Map<String, Object> AdvisedResponse +callResponse: ChatResponse +adviseContext: Map<String, Object> CallAroundAdvisorChain +nextAroundCall(AdvisedRequest) : AdvisedResponse StreamAroundAdvisorChain +nextAroundStream(AdvisedRequest) : Flux<AdvisedResponse> <<Reactive Stream>> Flux<AdvisedResponse> + ... 操作流的方法 ...

  1. 核心接口

    • Ordered

      定义 getOrder() 方法,用于控制 Advisor 的执行顺序(类似 Spring 的优先级机制)。

      小order先管请求、后管响应,大order反之;同值顺序随机。

    • Advisor

      基础接口,定义 getName() 方法标识 Advisor 名称。

  2. 具体 Advisor 实现

    • CallAroundAdvisor
      同步调用增强器,通过 aroundCall() 方法拦截请求并返回响应。
    • StreamAroundAdvisor
      流式调用增强器,通过 aroundStream() 方法处理流式响应(返回 Flux<AdvisedResponse>)。
  3. 请求与响应对象

    • AdvisedRequest
      包含可变的 Prompt 数据和上下文信息(adviseContext)。
    • AdvisedResponse
      封装 Chat 响应和上下文信息。
  4. 调用链(Chain)类

    • CallAroundAdvisorChain
      通过 nextAroundCall() 方法传递请求,支持链式调用多个 CallAroundAdvisor
    • StreamAroundAdvisorChain
      类似 CallAroundAdvisorChain,但用于流式场景的链式调用。

2.2.核心交互流程

  1. 请求初始化

    Spring AI框架将用户的输入(Prompt)转换为AdvisedRequest,并创建一个空的共享上下文对象AdvisorContext

  2. Advisor链处理请求

    • 每个Advisor依次处理AdvisedRequest,可以:
      • 修改请求内容(例如调整参数、添加元数据)。
      • 直接拦截请求 ,阻止后续调用,并自行生成响应(AdvisedResponse)。
    • 若未拦截,请求会传递到链中的下一个Advisor
  3. 调用Chat Model

    最后一个框架内置的Advisor负责将处理后的请求发送给Chat Model(如GPT模型)。

  4. 响应逆向传递

    • Chat Model生成的原始响应(ChatResponse)会转换为AdvisedResponse,并携带共享的AdvisorContext
    • 响应沿Advisor链反向传递 ,每个Advisor可对响应进行二次处理或修改(例如过滤敏感内容、格式化输出)。
  5. 响应返回

    最终的AdvisedResponse通过提取ChatCompletion(响应核心内容)返回给客户端。

流程图如下:

flowchart TD subgraph 请求阶段 A[Client Prompt] --> B[生成 AdvisedRequest\n+ AdvisorContext] B --> C[Advisor1] C -->|修改请求| D[Advisor2] D -->|拦截请求| E[生成 AdvisedResponse] D -->|放行| F[调用 Chat Model] end subgraph 响应阶段 F --> G[ChatResponse → AdvisedResponse] G --> H[Advisor2 处理响应] H --> I[Advisor1 处理响应] I --> J[返回最终响应] end E --> J J --> K[Client]

2.3.设计特点

  1. 责任链模式
    通过 AdvisorChain 实现多个 Advisor 的链式调用,支持动态扩展增强逻辑。
  2. 上下文传递
    AdvisedRequestAdvisedResponse 中的 adviseContext 允许在链式调用中共享数据。
  3. 同步与流式分离
    通过 CallAroundAdvisorStreamAroundAdvisor 区分处理普通请求和流式请求。

2.4.Avisor相关源码解析

Advisor 接口位于org.springframework.ai.chat.client.advisor.api包中,是实现自定义Advisor的关键接口:

java 复制代码
public interface Advisor extends Ordered {
	String getName();
}

同步和流式Advisor的两个子接口继承自Advisor:

  • 同步的CallAroundAdvisor
java 复制代码
/**
 * Around advisor that wraps the ChatModel#call(Prompt) method.
 *
 * @author Christian Tzolov
 * @author Dariusz Jedrzejczyk
 * @since 1.0.0
 */

public interface CallAroundAdvisor extends Advisor {

	/**
	 * Around advice that wraps the ChatModel#call(Prompt) method.
	 * @param advisedRequest the advised request
	 * @param chain the advisor chain
	 * @return the response
	 */
	AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);

}
  • 流式的StreamAroundAdvisor:
java 复制代码
/**
 * Around advisor that runs around stream based requests.
 *
 * @author Christian Tzolov
 * @author Dariusz Jedrzejczyk
 * @since 1.0.0
 */
public interface StreamAroundAdvisor extends Advisor {

	/**
	 * Around advice that wraps the invocation of the advised request.
	 * @param advisedRequest the advised request
	 * @param chain the chain of advisors to execute
	 * @return the result of the advised request
	 */
	Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);

}

要使用Advice链,需在你的Advice实现中使用CallAroundAdvisorChainStreamAroundAdvisorChain

java 复制代码
public interface CallAroundAdvisorChain {

	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);

}
java 复制代码
public interface StreamAroundAdvisorChain {

	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);

}

自定义Advisor就需要实现CallAroundAdvisorStreamAroundAdvisor 中的最少一个。其中的关键方法是nextAroundCall()nextAroundStream()

3.Spring AI内置的Advisor

  • MessageChatMemoryAdvisor

    作用:

    责维护对话的上下文记忆,将用户的问题与模型的回答保存到内存中,确保多轮对话的连贯性。例如,在连续对话中,历史记录会被自动附加到新请求中,帮助模型理解上下文。

    核心特点:

    • 上下文增强 :通过 messages 参数传递历史对话记录。
    • 模型兼容性要求 :仅适用于支持 messages 参数的模型(如 OpenAI 的 GPT 系列)。
    • 动态扩展:每次对话自动更新内存中的历史记录。
  • PromptChatMemoryAdvisor

    作用

    MessageChatMemoryAdvisor 类似,但将上下文历史记录封装到 systemPrompt 提示词中,而非直接依赖 messages 参数。这使得无论模型是否支持 messages 参数,都能实现上下文记忆。

    核心特点:

    • 灵活性 :通过修改系统提示词(systemPrompt)嵌入历史对话,兼容性更广。
    • 无侵入式设计:不依赖模型底层接口,仅通过提示词工程实现上下文管理。
  • QuestionAnswerAdvisor

    作用:

    实现检索增强生成(RAG),通过查询向量数据库或知识库获取相关文本片段,并将其附加到用户问题后,提升回答的准确性和相关性。

    核心特点:

    • 知识库集成 :支持自定义向量数据库(如:Milvus、Weaviate、Elasticsearch、FAISS)。
    • 默认拒绝机制:若知识库无匹配内容,则拒绝回答用户问题,避免生成错误信息。
    • 提示词优化:内置默认提示词模板,确保回答与检索内容强相关。
  • SafeGuardAdvisor

    作用:

    敏感词过滤与安全拦截,防止用户输入或模型输出包含违规内容。当检测到敏感词时,直接中断请求链,避免调用大模型处理。

    核心特点:

    • 实时拦截:基于预定义规则或动态词库进行校验。
    • 链式中断:直接终止后续处理,降低资源消耗与安全风险。
  • VectorStoreChatMemoryAdvisor

    作用:

    将对话历史长期存储到向量数据库中,并在每次提问时检索相关历史记录,增强上下文提示的准确性和长期记忆能力。

    核心特点:

    • 向量化存储:利用向量数据库高效检索相似历史对话片段。
    • 关键参数管理 :依赖 chat_memory_conversation_id 标识用户会话,避免数据冗余。
    • 数据清理机制:需定期清理旧数据,防止数据库膨胀。
  • SimpleLoggerAdvisor

    作用:

    记录请求与响应的日志信息,便于调试和监控 AI 交互流程。例如,可打印用户输入、模型输出及处理耗时。

    核心特点:

    • 轻量级工具:无需复杂配置,快速集成日志功能。
    • 可扩展性:支持自定义日志格式与存储方式。

4.自定义Advisor实现

4.1.自定义日志Advisor

我们自定义一个日志Advisor,在调用链中的下一个顾问之前记录AdvisedRequest,之后记录AdvisedResponse

此advisor只观察请求和响应,不做任何膝盖且同时支持非流和流场景。

java 复制代码
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

	private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

    // 为Advisor提供一个唯一的名称
	@Override
	public String getName() {
		return this.getClass().getSimpleName();
	}

    // 设置order值来控制执行顺序,值较小的将优先执行
	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);

		logger.debug("AFTER: {}", advisedResponse);

		return advisedResponse;
	}

	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
		
        // MessageAggregator是一个实用程序类,它将Flux响应聚合到单个AdvisedResponse中,
        // MessageAggregator为只读,不可更改其中的响应
        return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
                    advisedResponse -> logger.debug("AFTER: {}", advisedResponse));
	}
}

4.2.Re2 Advisor

"重读提升大型语言模型的推理能力"一文介绍了一种名为重读(Re2)的技术,它可以提升大型语言模型的推理能力。Re2 技术需要像这样扩充输入提示:

复制代码
{输入查询}
再次阅读问题:{Input_Query}

实现Re2技术的Advisor:

java 复制代码
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

	// 应用Re2增强用户的输入查询
	private AdvisedRequest before(AdvisedRequest advisedRequest) {

		Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
		advisedUserParams.put("re2_input_query", advisedRequest.userText());

		return AdvisedRequest.from(advisedRequest)
			.userText("""
			    {re2_input_query}
			    Read the question again: {re2_input_query}
			    """)
			.userParams(advisedUserParams)
			.build();
	}

    // 拦截非流式请求并应用Re2
	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
		return chain.nextAroundCall(this.before(advisedRequest));
	}

    // 拦截流试请求并应用Re2技术
	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
		return chain.nextAroundStream(this.before(advisedRequest));
	}

	@Override
	public int getOrder() {
		return 0;
	}

    @Override
    public String getName() {
		return this.getClass().getSimpleName();
	}
}

5.Advisor使用示例

我们以MessageChatMemoryAdvisor为例做一个advisor的使用实例,主要在ChatClient.builder中使用defaultAdvisors()方法配置Advisor,此方法可配置多个Advisor。

java 复制代码
Builder defaultAdvisors(Advisor... advisor);

Builder defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer);

Builder defaultAdvisors(List<Advisor> advisors);

示例主要有controller、service及config配置 ,相关的pom依赖主要使用spring-ai-starter-model-minimax包等,具体的pom及minimax模型 的配置请参考:Spring AI开发跃迁指南(第二章:极速上手------ChatClient20行代码构建人工智能应用)

5.1.示例代码

  • ChatConfig
java 复制代码
package com.common;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfig {
    @Bean
    public ChatMemory chatMemory() {
        return new InMemoryChatMemory(); // 简单内存实现
    }

    // 2. 创建 MessageChatMemoryAdvisor
    @Bean
    public MessageChatMemoryAdvisor chatMemoryAdvisor(ChatMemory chatMemory) {
        return new MessageChatMemoryAdvisor(chatMemory);
    }

    // 配置 ChatClient,绑定 Advisor
    @Bean
    public ChatClient chatClient(ChatModel chatModel, MessageChatMemoryAdvisor advisor) {
        return ChatClient.builder(chatModel)
                .defaultAdvisors(advisor) // 添加 Advisor
                .build();
    }
}
  • service
java 复制代码
@Service
public class TestSpringAIServiceImpl {
    @Autowired
    private ChatClient chatClient;

    @Override
    public String generate(String message) {
        return chatClient.prompt()
                .advisors(advisorSpec -> advisorSpec
                                .param("chat_memory_conversation_id", "111")
                                .param("chat_memory_response_size", 100))
                .user(message)
                .call()
                .content();
    }
}	
  • controller
java 复制代码
@RestController
public class TestSpringAIController {
    @Autowired
    private TestSpringAIServiceImpl service;  
    
	@GetMapping("/ai/generate")
    public String generate(@RequestParam(value = "message", defaultValue = "生成一个中国女演员及其电影作品") String message) {
        return service.generate(message);
    }
}
  • 运行结果
  1. 第一次运行结果:

  2. 第二次运行结果:

  3. 执行结果分析

  • 第一次对话 :声明自己的名字为"张三",MessageChatMemoryAdvisor 会将此对话记录(用户输入和模型回复)保存到内存中。
  • 第二次对话 :用户提问"我的名字是什么?",模型会根据 MessageChatMemoryAdvisor 提供的上下文历史(包含第一次对话)生成正确回答。

5.2.关键机制解释

  1. 内存管理
    InMemoryChatMemory 默认以 List<Message> 形式存储对话历史,每次调用 chatClient.call() 时,MessageChatMemoryAdvisor 会自动将历史记录附加到当前请求中(通过 messages 参数传递)。

  2. 上下文传递

    模型(如 OpenAI GPT)接收到完整的 messages 列表后,会自动解析历史对话,确保回答的连贯性。

  3. 会话隔离

    若需区分不同用户的对话,需通过 ChatMemoryconversationId 参数管理会话标识:

    java 复制代码
    // 为不同用户分配唯一会话 ID
    ChatMemory memory = new InMemoryChatMemory("user-123"); 

若需自定义历史记录的条数或存储策略:

java 复制代码
@Bean
public ChatMemory chatMemory() {
    InMemoryChatMemory memory = new InMemoryChatMemory();
    memory.setMaxHistorySize(10); // 限制最多保留 10 条历史记录
    return memory;
}

注意:

  • 模型兼容性 :确保底层模型(如 OpenAI、MiniMax)支持 messages 参数格式。
  • 内存泄漏风险 :若使用 InMemoryChatMemory,需在长期运行的应用中定期清理过期会话。
相关推荐
武昌库里写JAVA31 分钟前
Java 设计模式
java·vue.js·spring boot·课程设计·宠物管理
IT古董33 分钟前
【漫话机器学习系列】234.阈值类特征的方差分析(Thresholding Categorical Feature Variance)
人工智能·python·机器学习
钢铁男儿39 分钟前
Python 函数装饰器和闭包(闭包)
java·网络·python
多巴胺与内啡肽.1 小时前
OpenCV进阶操作:图像直方图、直方图均衡化
人工智能·opencv·计算机视觉
白熊1881 小时前
【计算机视觉】3d人脸重建:3DDFA_V2:实时高精度3D人脸重建与密集对齐技术指南
人工智能·计算机视觉·3d
Clf丶忆笙1 小时前
从零开始搭建第一个Spring Boot应用:从入门到精通
java·spring boot
东坡大表哥1 小时前
【Android】Android签名解析
android·java
phoenix@Capricornus1 小时前
神经网络发展的时间线——积跬步至千里
人工智能·深度学习·神经网络
程序员buddha1 小时前
使用 IDEA + Maven 搭建传统 Spring MVC + Thymeleaf 项目的详细步骤
spring·maven·intellij-idea
杨不易呀1 小时前
Java面试:微服务与大数据场景下的技术挑战
java·大数据·微服务·面试·技术栈