SpringAI实战链接
4.SpringAI实现AI应用-使用redis持久化聊天记忆-CSDN博客
5.SpringAI实现AI应用-自定义顾问(Advisor)-CSDN博客
概述
通过前两篇帖子,对SpringAI的使用应该有了大致的了解,那么本篇就需要真正去看以下SpringAI里面真正的东西了
首先先看SpringAI官方文档-Advisors
中文版:顾问 API :: Spring AI 参考 - Spring 框架
官方:Advisors API :: Spring AI Reference
通过文档可以知道Spring AI 框架提供了几个内置顾问,以增强 AI 交互。以下是可用顾问的概览:
聊天记忆顾问
这些顾问在聊天记忆存储中管理对话历史:
MessageChatMemoryAdvisor
检索记忆并将其作为消息集合添加到提示中。这种方法保持了对话历史的结构。请注意,并非所有 AI 模型都支持这种方法。
PromptChatMemoryAdvisor
检索记忆并将其并入提示的系统文本中。
VectorStoreChatMemoryAdvisor
从 VectorStore 中检索记忆并将其添加到提示的系统文本中。这个顾问对于高效搜索和检索大型数据集中的相关信息非常有用。
问题回答顾问
QuestionAnswerAdvisor
这个顾问使用向量存储提供问题回答能力,实现了 RAG(检索增强生成)模式。
内容安全顾问
SafeGuardAdvisor
一个简单的顾问,旨在防止模型生成有害或不适当的内容。
聊天记忆实例
接下来继续使用之前的代码,逐个验证这些内置顾问的功能和作用
ChatMemory
想要使用聊天记忆顾问之前,先看一下聊天记忆顾问里的源码
MessageChatMemoryAdvisor

PromptChatMemoryAdvisor

VectorStoreChatMemoryAdvisor

从源码中可以看到,两个内置顾问的构造方法中都有ChatMemory ,而VectorStoreChatMemoryAdvisor的构造方法入参的是向量库,那就先看一下ChatMemory,它是一个内存管理容器,用于存储和管理多轮对话中的ChatMessage。它不仅允许开发者保存消息,还提供了消息驱逐策略(Eviction Policy)、持久化存储(Persistence)以及特殊消息处理(如SystemMessage和ToolExecutionMessage)等功能。此外,ChatMemory还与high-level组件(如AI服务)集成,便于开发者更方便地管理对话历史。
先看一下ChatMemory里的方法

1.add(String conversationId, Message message):将单个消息添加到指定会话的对话中。
2.add(String conversationId, List<Message> messages):将消息列表添加到指定会话的对话中。
3.get(String conversationId, int lastN):从指定会话的对话中检索最新的N条消息。
4.clear(String conversationId):清除指定会话的对话历史记录。
而ChatMemory的实现类是InMemoryChatMemory这个类再之前两篇帖子的配置类中有实现。

InMemoryChatMemory是Spring AI框架提供的一种ChatMemory实现,它将对话历史记录存储在内存中。这种实现方式具有快速访问和检索消息的优点,适用于快速原型开发和测试场景。由于内存是易失性存储介质,因此InMemoryChatMemory不适用于需要长期保存聊天记录的应用场景。
而其中的方法也为能持久化聊天记录提供了帮助,
修改之前接口代码中的call方法如下(简单实现)
java
/**
* 根据消息直接输出回答
* @param map
* @return
*/
@PostMapping("/ai/call")
public String call(@RequestBody Map<String,String> map) {
String message = map.get("message");
Message message1 = new UserMessage(message);
String trim = chatClient.prompt().user(message).call().content().trim();
Message message2 = new UserMessage(MessageType.SYSTEM, trim, new ArrayList<>(), Map.of());
inMemoryChatMemory.add("123456",List.of(message1,message2));
return trim;
}
然后再获取聊天记录进行(简单实现)
java
/**
* 查询聊天记里
* @return
*/
@GetMapping("/ai/chatMemory")
public List<Message> chatMemory(){
List<Message> messages = inMemoryChatMemory.get("123456", 10);
for (Message message : messages) {
System.out.println(message.getText());
}
return messages;
}
MessageChatMemoryAdvisor
了解完ChatMemory,那就开始看第一个内置顾问MessageChatMemoryAdvisor,这个也在之前的代码中实现过,当时为了简单实现多轮对话记忆,而且还是加在了方法上

这篇帖子,我们将代码加在配置类上,修改配置类如下,配置类中没有配置会话id和储存大小,是因为统一进行的配置,这种方式将chatClient注册到spring容器的时候,还没有办法知道用户的会话id,所以只能默认,储存大小也使用默认,默认大小为100
java
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")
.defaultAdvisors(new MessageChatMemoryAdvisor(inMemoryChatMemory()))
.build();
}

此时所有的接口都实现了记忆对话的功能
测试


由此可见在全局都已经实现了对话记忆
PromptChatMemoryAdvisor
再来看PromptChatMemoryAdvisor ,它和MessageChatMemoryAdvisor的类似,也是直接修改配置类,如下
java
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")
.defaultAdvisors(
// new MessageChatMemoryAdvisor(inMemoryChatMemory())
new PromptChatMemoryAdvisor(inMemoryChatMemory())
)
.build();
}

VectorStoreChatMemoryAdvisor
VectorStoreChatMemoryAdvisor是 Spring AI 框架中的一个组件,它结合了向量存储(VectorStore)技术来增强聊天机器人的记忆能力。这个 Advisor 利用向量数据库存储用户与聊天机器人的交互历史,包括用户提出的问题和模型的回答。在生成新的回复时,VectorStoreChatMemoryAdvisor 会检索与当前问题相关的历史记录,并将其作为上下文信息添加到提示中,从而帮助大型语言模型(LLM)生成更连贯和准确的回复。
具体实现需要将聊天记录持久化到向量库,下篇帖子再详细说明聊天记录持久化的问题
区别
看了SpringAI的官方文档,网上也搜了一下这三个内置顾问,都说的太官方了,简单说一下:
**MessageChatMemoryAdvisor:**将对话历史以消息集合的形式存储和传递。它维护了一个结构化的对话历史记录,通常将用户消息和模型响应封装为消息对象。适用于需要保持对话历史结构的场景,例如需要明确区分用户消息和系统消息。
**PromptChatMemoryAdvisor:**将对话历史以纯文本的形式合并到系统提示中。它将历史消息转换为一个字符串,并将其附加到系统提示的文本中。适用于需要将历史信息作为上下文传递给模型的场景,尤其是当模型不支持消息集合时。
**VectorStoreChatMemoryAdvisor:**使用向量存储技术将对话历史存储在向量数据库中。它将对话记录封装为向量形式的文档,并通过向量检索来获取相关的历史信息。适用于需要高效检索和处理大规模数据集的场景。
问题回答实例
QuestionAnswerAdvisor
这个顾问访问的就是上篇帖子实现的向量库,当用户提出问题时,QuestionAnswerAdvisor会首先对知识库进行检索,并将匹配到的相关引用文本添加到用户提问的后面,从而为生成的回答提供更为丰富和准确的上下文。此外,该Advisor还设定了一个默认提示词,旨在确保回答的质量和相关性。如果在知识库中无法找到匹配的文本,系统将可能拒绝回答用户的问题。
实现如下:
java
@Bean
ChatClient chatClient(ChatClient.Builder builder,VectorStore vectorStore) {
return builder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")
.defaultAdvisors(
// new MessageChatMemoryAdvisor(inMemoryChatMemory())
new PromptChatMemoryAdvisor(inMemoryChatMemory()),
QuestionAnswerAdvisor.builder(vectorStore).order(1).build()
)
.build();
}

测试


经过测试可以看到,只有问向量库中的内容,才会进行回答
内容安全实例
SafeGuardAdvisor
这个顾问顾名思义就是过滤敏感词汇的,具体实现如下
java
@Bean
ChatClient chatClient(ChatClient.Builder builder,VectorStore vectorStore) {
return builder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")
.defaultAdvisors(
// new MessageChatMemoryAdvisor(inMemoryChatMemory())
new PromptChatMemoryAdvisor(inMemoryChatMemory()),
QuestionAnswerAdvisor.builder(vectorStore).order(1).build(),
SafeGuardAdvisor.builder().sensitiveWords(List.of("色情", "暴力")) // 敏感词列表
.order(2) // 设置优先级
.failureResponse("抱歉,我无法回答这个问题。").build() // 敏感词过滤失败时的响应
)
.build();
}

