AI超级智能开发系列从入门到上天第六篇:自定义AI记忆持久化

一:当前现状

对话记忆持久化背景提取

之前使用的是基于内存的对话记忆来保存对话上下文,但这种方式存在缺陷:

  • 服务器重启后,内存中的对话记录会全部丢失

因此,实际业务中可能需要将对话记忆持久化,把数据保存到:

  • 文件
  • 数据库
  • Redis
  • 其他对象存储

核心问题:如何实现对话记忆的持久化存储与恢复

二:利用现有依赖实现

Spring AI 对话记忆持久化支持提取

Spring AI 官方提供了第三方数据库整合的对话记忆实现,可将对话保存到不同数据源:

  • InMemoryChatMemory:基于内存的临时存储(默认实现,重启丢失)
  • CassandraChatMemory:在 Cassandra 中实现带过期时间的持久化存储
  • Neo4jChatMemory:在 Neo4j 中实现无过期时间限制的持久化存储
  • JdbcChatMemory:在 JDBC 兼容数据库中实现无过期时间限制的持久化存储

关于 JdbcChatMemory 的使用提示

若需将对话持久化到传统关系型数据库,理论上可使用 JdbcChatMemory,但不推荐

  • 依赖 spring-ai-starter-model-chat-memory-jdbc 版本稀少
  • 官方文档 / 介绍匮乏
  • Maven 中央仓库无法检索到该依赖
  • 生产环境稳定性与兼容性存疑

三:自定义ChatMemory

1. 核心设计思想

Spring AI 的对话记忆实现解耦了「存储」和「记忆算法」

  • 可以独立修改 ChatMemory 的存储层(比如从内存换成数据库 / Redis)
  • 无需改动上层保存对话记忆的业务流程

2. 自定义实现方式

官方文档未提供自定义 ChatMemory 示例,但可以参考默认实现类 InMemoryChatMemory 的源码进行仿写。

InMemoryChatMemory 源码如下:

3. ChatMemory 接口核心职责

接口方法较少,核心是实现对话消息的 增、查、删 操作:

  • 新增:添加新的对话消息到记忆中
  • 查询:根据会话 ID 获取历史消息
  • 删除:清除指定会话的记忆或过期消息

四:自定义文件持久化

1:引入依赖

复制代码
<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>5.6.2</version>
</dependency>

2:编写代码

复制代码
/**
 * 基于文件持久化的对话记忆
 */
public class FileBasedChatMemory implements ChatMemory {

    private final String BASE_DIR;
    private static final Kryo kryo = new Kryo();

    static {
        kryo.setRegistrationRequired(false);
        // 设置实例化策略
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
    }

    // 构造对象时,指定文件保存目录
    public FileBasedChatMemory(String dir) {
        this.BASE_DIR = dir;
        File baseDir = new File(dir);
        if (!baseDir.exists()) {
            baseDir.mkdirs();
        }
    }

    @Override
    public void add(String conversationId, List<Message> messages) {
        List<Message> conversationMessages = getOrCreateConversation(conversationId);
        conversationMessages.addAll(messages);
        saveConversation(conversationId, conversationMessages);
    }

    @Override
    public List<Message> get(String conversationId, int lastN) {
        List<Message> allMessages = getOrCreateConversation(conversationId);
        return allMessages.stream()
                .skip(Math.max(0, allMessages.size() - lastN))
                .toList();
    }

    @Override
    public void clear(String conversationId) {
        File file = getConversationFile(conversationId);
        if (file.exists()) {
            file.delete();
        }
    }

    private List<Message> getOrCreateConversation(String conversationId) {
        File file = getConversationFile(conversationId);
        List<Message> messages = new ArrayList<>();
        if (file.exists()) {
            try (Input input = new Input(new FileInputStream(file))) {
                messages = kryo.readObject(input, ArrayList.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return messages;
    }

    private void saveConversation(String conversationId, List<Message> messages) {
        File file = getConversationFile(conversationId);
        try (Output output = new Output(new FileOutputStream(file))) {
            kryo.writeObject(output, messages);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private File getConversationFile(String conversationId) {
        return new File(BASE_DIR, conversationId + ".kryo");
    }
}

3:修改MessageChatMemoryAdvisor对象的上下文存储类

复制代码
public LoveApp(ChatModel dashscopeChatModel) {
    // 初始化基于文件的对话记忆
    String fileDir = System.getProperty("user.dir") + "/chat-memory";
    ChatMemory chatMemory = new FileBasedChatMemory(fileDir);
    chatClient = ChatClient.builder(dashscopeChatModel)
            .defaultSystem(SYSTEM_PROMPT)
            .defaultAdvisors(
                    new MessageChatMemoryAdvisor(chatMemory)
            )
            .build();
}

4:测试效果

相关推荐
MatrixOrigin3 小时前
数据库没有死,只是范式变了
数据库·oracle
羊小蜜.4 小时前
Mysql 13: 触发器全解——创建、查看、使用与注意事项
数据库·mysql·触发器
阿里加多4 小时前
第 1 章:Go 并发编程概述
java·开发语言·数据库·spring·golang
ShiJiuD6668889994 小时前
Mysql 进阶
数据库·mysql
一 乐5 小时前
物流信息管理|基于springboot + vue物流信息管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·物流信息管理系统
Rick19935 小时前
Redis 分布式锁:核心使用场景
数据库·redis·分布式
身如柳絮随风扬6 小时前
Redis如何实现高效插入大量数据
数据库·redis·缓存
Dream of maid6 小时前
Mysql(3)运算符
数据库·mysql·adb