MessageChatMemoryAdvisor 类用于管理 AI 对话历史(聊天记忆)的组件,它结合了 ChatMemory 和 Advisor,帮助在对话式 AI 应用中维护上下文。
该类主要负责:
自动管理对话历史:在多次交互中保留用户和 AI 的消息记录。
可配置的记忆策略:支持限制记忆的消息数量。
MessageChatMemoryAdvisor 类没有提供公开的构造方法,它的构造方法是 private 私有的,源码如下:
private MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int order, Scheduler scheduler) { Assert.notNull(chatMemory, "chatMemory cannot be null"); Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty"); Assert.notNull(scheduler, "scheduler cannot be null"); this.chatMemory = chatMemory; this.defaultConversationId = defaultConversationId; this.order = order; this.scheduler = scheduler; }
我们可以通过静态方法 builder(ChatMemory chatMemory) 创建一个 Builder,然后调用 build() 方法获取 MessageChatMemoryAdvisor 的实例,源码如下:
public static Builder builder(ChatMemory chatMemory) { return new Builder(chatMemory); }
public MessageChatMemoryAdvisor build() { return new MessageChatMemoryAdvisor(this.chatMemory, this.conversationId, this.order, this.scheduler); }
简单示例:
@Configuration public class AiConfig { @Bean public ChatMemory chatMemory() { // 默认存储最近的 10 条消息 return MessageWindowChatMemory.builder().maxMessages(10).build(); } @Bean public MessageChatMemoryAdvisor chatMemoryAdvisor(ChatMemory chatMemory) { return MessageChatMemoryAdvisor.builder(chatMemory).build(); } }
上述代码,通过 @Configuration 配置类创建了一个 ChatMemory(内存实现方式 MessageWindowChatMemory),然后将 ChatMemory 作为 builder() 的参数去创建 MessageChatMemoryAdvisor。
用于构建 MessageChatMemoryAdvisor 实例的建造者类,允许你通过链式调用方法来配置和创建 MessageChatMemoryAdvisor 对象。
Builder 支持的属性如下:
public static final class Builder { // 消息会话ID private String conversationId = ChatMemory.DEFAULT_CONVERSATION_ID; // 执行顺序 private int order = Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER; // 调度策略 private Scheduler scheduler = BaseAdvisor.DEFAULT_SCHEDULER; // 实际会话历史存储实现 private ChatMemory chatMemory; }
其中:
conversationId:标识当前对话的唯一会话ID,区分不同用户、不同对话或不同场景的聊天会话。默认值 ChatMemory.DEFAULT_CONVERSATION_ID 为 "default"。
注意:如果在多用户系统中,必须设置唯一值,否则会导致多个用户的聊天上下文共用。重启会话时应保持相同 ID 以恢复历史对话。如未设置时将使用默认值,可能导致不同会话的记忆混淆,即所有聊天均在一个会话中。
order:定义 Advisor 在代理链中的执行顺序,当有多个 Advisor 时,控制记忆处理与其他操作(如日志、安全)的执行顺序。
scheduler:控制记忆操作的执行线程和调度策略,异步记忆操作、后台记忆清理、高并发场景优化。常用调度器:
Schedulers.immediate() 立即在当前线程执行(默认)
Schedulers.boundedElastic() 弹性线程池,适合阻塞操作
Schedulers.parallel() 固定大小线程池,适合计算密集型
Schedulers.single() 单线程执行
chatMemory:对话记忆的实际存储实现,决定记忆如何存储(内存、Redis、DB等)和保留策略。常用实现如下图:
其中:
InMemoryChatMemoryRepository 基于内存的对话记忆存储实现,适用于开发测试环境或轻量级生产场景,提供零延迟的对话记忆访问。
JdbcChatMemoryRepository 基于 关系型数据库 的对话记忆存储实现,通过 JDBC 提供标准化的 SQL 数据库访问,支持主流关系型数据库如 MySQL、PostgreSQL、Oracle 等。
Neo4jChatMemoryRepository 为 Neo4j 图数据库设计的对话记忆存储实现,它利用图数据库的天然优势来高效存储和检索复杂的对话关系。
CassandraChatMemoryRepository 基于 Apache Cassandra 数据库的对话记忆存储实现,专为大规模、高并发的对话系统设计,利用 Cassandra 的分布式特性实现高性能的记忆存储与检索。
为了方便观察 Spring AI 保存的会话历史数据,这里选择 MySQL 数据库。
创建数据库表,用来保存会话历史数据,那么数据库表叫什么名字,有哪些字段呢!我们可以到 org.springframework.ai.chat.memory.repository.jdbc 包下面找到几个 schema-*.sql 文件,如下图:
遗憾的是,没有 MySQL 的 schema,我们可以参考 schema-mariadb.sql 中的 SQL 语句,简单修改,在 MySQL 中创建数据表:
(1)先创建名为 chat_db 的数据库,如下图:
(2)在 chat_db 数据库下面创建如下数据表:
SQL 语句:
CREATE TABLE `spring_ai_chat_memory` ( `conversation_id` varchar(36) NOT NULL, `content` text NOT NULL, `type` varchar(10) NOT NULL, `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY `spring_ai_chat_memory_timestamp_IDX` (`timestamp`,`conversation_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
由于我们需要使用 MySQL 数据库,因此需要添加 Mysql,以及 Spring JDBC 等依赖,如下:
<!-- 会话记忆 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-chat-memory-repository-jdbc</artifactId> </dependency> <!-- Spring Data JDBC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <!-- mysql 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency>
创建 application.yml 配置文件,配置数据源、日志信息、大模型等信息,如下:
spring: application: name: springai_demo1 # 数据库配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/chat_db username: root password: aaaaaa # AI配置 ai: # openai相关配置 openai: # 基础地址 base-url: https://api.xty.app # AI KEY api-key: sk-TmZpI4DifxqHTNb6AXPS*********ZpBhReaAhsiPEwUik # 聊天模型配置 chat: options: model: gpt-3.5-turbo # 图片模型配置 image: options: # 需要高级接口 model: dall-e-3 chat: memory: repository: jdbc: # 启动时不初始化数据库表,我们手动执行SQL脚本创建表 initialize-schema: never client: # true 开启,自动注入 ChatClient,false 关闭,需要手动创建 ChatClient enabled: true # 日志配置 logging: charset: console: UTF-8 level: root: info org.springframework.ai: debug
创建名为 AiConfig 的配置类,配置 JdbcChatMemoryRepository、ChatMemory 和 MessageChatMemoryAdvisor,如下:
package com.hxstrive.springai.springai_openai.advisor_MessageChatMemoryAdvisor.config; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; /** * AI配置 * @author Administrator */ @Configuration public class AiConfig { @Bean public ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).build(); } @Bean public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder() .chatMemoryRepository(chatMemoryRepository) .maxMessages(10).build(); } @Bean public MessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory chatMemory) { return MessageChatMemoryAdvisor.builder(chatMemory).build(); } @Bean public SimpleLoggerAdvisor simpleLoggerAdvisor() { return new SimpleLoggerAdvisor(); } }
注意,上面配置的 SimpleLoggerAdvisor 仅仅是为了打印日志信息。如果没有显示日志信息,请修改日志级别,Spring 默认是 INFO 级别,但是 SimpleLoggerAdvisor 打印日志的级别是 DEBUG:
只需要在 application.yml 配置文件中添加如下内容:
logging: charset: console: UTF-8 # 解决中文乱码问题 level: root: info org.springframework.ai: debug
创建一个名为 AIController 的 Controller,编写 API 来验证上述配置信息,如下:
package com.hxstrive.springai.springai_openai.advisor_MessageChatMemoryAdvisor.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AIController { @Autowired private ChatModel chatModel; @Autowired private MessageChatMemoryAdvisor messageChatMemoryAdvisor; @Autowired private SimpleLoggerAdvisor simpleLoggerAdvisor; @GetMapping("/ai/simple") public String completion(@RequestParam("userText") String userText) { // 构建 RAG advisor 时需要完成 vectorStore 初始化 ChatClient chatClient = ChatClient.builder(chatModel) .defaultSystem("使用中文进行回复") .defaultAdvisors( // 使用 Advisor simpleLoggerAdvisor, messageChatMemoryAdvisor ) .build(); // conversationId 区分不同对话,定位对应的历史记录 String conversationId = "sessionId-202572020514"; return chatClient.prompt() // 运行时设置会话ID参数 .advisors(advisor -> { advisor.param(ChatMemory.CONVERSATION_ID, conversationId); }) .user(userText) .call() .content(); } }
运行应用程序,使用浏览器访问“http://localhost:8080/ai/simple?userText=四川大学是211吗?”地址,如下图:
再去查看数据库数据,如下图:
看见了两轮会话数据,其中的 conversation_id 就是在如下代码进行设置的:
String conversationId = "sessionId-202572020514"; return chatClient.prompt() // 运行时设置会话ID参数 .advisors(advisor -> { advisor.param(ChatMemory.CONVERSATION_ID, conversationId); // 看这里 }) .user(userText) .call() .content();
注意,一定要根据业务场景设置 conversationId,因为 conversationId 直接影响会话历史管理。应该是不同的人拥有不同的 conversationId,而且允许用户手动创建新的 conversationId,表示新的会话开始。一定不要使用 UUID 来设置 conversationId,不然每次都是新的 UUID,导致会话历史功能失效。