LangChain4j之聊天记忆持久化
聊天记忆持久化(Persistence)
默认情况下,聊天记忆存储在内存中。如果需要持久化存储,可以实现一个自定义的聊天记忆存储类,以便将聊天消息存储在你选择的任何持久化存储介质中。
存储介质的选择
大模型中聊天记忆的存储选择哪种数据库,需要综合考虑数据特点、应用场景和性能要求等因素,以下是一些常见的选择及其特点:
(1)MySQL:
- 特点:关系型数据库。支持事务处理,确保数据的一致性和完整性,适用于结构化数据的存储和查询。
- 适用场景:如果聊天记忆数据结构较为规整,例如包含固定的字段如对话 ID、用户 ID、时间戳、消息内容等,且需要进行复杂的查询和统计分析,如按用户统计对话次数、按时间范围查询特定对话等,MySQL是不错的选择。
(2)Redis:
- 特点:内存数据库,读写速度极高。它适用于存储热点数据,并且支持多种数据结构,如字符串、哈希表、列表等,方便对不同类型的聊天记忆数据进行处理。
- 适用场景:对于实时性要求极高的聊天应用,如在线客服系统或即时通讯工具,Redis可以快速存储和获取最新的聊天记录,以提供流畅的聊天体验。
(3)MongoDB:
- 特点:文档型数据库,数据以JSON - like的文档形式存储,具有高度的灵活性和可扩展性。它不需要预先定义严格的表结构,适合存储半结构化或非结构化的数据。
- 适用场景:当聊天记忆中包含多样化的信息,如文本消息、图片、语音等多媒体数据,或者消息格式可能会频繁变化时,MongoDB能很好地适应这种灵活性。例如,一些社交应用中用户可能会发送各种格式的消息,使用MongoDB可以方便地存储和管理这些不同类型的数据。
(4)Cassandra:
- 特点:是一种分布式的 NoSQL 数据库,具有高可扩展性和高可用性,能够处理大规模的分布式数据存储和读写请求。适合存储海量的、时间序列相关的数据。
- 适用场景:对于大型的聊天应用,尤其是用户量众多、聊天数据量巨大且需要分布式存储和处理的场景,Cassandra 能够有效地应对高并发的读写操作。例如,一些面向全球用户的社交媒体平台,其聊天数据需要在多个节点上进行分布式存储和管理,Cassandra可以提供强大的支持。
MongoDB
MongoDB简介
(1)MongoDB是一个基于文档的NoSQL数据库,由MongoDB Inc.开发。NoSQL指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
(2)MongoDB的设计理念是为了应对大数据量、高性能和灵活性需求。
(3)MongoDB使用集合(Collections)来组织文档(Documents),每个文档都是由键值对组成:
- 数据库(Database):存储数据的容器,类似于关系型数据库中的数据库。
- 集合(Collection):数据库中的一个集合,类似于关系型数据库中的表。
- 文档(Document):集合中的一个数据记录,类似于关系型数据库中的行(row),以 BSON 格式存储。
(4)MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成,文档类似于JSON对象,字段值可以包含其他文档,数组及文档数组:

MongoDB本地安装及使用
【传统方式安装】
第一步,安装 mongodb-windows-x86_64-8.0.6-signed.msi,这是服务端;
第二步,安装 mongosh-2.5.0-win32-x64.zip,这是命令行客户端;
第三步,安装 mongodb-compass-1.39.3-win32-x64.exe,这是图形客户端,方便操作MongoDB。
接下来介绍如何使用mongosh,具体的步骤如下:
第一步,启动 MongoDB Shell。在命令行中输入 mongosh 命令,启动 MongoDB Shell,如果 MongoDB 服务器运行在本地默认端口(27017),则可以直接连接。
1 | mongosh |
第二步,连接到 MongoDB 服务器。如果 MongoDB 服务器运行在非默认端口或者远程服务器上,可以使用以下命令连接:
1 | mongosh --host <hostname>:<port> |
其中
第三步,执行基本操作。连接成功后,可以执行各种 MongoDB 数据库操作,如:
- 查看当前数据库:
db - 显示数据库列表:
show dbs - 切换到指定数据库:
use <database_name> - 执行查询操作:
db.<collection_name>.find() - 插入文档:
db.<collection_name>.insertOne({ ... }) - 更新文档:
db.<collection_name>.updateOne({ ... }) - 删除文档:
db.<collection_name>.deleteOne({ ... }) - 退出 MongoDB Shell:
quit()或者exit
第四步,执行一些CRUD操作:
1 | 插入文档 |
Docker安装MongoDB
第一步,在本地创建数据持久化目录:
1 | E:/DockerVolume/mongo |
第二步,执行如下命令:
1 | docker run -d --name my-mongo7 -p 27117:27017 -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=123456 -v E:/DockerVolume/mongo7.0:/data/db mongo:7.0 |
解释一下上述命令的含义:
(1)-d表示后台运行;
(2)–name my-mongo7表示指定容器名称为my-mongo7;
(3)-p 27117:27017表示将虚拟机的27017端口映射到宿主机的27117端口;
(4)-e MONGO_INITDB_ROOT_USERNAME=admin表示设置超级管理员用户名;
(5)-e MONGO_INITDB_ROOT_PASSWORD=123456表示设置超级管理员密码;
(6)-v E:/DockerVolume/mongo7.0:/data/db表示将容器的/data/db目录挂载到宿主机的E:/DockerVolume/mongo7.0目录。
第三步,验证连接。执行如下命令进入到Docker容器中:
1 | docker exec -it my-mongo7 bash |
之后切换到bin目录:
1 | cd bin |
执行如下命令:
1 | mongosh -u root -p 123456 --authenticationDatabase admin |
出现下面的信息,则表示连接成功:

