Spring Boot3 集成 Spring AI 实现 Advisor 增强机制的完整流程
作者:拾荒的小海螺
1、简述
Spring AI 的 Advisor API 是一种声明式的拦截机制,借鉴了 Spring AOP 的设计理念,允许开发者在 AI 交互的生命周期关键节点插入自定义逻辑。
2、实现原理
2.1 核心概念
Advisor(顾问)本质上是围绕 AI 模型调用的拦截器,在用户发送问题之后、调用大模型之前执行一系列增强操作。
┌─────────────────────────────────────────────────────────────────┐ │ Advisor Chain 执行流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 用户请求 ──→ Advisor 1 ──→ Advisor 2 ──→ ... ──→ LLM │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ 请求预处理 请求预处理 模型调用 │ │ │ │ 用户响应 ←── Advisor 1 ←── Advisor 2 ←── ... ←── LLM │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ 响应后处理 响应后处理 原始响应 │ │ │ └─────────────────────────────────────────────────────────────────┘
2.2 核心价值
| 优势 | 说明 |
|---|---|
| 非侵入式增强 | 无需修改核心业务代码即可添加功能 |
| 关注点分离 | 将横切关注点(日志、安全、缓存)与业务逻辑解耦 |
| 可复用性 | 同一 Advisor 可在不同 ChatClient 间复用 |
| 组合性 | 多个 Advisor 可灵活组合形成拦截链 |
2.3 执行顺序控制
Advisor 链的执行顺序通过 getOrder() 方法控制,值越小优先级越高(越先执行):
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; // 最高优先级
int LOWEST_PRECEDENCE = Integer.MAX_VALUE; // 最低优先级
int getOrder();
}
关键理解:由于 Advisor 链的堆栈特性,优先级最高的 Advisor 最先处理请求,最后处理响应。
3、环境准备
3.1 Maven 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI OpenAI Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 添加Jackson支持JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>3.2 基础配置
spring:
application:
name: lm-advisor
# DeepSeek API 配置 (兼容 OpenAI API)
ai:
openai:
# DeepSeek API Base URL
base-url: https://api.deepseek.com
# DeepSeek API Key (请替换为你的实际API Key)
api-key: ${DEEPSEEK_API_KEY:you-key}
# 使用的模型
chat:
options:
model: deepseek-chat
temperature: 0.7
max-tokens: 20003.3 ChatClient 配置
package com.example.demo.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
public static String CONVERSATION_ID = "conversation_id";
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
// 可在此配置默认系统提示词
return builder
.defaultSystem("你是一个专业的AI助手,请用中文回答用户问题。")
.build();
}
}4、内置 Advisor 详解
Spring AI 1.0.0 提供了多个开箱即用的内置 Advisor。
4.1 对话记忆 Advisor
MessageChatMemoryAdvisor
将历史消息直接添加到请求的 messages 列表中,适用于支持结构化消息的 Chat 模型(如 OpenAI GPT 系列)。
package com.example.demo.service;
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.MessageWindowChatMemory;
import org.springframework.stereotype.Service;
@Service
public class MemoryConversationService {
private final String CONVERSATION_ID = "conversation_id";
private final ChatClient chatClient;
public MemoryConversationService(ChatClient.Builder builder) {
// 创建聊天记忆存储(内存窗口模式,保留最近20条消息)
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
// 创建 MessageChatMemoryAdvisor
MessageChatMemoryAdvisor memoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId("default-session")
.build();
this.chatClient = builder
.defaultAdvisors(memoryAdvisor)
.build();
}
/**
* 多轮对话 - 自动保持上下文
* @param conversationId 会话ID(首次调用可传null)
* @param message 用户消息
*/
public String chatWithMemory(String conversationId, String message) {
String convId = conversationId != null ? conversationId : "default-session";
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(CONVERSATION_ID, convId))
.call()
.content();
}
}PromptChatMemoryAdvisor
将历史消息嵌入到系统提示词(System Prompt)中,适用于不支持结构化消息的文本模型(如本地部署的 LLaMA、BLOOM 等)。
import com.lm.advisor.config.AiConfig;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.stereotype.Service;
@Service
public class PromptMemoryConversationService {
private final ChatClient chatClient;
public PromptMemoryConversationService(ChatClient.Builder builder) {
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
// 创建 PromptChatMemoryAdvisor
PromptChatMemoryAdvisor memoryAdvisor = PromptChatMemoryAdvisor.builder(chatMemory)
.conversationId("default-session")
.build();
this.chatClient = builder
.defaultAdvisors(memoryAdvisor)
.build();
}
public String chatWithMemory(String conversationId, String message) {
String convId = conversationId != null ? conversationId : "default-session";
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(AiConfig.CONVERSATION_ID, convId))
.call()
.content();
}
}4.2 敏感词过滤 Advisor(SafeGuardAdvisor)
在用户输入中检测敏感词,发现敏感词时阻止调用模型并返回预设响应。
package com.example.demo.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SafeGuardService {
private final ChatClient chatClient;
public SafeGuardService(ChatClient.Builder builder) {
// 配置敏感词列表
List<String> sensitiveWords = List.of(
"机密", "绝密", "内部资料",
"confidential", "secret", "classified"
);
// 创建 SafeGuardAdvisor
SafeGuardAdvisor safeGuardAdvisor = SafeGuardAdvisor.builder()
.sensitiveWords(sensitiveWords)
.failureResponse("检测到敏感词,请修改您的输入后重试")
.build();
this.chatClient = builder
.defaultAdvisors(safeGuardAdvisor)
.build();
}
public String safeChat(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call()
.content();
}
}4.3 RAG Advisor(QuestionAnswerAdvisor)
从向量数据库中检索相关文档,增强上下文后传递给 LLM。
package com.example.demo.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
@Service
public class RagService {
private final ChatClient chatClient;
public RagService(ChatClient.Builder builder, VectorStore vectorStore) {
// 配置 RAG Advisor
QuestionAnswerAdvisor ragAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.75) // 相似度阈值
.topK(5) // 返回文档数量
.build())
.build();
this.chatClient = builder
.defaultAdvisors(ragAdvisor)
.build();
}
/**
* 基于知识库的问答
*/
public String askWithRag(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
/**
* 动态过滤表达式 - 只检索特定类型的文档
*/
public String askWithFilter(String question, String filterExpression) {
return chatClient.prompt()
.user(question)
.advisors(advisor -> advisor
.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, filterExpression))
.call()
.content();
}
}4.4 日志记录 Advisor(SimpleLoggerAdvisor)
打印请求和响应信息,默认 JSON 格式化输出。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.stereotype.Service;
@Service
public class LoggingService {
private final ChatClient chatClient;
public LoggingService(ChatClient.Builder builder) {
// 使用内置的 SimpleLoggerAdvisor
this.chatClient = builder
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}5、自定义 Advisor 开发
5.1 继承 BaseAdvisor(推荐方式)
Spring AI 1.0.0 推荐继承 BaseAdvisor 抽象类,只需实现 before() 和 after() 方法。
package com.example.demo.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.core.Ordered;
@Slf4j
public class PerformanceLoggingAdvisor implements BaseAdvisor, Ordered {
@Override
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
// 请求前处理:记录开始时间
long startTime = System.currentTimeMillis();
// 将开始时间存入上下文,供 after 方法使用
request.context().put("startTime", startTime);
log.info("=== AI 请求开始 ===");
log.info("用户消息: {}", request.prompt().getUserMessage().getText());
log.info("系统消息: {}", request.prompt().getSystemMessage().getText());
return request;
}
@Override
public ChatClientResponse after(ChatClientResponse response, AdvisorChain chain) {
// 响应后处理:计算耗时
long startTime = (long) response.context().getOrDefault("startTime", System.currentTimeMillis());
long duration = System.currentTimeMillis() - startTime;
String content = response.chatResponse().getResult().getOutput().getText();
log.info("=== AI 响应完成 ===");
log.info("响应内容: {}", content != null && content.length() > 100
? content.substring(0, 100) + "..." : content);
log.info("耗时: {}ms", duration);
return response;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 100;
}
}5.2 敏感词拦截 Advisor(拦截式)
继承 BaseAdvisor 并重写 before(),在检测到敏感词时提前终止链调用。
package com.example.demo.advisor;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.core.Ordered;
import java.util.List;
public class SensitiveWordInterceptorAdvisor implements BaseAdvisor, Ordered {
private final List<String> sensitiveWords;
private final String blockMessage;
public SensitiveWordInterceptorAdvisor(List<String> sensitiveWords) {
this(sensitiveWords, "您的输入包含敏感词,请修改后重试");
}
public SensitiveWordInterceptorAdvisor(List<String> sensitiveWords, String blockMessage) {
this.sensitiveWords = sensitiveWords;
this.blockMessage = blockMessage;
}
@Override
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
String userText = request.prompt().getUserMessage().getText();
String detectedWord = detectSensitiveWord(userText);
if (detectedWord != null) {
// 检测到敏感词,设置阻断标记
request.context().put("blocked", true);
request.context().put("blockReason", "敏感词: " + detectedWord);
}
return request;
}
@Override
public ChatClientResponse after(ChatClientResponse response, AdvisorChain chain) {
// 检查是否被阻断
if (Boolean.TRUE.equals(response.context().get("blocked"))) {
// 返回预设的阻断响应
List<Generation> generations = new java.util.ArrayList<>();
generations.add(new Generation(new AssistantMessage(blockMessage)));
ChatResponse blockResponse = new ChatResponse(generations);
return new ChatClientResponse(blockResponse, response.context());
}
return response;
}
private String detectSensitiveWord(String text) {
if (text == null) return null;
for (String word : sensitiveWords) {
if (text.contains(word)) {
return word;
}
}
return null;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 50;
}
}5.3 Re-Reading 增强 Advisor
基于 Re-Reading(Re2)技术,通过重复阅读问题来提升理解准确率。
package com.example.demo.advisor;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.core.Ordered;
import java.util.HashMap;
import java.util.Map;
/**
* Re-Reading 增强 Advisor
* 通过让模型重复阅读问题来提升理解准确率
* 基于 Re2 技术:https://arxiv.org/abs/2309.06275
*/
public class ReReadingAdvisor implements BaseAdvisor, Ordered {
private static final String RE_READ_TEMPLATE = """
{re2_input_query}
Read the question again: {re2_input_query}
""";
@Override
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
// 获取原始用户消息
String inputQuery = request.prompt().getUserMessage().getText();
// 构建增强后的用户消息参数
Map<String, Object> params = new HashMap<>(request.prompt().getUserMessage().getMetadata());
params.put("re2_input_query", inputQuery);
// 创建增强后的请求
return request.copy()
.mutate()
.context(params)
.build();
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return null;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}6、Advisor 链配置与组合
6.1 组合多个 Advisor
package com.example.demo.config;
import com.lm.advisor.advisor.PerformanceLoggingAdvisor;
import com.lm.advisor.advisor.ReReadingAdvisor;
import com.lm.advisor.advisor.SensitiveWordInterceptorAdvisor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class AdvisorChainConfig {
@Bean
public ChatClient chatClientWithAdvisors(
ChatClient.Builder builder,
VectorStore vectorStore) {
// 创建各个 Advisor
SensitiveWordInterceptorAdvisor sensitiveWordAdvisor =
new SensitiveWordInterceptorAdvisor(List.of("敏感词1", "敏感词2"));
PerformanceLoggingAdvisor loggingAdvisor = new PerformanceLoggingAdvisor();
MessageChatMemoryAdvisor memoryAdvisor = MessageChatMemoryAdvisor.builder(
MessageWindowChatMemory.builder().maxMessages(20).build())
.conversationId("default")
.build();
QuestionAnswerAdvisor ragAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.build();
ReReadingAdvisor reReadingAdvisor = new ReReadingAdvisor();
// 按顺序配置 Advisor 链
// 执行顺序:敏感词过滤 -> 日志记录 -> 对话记忆 -> RAG -> Re-Reading
return builder
.defaultAdvisors(
sensitiveWordAdvisor,
loggingAdvisor,
memoryAdvisor,
ragAdvisor,
reReadingAdvisor
)
.defaultSystem("你是一个乐于助人的 AI 助手,请用中文回答用户问题。")
.build();
}
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
// 使用内存向量存储,适合开发和测试环境
return SimpleVectorStore.builder(embeddingModel)
.build();
}
}6.2 运行时动态添加 Advisor
import com.lm.advisor.config.AiConfig;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.stereotype.Service;
@Service
public class DynamicAdvisorService {
private final ChatClient chatClient;
public DynamicAdvisorService(ChatClient.Builder builder) {
// 基础配置,不预设 Advisor
this.chatClient = builder.build();
}
/**
* 运行时根据场景动态添加 Advisor
*/
public String chatWithRuntimeAdvisors(String question, String conversationId) {
return chatClient.prompt()
.user(question)
// 动态添加对话记忆 Advisor
.advisors(advisor -> advisor
.param(AiConfig.CONVERSATION_ID, conversationId))
// 动态添加 RAG Advisor
.advisors(advisor -> advisor
.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'knowledge'"))
.call()
.content();
}
}7、常见问题与最佳实践
7.1 MessageChatMemoryAdvisor 与 PromptChatMemoryAdvisor 的区别
| 特性 | MessageChatMemoryAdvisor | PromptChatMemoryAdvisor |
|---|---|---|
| 消息存储方式 | 直接添加到 messages 列表 | 嵌入到系统提示词 |
| 适用模型 | OpenAI GPT 等 Chat 模型 | LLaMA、BLOOM 等文本模型 |
| 优点 | 精确控制消息类型(用户、系统、助手) | 通用性更强,不依赖模型能力 |
| 缺点 | 依赖模型支持 messages 参数 | 可能增加 token 消耗 |
7.2 为什么需要覆盖 adviseStream 方法
在流式响应场景中,多个流式响应块需要合并成一个完整的响应对象后,再调用 after() 方法,确保只保留完整的模型输出,避免部分信息写入 memory 导致数据混乱。
7.3 性能优化建议
// 1. 避免在 Advisor 中进行重量级操作
public class LightweightAdvisor extends BaseAdvisor {
@Override
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
// 快速检查,避免阻塞
if (skipCondition()) {
return request;
}
// 异步处理非关键逻辑
CompletableFuture.runAsync(() -> asyncProcess(request));
return request;
}
}
// 2. 合理设置执行顺序
// 敏感词过滤应最先执行(优先级最高)
// 日志记录应在中间执行
// Re-Reading 应在最后执行7.4 注意事项
| 注意事项 | 说明 |
|---|---|
| 会话 ID 管理 | 必须妥善维护 chat_memory_conversation_id,避免每次默认生成新 ID 导致垃圾数据 |
| 敏感数据 | 启用 prompt/completion 日志记录时,存在暴露敏感信息的风险 |
| 流式处理线程 | BaseAdvisor 默认使用 Schedulers.boundedElastic() 进行流式处理线程调度 |
| 顺序敏感 | 部分 Advisor(如 SafeGuardAdvisor)需要放在链的最前面,才能在早期拦截请求 |
8、总结
本文全面介绍了 Spring Boot 集成 Spring AI 1.0.0 实现 Advisor 增强机制的完整流程,涵盖以下核心内容:
| 章节 | 核心知识点 |
|---|---|
| 基础概念 | Advisor 拦截机制、核心接口、执行顺序 |
| 环境配置 | Maven 依赖、配置文件设置 |
| 内置 Advisor | 对话记忆(两种实现)、敏感词过滤、RAG、日志记录 |
| 自定义开发 | 继承 BaseAdvisor、性能日志、敏感词拦截、Re-Reading 增强 |
| 链式组合 | 多 Advisor 组合、运行时动态添加 |
| 可观测性 | Micrometer 指标、OpenTelemetry 追踪 |
| 最佳实践 | 性能优化、注意事项、常见问题 |
Spring AI 1.0.0 的 Advisor 机制为构建生产级 AI 应用提供了强大的扩展能力。通过合理设计和组合 Advisor,可以实现:
- 非侵入式增强:无需修改核心业务代码即可添加功能
- 关注点分离:将横切关注点(日志、安全、缓存)与业务逻辑解耦
- 可复用性:同一 Advisor 可在不同 ChatClient 间复用
- 可观测性:完整的指标收集和链路追踪能力
到此这篇关于Spring Boot3 集成 Spring AI 实现 Advisor 增强机制的完整流程的文章就介绍到这了,更多相关Spring Boot 集成 Spring AI Advisor 增强内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- LangChain4j 集成 SpringBoot的项目实践
- springboot3.x版本集成log4j遇到Logging system failed to initialize using configuration from‘classpath:log4问题
- springboot集成nacos报错:get data from Nacos error,dataId:null.yaml的原因及解决方法
- Java SpringBoot集成ChatGPT实现AI聊天
- springBoot集成Elasticsearch 报错 Health check failed的解决
- SpringBoot集成E-mail发送各种类型邮件
- Springboot源码 AbstractAdvisorAutoProxyCreator解析