测试

经过测试,安全顾问已经把敏感词进行了过滤
完整代码
AiConfig(配置文件)
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.*;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @Author majinzhong
* @Date 2025/4/28 10:34
* @Version 1.0
*/
@Configuration
public class AiConfig {
@Bean
ChatClient chatClient(ChatClient.Builder builder,VectorStore vectorStore) {
return builder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")
//这里可以添加多个顾问 order(优先级)越小,越先执行
// 注意:顾问添加到链中的顺序至关重要,因为它决定了其执行的顺序。每个顾问都会以某种方式修改提示或上下文,一个顾问所做的更改会传递给链中的下一个顾问。
// 在此配置中,将首先执行MessageChatMemoryAdvisor,将对话历史记录添加到提示中。然后,问答顾问将根据用户的问题和添加的对话历史进行搜索,从而可能提供更相关的结果。
.defaultAdvisors(
//内存存储对话记忆
// new MessageChatMemoryAdvisor(inMemoryChatMemory()),
new PromptChatMemoryAdvisor(inMemoryChatMemory()),
// QuestionAnswerAdvisor 此顾问使用矢量存储提供问答功能,实现RAG(检索增强生成)模式
QuestionAnswerAdvisor.builder(vectorStore).order(1).build(),
// SafeGuardAdvisor是一个安全防护顾问,它确保生成的内容符合道德和法律标准。
SafeGuardAdvisor.builder().sensitiveWords(List.of("色情", "暴力")) // 敏感词列表
.order(2) // 设置优先级
.failureResponse("抱歉,我无法回答这个问题。").build(), // 敏感词过滤失败时的响应
// SimpleLoggerAdvisor是一个记录ChatClient的请求和响应数据的顾问。这对于调试和监控您的AI交互非常有用,建议将其添加到链的末尾。
new SimpleLoggerAdvisor()
)
.defaultOptions(ChatOptions.builder()
.topP(0.7) // 取值越大,生成的随机性越高;取值越低,生成的随机性越低。默认值为0.8
.build())
.build();
}
@Bean
ChatMemory inMemoryChatMemory() {
return new InMemoryChatMemory();
}
}
AdvisorController(测试接口)
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* @Author majinzhong
* @Date 2025/5/6 14:22
* @Version 1.0
*/
@CrossOrigin
@RestController
public class AdvisorController {
// 负责处理OpenAI的bean,所需参数来自properties文件
private final ChatClient chatClient;
//对话记忆
private final InMemoryChatMemory inMemoryChatMemory;
public AdvisorController(ChatClient chatClient,InMemoryChatMemory inMemoryChatMemory) {
this.chatClient = chatClient;
this.inMemoryChatMemory = inMemoryChatMemory;
}
/**
* 普通聊天
* @param message
* @param sessionId
* @return
*/
@GetMapping("/ai/generateCall")
public String generateCall(@RequestParam(value = "message", defaultValue = "讲个笑话") String message, @RequestParam String sessionId) {
return chatClient.prompt().user(message)
.advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call().content().trim();
}
/**
* 根据消息直接输出回答
* @param map
* @return
*/
@PostMapping("/ai/callAdvisor")
public String call(@RequestBody Map<String,String> map) {
String message = map.get("message");
Message message1 = new UserMessage(message);
String trim = chatClient.prompt().user(message).call().content().trim();
Message message2 = new UserMessage(MessageType.SYSTEM, trim, new ArrayList<>(), Map.of());
inMemoryChatMemory.add("123456",List.of(message1,message2));
return trim;
}
/**
* 查询聊天记里
* @return
*/
@GetMapping("/ai/chatMemory")
public List<Message> chatMemory(){
List<Message> messages = inMemoryChatMemory.get("123456", 10);
for (Message message : messages) {
System.out.println(message.getText());
}
return messages;
}
}
代码中的这个方法设置了记忆对话的对话id和存储的大小
