基于redis+mongoDB+kryi实现的用户对话记忆分层
基于Spring AI Chat Memory + Advisor接口实现基于Redis的近期会话存储,MongoDB用于长期记忆持久化,Kryo用于序列化压缩的多轮本地会话存储功能应该怎么做
为什么要这样做
如果要做一个分布式或者超长上下论用户对话功能的agent,应该考虑到用户与agent的对话以及agent之间的会话是存储在内存中的,如果不进行消息持久化,可能会因为网络抖动或者内存限制导致消息丢失,因此需要实现一个分层记忆架构
为什么选择redis,mongodb以及kryio这三个进行操作
mongodb作为非关系型数据集,存储类json数据,适合变动频率比较大的集合(支持嵌套对象,数据,不用拆分为多表关联)适合需求频繁变更的项目,电商项目,用户画像和物联网数据;更容易扩展:关系型数据库扩展的时候需要进行复杂的设计(分库分表,主从分库等需要精心设计),但是mongodb天生是为了分布式设计的,支持分片横向扩展,把数据分散到多态服务器,集群扩容不停机,不影响业务,轻松支持亿级别数据,支持高并发读写;
考虑到mongodb主要用于存储对话数据,并且用户对话数据结构可能会随着系统的升级而发生变化(图片,语音等多模态数据)而且采用pgsql的一般都是用来做rag的,这里的mongdb一开始设计的目的就是作为快速迭代使用的用户对话存储数据库;
redis用于存储用户最近对话,如果用户上下文窗口即将满了之后采用redis将最近的20条对话信息存储在内存中,而前面的对话直接存储在mongodb中,可以在保证对话和检索效率的情况下提升用户体验。
kryo的作用就是为了将json对话数据结构进行序列化,如果手动序列化很容易出现一些意想不到的异常(空值,异常值等)采用kryo直接进行序列化保证了数据的前后一致性,此外采用序列化后可以配合压缩算法降低内存占用;
具体实现:
先要清楚消息的发送流程,用户发送消息前可以使用advisor拦截器对数据做一些预处理,这里的advisor就可以用于进行用户数据的拆分和存储。
kryo实现本地会话压缩,先提前将基于spring ai message体系的注册(提前注册提高效率,避免创建时new 对象) 返回kryo对象;
重写chatmemoryrepository接口,实现 findConversationIds(),获取所有对话id列表,先扫描本地存储会话的目录,过滤带kryo后缀的文件,将其kryo剔除就是对应的会话id,然后收集后转为list返回
findByConversationId(@NotNull String conversationId)根据对话id去查找本地缓存如果本地缓存没有就去本地文件中查找,找到后尝试将其填到缓存中去
saveAll(@NotNull String conversationId, @NotNull List
deleteByConversationId(@NotNull String conversationId) :同理,根据id将对应的kryo消息给删除掉
二级redis内存,缓存用户近七天的临时会话,避免查库
RedisChatMemoryRepository implements ChatMemoryRepository
实现用户与ai对话历史的存储,查询,删除和管理
同样的findConversationIds()用于找到所有的对话id列表,区别在于使用redis的set集合进行存储
findByConversationId(conversationId)根据对话id查找历史消息列表,采用json格式存储,读取时根据key找打对应的value然后将其反序列化为List
saveAll(@NotNull String conversationId, @NotNull List<org.springframework.ai.chat.messages.Message> messages):将用户对话List
deleteByConversationId(@NotNull String conversationId):直接删除即可
结合kryo和redis的组合式CompositeChatMemoryRepository,实现本地基于内存的高效存储以及基于redis的高效缓存保证消息持久化
基于mongodb的长期记忆实现:LongTermMemoryRetrievalAdvisor
继承CallAdvisor, StreamAdvisor接口,实现getName(),getOrder(),adviseCall(),adviseStream()方法
getname用于获取拦截器名称,getOrder()方法用于确定拦截器执行等级,数字越小,等级越高。
advisCall方法接受CallAdvisorChain,并通过chain.next方法指定如何对用户发送的会话请求进行预处理
next会接受before和after方法,这个是可以自定义的,before方法先根据LongTermMemoryService检索到用户的基本信息,然后提取到用户的长期画像和偏好行为然后将其注入到prompt模板中实现用户偏好风格建模
after方法是处理模型输出结果的,由于这里是前置拦截器因此不做处理
