Spring AI

Spring AI 记忆管理技术:持久化记忆

在Spring AI中,我们最熟悉的就是MessageWindowChatMemory ,它默认是一个基于内存的短期记忆实现,它维护最多指定最大大小(默认值:20 条消息)的消息窗口。 当消息数量超过此限制时,较旧的消息会被逐出。这…

TL;DR

在Spring AI中,我们最熟悉的就是MessageWindowChatMemory ,它默认是一个基于内存的短期记忆实现,它维护最多指定最大大小(默认值:20 条消息)的消息窗口。 当消息数量超过此限制时,较旧的消息会被逐出。这…

在Spring AI中,我们最熟悉的就是MessageWindowChatMemory ,它默认是一个基于内存的短期记忆实现,它维护最多指定最大大小(默认值:20 条消息)的消息窗口。 当消息数量超过此限制时,较旧的消息会被逐出。这确保对话始终可以使用最新的上下文,同时保持内存使用有界。应用一旦重启,内存中的记忆就会丢失。 Spring AI中的记忆存储是基于ChatMemoryRepository这个接口扩展的,他提供了一些列实现用来做基于存储,包括前面的MessageWindowChatMemory所使用到的InMemoryChatMemoryRepository,这就是一种基于ConcurrentHashMap的本地内存的记忆方式。 这就是MessageWindowChatMemory的默认实现,除此之外,ChatMemoryRepository还有一系列扩展实现,我们也可以用,包括: - JdbcChatMemoryRepository - 使用 JDBC 将消息存储在关系型数据库中,它支持多种数据库,PostgreSQL、MySQL / MariaDB、SQL Server、Oracle等 - CassandraChatMemoryRepository - 使用 Apache Cassandra 存储消息。它适用于需要持久化存储聊天记录的应用,特别是在需要高可用性、持久性、可扩展性,以及利用TTL功能时。 - Neo4jChatMemoryRepository - 使用 Neo4j 将聊天消息存储为属性图数据库中的节点和关系。它适用于希望利用 Neo4j 的图功能进行聊天记忆持久化的应用程序。 - CosmosDBChatMemoryRepository - 使用 Azure Cosmos DB NoSQL API 来存储消息。它适用于需要全球分布式、高度可扩展的文档数据库来持 久化聊天内存的应用程序。该存储库使用对话 ID 作为分区键,以确保高效的数据分布和快速检索。 - MongoChatMemoryRepository - 使用 MongoDB 存储消息。它适用于需要灵活的、面向文档的数据库进行聊天内存持久化的应用程序。

使用MySQL持久化记忆

我们借助Spring AI,使用Spring来做记忆保存,Spring AI提供了

spring-ai-starter-model-chat-memory-repository-jdbc

我们可以直接用它来实现。首先我们在代码中增加依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
    <version>1.1.0</version>
</dependency>

增加依赖后会自动引入这两个jar包:

spring-ai-autoconfigure-model-chat-memory-repository-jdbc
spring-ai-model-chat-memory-repository-jdbc

一个是jdbc操作的工具包,一个是对他做自动化配置的。 通过org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure.JdbcChatMemoryRepositoryProperties 我们可以知道,都需要增加哪些配置项目:

spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            platform: mysql
            initialize-schema: always

platform用于指定具体哪个数据库,他支持很多数据库,在platform和schema二选一进行配置就行了,就是指定用哪个表结构。 如果运行时还是提示没建表(initialize-schema: always 默认会自动创建表):

java.sql.SQLSyntaxErrorException: Table 'spring_ai_db2.spring_ai_chat_memory' doesn't exist
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.33.jar:8.0.33]

可以自己建个表,先创建一个数据库,然后建表一张,建表语句如下: (不要用官方提供的SQL,会出现乱码和无法解析问题,就用我这个)

CREATE TABLE `spring_ai_chat_memory` (
  `conversation_id` varchar(36) CHARACTER SET utf8mb4 NOT NULL,
  `content` text CHARACTER SET utf8mb4 NOT NULL,
  `type` enum('USER','ASSISTANT','SYSTEM','TOOL') CHARACTER SET utf8mb4 NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`,`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
;

有了这些之后,还需要一个单独的配置,就是他这个东西要和数据库交互还需要一个datasource,我们需要有个数据库连接的能力,所以需要导入包:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

增加数据库相关配置:

spring:
  datasource:
      url: jdbc:mysql://xx.xx.xx.xx/spring_ai_db2?useUnicode=true&characterEncoding=UTF-8
      username: xxx
      password: xxx
      driver-class-name: com.mysql.cj.jdbc.Driver

接着就需要定义一个使用JDBC做记忆的ChatMemory:

public class JdbcChatMemoryConfiguration {

    @Bean
    ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
        return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
    }
}

然后在chatClient中使用这个memory:

@RestController
@RequestMapping("/jdbc/memory")
public class JdbcChatMemoryController implements InitializingBean {

    @Autowired
    private ChatModel chatModel;

    private ChatClient chatClient;

    @Autowired
    private ChatMemory jdbcChatMemory;

    @GetMapping("/callDb")
    public Flux<String> callDb(String message, String chatId, HttpServletResponse httpServletResponse) {
        httpServletResponse.setCharacterEncoding("UTF-8");
        return chatClient
                .prompt()
                .user(message)
                .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream().content();
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        this.chatClient = ChatClient.builder(chatModel)
                // 实现 Logger 的 Advisor
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(jdbcChatMemory).build(), new SimpleLoggerAdvisor())
                // 设置 ChatClient 中 ChatModel 的 Options 参数
                .defaultOptions(
                        DashScopeChatOptions.builder()
                                .withTopP(0.7)
                                .build()
                ).build();
    }
}

接着可以做两次对话: 第一次:http://localhost:8010/ai/long_term_memory/chat?chatId=12321&message=i%20am%20hollis 第二次:http://localhost:8010/ai/long_term_memory/chat?chatId=12321&message=who%20am%20i? 然后查看数据库,可以看到数据已经存下来了, 我们重启应用,在用同一个chatId问问题,他还是可以正常回答,历史的记忆还是存在的。

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。