第四步,使用Navicat进行连接:

SpringBoot整合MongoDB
第一步,在项目POM文件引入mongodb依赖:
1 | <!-- Spring Boot Starter Data MongoDB --> |
第二步,在application.properties配置文件中新增如下配置:(这里使用了Docker安装的MongoDB,端口有变化)
1 | # mongodb数据库配置 |
第三步,新建一个名为pojo的包,并在里面定义一个名为MyChatMessage的类:
1 | @Data |
请注意,这里的messageId是唯一标识,用于映射到MongoDB文档的_id字段,它的类型是<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">ObjectId</font>,不可以是字符串。
<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">ObjectId</font>是<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">MongoDB</font>默认使用的主键类型,它是一个 12 字节的<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">BSON</font>类型,通常用作<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">_id</font>字段。<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">ObjectId</font>的值是一个有效的<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">24 字符</font>的十六进制字符串。<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">12*8=24*4=96</font>
<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">ObjectId</font> 的值由以下部分组成:
4 字节:当前时间戳(秒级,表示创建的时间)
5 字节:机器标识符和进程ID(唯一)
3 字节:计数器(递增值,用于确保在同一毫秒内创建多个 <font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">ObjectId</font> 时的唯一性)
<font style="color:rgba(0, 0, 0, 0.8);background-color:rgb(247, 247, 249);">new ObjectId()</font>默认使用当前时间戳,当然开发者也可以传入一个时间戳。
第四步,在controller包内定义一个名为MongoController的类,用于测试Mongo的增删改查操作:
1 | @RestController |
第五步,启动项目,访问如下地址来往MongoDB中插入一条数据:
1 | http://localhost:8080/mongo/create |
页面显示结果如下:

说明数据已经添加成功,查看一下MongoDB数据库:

在read方法中传入之前创建数据的id,之后访问如下地址:
1 | http://localhost:8080/mongo/read |
页面显示结果如下:

说明我们已经可以根据id来查询对应数据了。在update方法中添加数据的id以及更新后的内容,之后访问如下地址:
1 | http://localhost:8080/mongo/update |
页面显示结果如下:

说明此时数据被更新了,查看一下MongoDB数据库:

说明数据确实被更新。最后我们在update方法中传入对应数据的id,然后访问如下地址:
1 | http://localhost:8080/mongo/delete |
页面显示结果如下:

说明数据被成功删除了,这样我们就实现了使用SpringBoot来操作MongoDB的目的。
聊天持久化
优化消息实体类
回到MyChatMessage类中,我们修改一下消息实体类:
1 | @Data |
这里我们使用id属性作为与MongoDB文档的_id字段进行映射,然后messageId作为备用字段。
创建持久化类
创建一个名为store的包,并在里面定义一个名为MongoChatMemoryStore的类,这个类需要实现ChatMemoryStore接口。这个ChatMemoryStore接口中的代码如下:
1 | public interface ChatMemoryStore { |
可以看到这个ChatMemoryStore 接口定义了三个方法:
List<ChatMessage> getMessages(Object memoryId),根据memoryId(通常是用户 ID 或会话 ID)检索对应的聊天消息列表。void updateMessages(Object memoryId, List<ChatMessage> messages),更新指定memoryId的聊天消息列表。每当有新的消息添加到聊天内存中时,LangChain4j会调用此方法。void deleteMessages(Object memoryId),删除指定memoryId的所有聊天消息。
这些方法允许开发者实现自定义的持久化逻辑,以满足特定的存储需求。
MongoChatMemoryStore类中的代码如下所示:
1 | @Component |
简单解释其中的代码含义:
(1)Criteria用于构造查询条件,即where是memoryId等于传入的memoryId;
(2)Query用于封装查询请求,如条件、分页、排序等;
(3)ChatMessageDeserializer.messagesFromJson(),该方法是LangChain4j中的工具类,用于将JSON字符串反序列化为聊天消息对象;
(4)ChatMessageSerializer.messagesToJson(),该方法是LangChain4j中的工具类,用于将聊天消息对象序列化为JSON字符串。
更新配置类信息
回到config包中,之前我们使用SeparateChatAssistantConfig实现了隔离聊天,接下来我们在里面注入持久化对象,并进行配置:
1 | @Configuration |
可以看到,这里使用MongoChatMemoryStore来存储会话,实现聊天消息持久化功能。
启动项目进行测试
启动项目,访问如下地址:
1 | http://localhost:8080/test/testChatMemoryV4 |
然后查看一下MongoDB数据库,可以看到聊天记录已经存储了:

