MessageChatMemoryAdvisor 对话历史管理

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.Builder

用于构建 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等)和保留策略。常用实现如下图:

MessageChatMemoryAdvisor 对话历史管理

其中:

  • 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 文件,如下图:

MessageChatMemoryAdvisor 对话历史管理

遗憾的是,没有 MySQL 的 schema,我们可以参考 schema-mariadb.sql 中的 SQL 语句,简单修改,在 MySQL 中创建数据表:

(1)先创建名为 chat_db 的数据库,如下图:

MessageChatMemoryAdvisor 对话历史管理

(2)在 chat_db 数据库下面创建如下数据表:

MessageChatMemoryAdvisor 对话历史管理

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:

MessageChatMemoryAdvisor 对话历史管理

只需要在 application.yml 配置文件中添加如下内容:

logging:
  charset:
    console: UTF-8 # 解决中文乱码问题
  level:
    root: info
    org.springframework.ai: debug

创建 Controller

创建一个名为 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吗?”地址,如下图:

MessageChatMemoryAdvisor 对话历史管理

再去查看数据库数据,如下图:

MessageChatMemoryAdvisor 对话历史管理

看见了两轮会话数据,其中的 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,导致会话历史功能失效。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号