SpringBoot 整合 Langchain4j 实现会话记忆存储深度解析

目录

一、前言

二、AI大模型会话记忆介绍

[2.1 AI 大模型的会话记忆是什么](#2.1 AI 大模型的会话记忆是什么)

[2.2 AI 大模型为什么需要会话记忆](#2.2 AI 大模型为什么需要会话记忆)

[2.3 AI 大模型会话记忆常用实现方案](#2.3 AI 大模型会话记忆常用实现方案)

[2.4 LangChain4j 会话记忆介绍](#2.4 LangChain4j 会话记忆介绍)

[2.4.1 LangChain4j 会话记忆介绍](#2.4.1 LangChain4j 会话记忆介绍)

[2.4.2 LangChain4j 会话记忆类型](#2.4.2 LangChain4j 会话记忆类型)

[三、Langchain4j 会话记忆操作案例使用](#三、Langchain4j 会话记忆操作案例使用)

[3.1 前置准备](#3.1 前置准备)

[3.1.1 导入依赖文件](#3.1.1 导入依赖文件)

[3.1.2 添加配置文件](#3.1.2 添加配置文件)

[3.1.3 前置案例](#3.1.3 前置案例)

[3.2 会话记忆的实现](#3.2 会话记忆的实现)

[3.2.1 基于多轮对话存储结果实现会话记忆](#3.2.1 基于多轮对话存储结果实现会话记忆)

[3.2.2 基于ChatMemory 实现会话记忆](#3.2.2 基于ChatMemory 实现会话记忆)

[3.2.3 基于ChatMemory 会话记忆升级](#3.2.3 基于ChatMemory 会话记忆升级)

[3.3 会话隔离实现](#3.3 会话隔离实现)

[3.3.1 自定义一个Assistant](#3.3.1 自定义一个Assistant)

[3.3.2 自定义chatMemoryProvider 配置bean](#3.3.2 自定义chatMemoryProvider 配置bean)

[3.3.3 添加测试接口](#3.3.3 添加测试接口)

[3.3.4 效果测试](#3.3.4 效果测试)

四、基于Redis实现会话记忆持久化存储

[4.1 前置准备](#4.1 前置准备)

[4.1.1 导入redis依赖](#4.1.1 导入redis依赖)

[4.1.2 添加redis配置信息](#4.1.2 添加redis配置信息)

[4.1.3 自定义redis序列化类](#4.1.3 自定义redis序列化类)

[4.2 会话记忆代码改造](#4.2 会话记忆代码改造)

[4.2.1 自定义ChatMemoryStore](#4.2.1 自定义ChatMemoryStore)

[4.2.2 ChatMemoryProvider 配置bean改造](#4.2.2 ChatMemoryProvider 配置bean改造)

[4.2.3 接口效果测试](#4.2.3 接口效果测试)

五、写在文末


一、前言

在于大模型对话的时候,细心的伙伴们会发现,前面跟大模型聊的一句话,后面再基于这句话继续问问题的时候,仍然可以得到预期的回答,这就是大模型的记忆能力。什么是记忆功能?默认情况下向大模型每次发起的提问都是新的,大模型无法把每次对话形成记忆,也无法根据对话上下文给出人性化的答案。比如:我的第一次提的一个问题,大模型给出了一个回答的列表,当我再次提问这个回答列表中的一个问题时,它就不知道我在说什么了,因为大模型已经失去了上一次的提问记忆。所以让智能体(如AI助手、机器人、虚拟角色等)拥有记忆功能不仅能提升交互体验,还能增强其功能性、适应性和长期价值。

二、AI大模型会话记忆介绍

2.1 AI 大模型的会话记忆是什么

AI 大模型的会话记忆,通常指的是在对话式人工智能系统中,为了让AI能够理解并回应用户输入的信息时,考虑到之前的交互内容的能力。这种能力使得AI能够在长时间的对话中维持上下文,记住之前提到的关键信息,并根据这些信息进行更加准确和相关的回应。在下面的这2段对话中,基于第一次给出的回答内容,再次发起相关的问题时,大模型仍然能够给出预期的回答。

传统上,很多对话系统处理每个请求都是独立的,不考虑之前发生的对话内容。这种方式对于简单、直接的查询是有效的,但当涉及到需要上下文理解的复杂对话时,就显得力不从心了。为了解决这个问题,研究人员开发了不同的技术来赋予AI"记忆"功能,使它们能够在对话中保持连贯性和一致性。

2.2 AI 大模型为什么需要会话记忆

AI大模型需要会话记忆,主要是为了提升对话的质量和连贯性,使得人机交互更加自然、有效。以下是几个主要原因:

  1. 上下文理解:在实际对话中,人们经常依赖于之前提到的信息来构建后续的讨论。没有会话记忆的话,AI每次只能处理独立的请求,而无法理解或记住之前的对话内容,这会导致回答缺乏上下文关联性,显得机械且不自然。

  2. 个性化体验:通过记住用户先前提供的信息(例如偏好、历史行为等),AI可以提供更加个性化的服务和建议,增强用户体验。这种能力对于客服机器人、个人助手等应用场景尤为重要。

  3. 复杂问题解决:有些问题或任务需要跨多个对话回合进行,可能涉及收集额外的数据或者逐步细化需求。拥有会话记忆可以让AI更有效地管理这些复杂的交互过程,提高解决问题的效率。

  4. 持续学习与改进:虽然这不是传统意义上的"记忆",但一些先进的系统能够从过去的对话中学习,识别常见模式、错误和成功案例,从而不断优化其响应策略。

  5. 维护对话的一致性:在长时间的对话过程中保持角色一致性、立场一致性和事实准确性是非常重要的。会话记忆帮助AI在整个对话过程中维持这些方面的一致性,避免出现自相矛盾的回答。

  6. 增强用户信任:当AI能够记住并正确引用早先的对话细节时,它能给用户留下深刻印象,并增加对AI系统的信任感。这对于建立长期的用户关系至关重要。

综上所述,会话记忆是使AI大模型能够在各种应用场景中实现更加智能、灵活和人性化的关键因素之一。它不仅提高了对话的质量,还为用户提供了一个更加连贯、个性化的交流体验。

2.3 AI 大模型会话记忆常用实现方案

随着大模型技术的广泛使用,AI大模型实现会话记忆的方式有多种,如下:

  1. 基于缓存的记忆:这种方法涉及存储最近几个回合的对话信息,并在处理新的输入时参考这些信息。这可以提供一定程度的上下文,但其容量有限,因为只能记住最近的交互。

  2. 外部数据库或知识图谱:在这种方法中,AI可以通过查询外部数据库或知识图谱来获取长期记忆。这种方式允许AI访问大量信息,但是如何高效地检索和利用这些信息是一个挑战。

  3. 长短期记忆网络(LSTM)和其他递归神经网络(RNNs):这些是专门设计用来处理序列数据的深度学习模型,能够通过内部状态保存一些关于过去事件的记忆。虽然它们能捕捉到一定的上下文信息,但在处理非常长的对话历史时仍面临挑战。

  4. Transformer架构:近年来,Transformer及其变种(如BERT、GPT等)已经成为构建大规模语言模型的基础,这些模型可以同时关注多个输入部分,从而更有效地理解和记忆对话中的关键信息。特别是自注意力机制允许模型对整个对话历史进行编码,以便更好地生成响应。

随着技术的进步,现代AI大模型越来越擅长模拟人类对话,不仅能够回答问题,还能以自然流畅的方式参与复杂的对话,这部分得益于其先进的会话记忆技术。

2.4 LangChain4j 会话记忆介绍

LangChain4j 是 Java 版的 LangChain 实现,提供了构建大模型应用的组件,其中会话记忆(Memory)是核心功能之一。入口:Chat Memory | LangChain4j

2.4.1 LangChain4j 会话记忆介绍

LangChain4j 中的会话记忆是指在与大模型交互过程中,保存和管理对话历史、上下文信息的机制。它使应用能够:

  • 维护多轮对话状态

  • 记住用户偏好和历史交互

  • 提供连贯的对话体验

2.4.2 LangChain4j 会话记忆类型

LangChain4j 提供了多种记忆实现:

1)基础记忆类型

LangChain4j 提供的基础记忆类型主要是其核心接口ChatMemory 下面的几种实现方式,如下:

  1. ChatMemoryStore

    1. 基础接口,定义了记忆存储的基本操作

    2. 主要方法:add(), getMessages(), clear()

  2. MessageWindowChatMemory

    1. 基于滑动窗口的记忆实现

    2. 只保留最近N条消息(可配置)

    3. 防止记忆无限增长

  3. TokenWindowChatMemory

    1. 基于token数量的记忆实现

    2. 保留最近的消息直到达到token限制

    3. 更适合大模型的上下文窗口限制

2)高级记忆类型

高级记忆主要是在实际开发中,借助外部的存储组件进行会话记忆的存储以及相关的扩展点,包括:

  1. PersistentChatMemory

    1. 持久化记忆,可保存到数据库或文件

    2. 支持跨会话记忆

  2. VectorStoreChatMemory

    1. 使用向量存储保存记忆

    2. 支持基于语义的记忆检索

  3. SummaryChatMemory

    1. 自动生成对话摘要作为记忆

    2. 减少需要保存的原始消息数量

三、Langchain4j 会话记忆操作案例使用

接下来通过代码的案例操作,详细介绍Langchain4j 的会话记忆功能的使用。

3.1 前置准备

3.1.1 导入依赖文件

创建一个springboot 工程,pom中导入下面的核心依赖

java 复制代码
<properties>
     <maven.compiler.source>17</maven.compiler.source>
     <maven.compiler.target>17</maven.compiler.target>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <spring-boot.version>3.2.6</spring-boot.version>
     <langchain4j.version>1.0.0-beta3</langchain4j.version>
 </properties>

 <dependencies>

     <!-- web应用程序核心依赖 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

     <!-- 编写和运行测试用例 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
     </dependency>

     <!-- 基于openai系列整合的springboot-starter -->
     <dependency>
         <groupId>dev.langchain4j</groupId>
         <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
     </dependency>

     <!-- 接入阿里云百炼平台 -->
     <dependency>
         <groupId>dev.langchain4j</groupId>
         <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
     </dependency>

     <!--langchain4j高级功能-->
     <dependency>
         <groupId>dev.langchain4j</groupId>
         <artifactId>langchain4j-spring-boot-starter</artifactId>
     </dependency>

     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>2.0.39</version> <!-- 使用最新安全版本 -->
     </dependency>

 </dependencies>

 <dependencyManagement>
     <dependencies>
         <!--引入SpringBoot依赖管理清单-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-dependencies</artifactId>
             <version>${spring-boot.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>

         <!--引入langchain4j依赖管理清单-->
         <dependency>
             <groupId>dev.langchain4j</groupId>
             <artifactId>langchain4j-bom</artifactId>
             <version>${langchain4j.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>

         <!--引入百炼依赖管理清单-->
         <dependency>
             <groupId>dev.langchain4j</groupId>
             <artifactId>langchain4j-community-bom</artifactId>
             <version>${langchain4j.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>

     </dependencies>

 </dependencyManagement>

3.1.2 添加配置文件

在工程配置文件中添加下面的配置内容

java 复制代码
server:
  port: 8082

#直接对接的是deepseek官网的的大模型
langchain4j:
  #阿里百炼平台的模型
  community:
    dashscope:
      chat-model:
        api-key: 你的apikey #这个是百炼平台的apikey
        model-name: qwen-max

logging:
  level:
    root: debug

注意:

如果没有apikey的同学,可以前往阿里云百炼平台注册并获取一个apikey,操作入口:

大模型服务平台百炼控制台

3.1.3 前置案例

1)增加一个 Assistant接口

自定义一个**Assistant,**里面有一个chat方法

java 复制代码
package com.congge.assistant;

import dev.langchain4j.service.spring.AiService;

@AiService
public interface Assistant {
    String chat(String userMessage);
}

2)增加一个测试接口

在工程中增加一个测试接口,如下代码:

java 复制代码
@Resource
private Assistant assistant;

//localhost:8082/chat/test
@GetMapping("/chat/test")
public String chat() {
    String result1 = assistant.chat("我是小王");
    System.out.println(result1);
    String result2 = assistant.chat("你知道我是谁吗?");
    System.out.println(result2);
    return "chat";
}

启动工程后调用该接口,通过控制台输出效果可以发现一个问题,那就是通过这种方式与大模型进行对话,大模型是没有会话记忆的。

3.2 会话记忆的实现

通过上面的接口案例不难发现,直接与大模型对话是没有会话记忆的,如果实现会话记忆功能,就需要引入其他的方式进行实现,下面分别说明。

3.2.1 基于多轮对话存储结果实现会话记忆

保存对话内容实现会话记忆是一种原始但是比较容易实现的方式,具体来说就是,在第一轮对话之后,后面的每一轮对话在调用chat方法的时候,均需要把之前的对话加入到参数里面去。

java 复制代码
package com.congge.controller;

import com.congge.assistant.Assistant;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.response.ChatResponse;

import java.util.Arrays;

@RestController
public class ChatMemoryTestController {

    @Resource
    private Assistant assistant;

    @Autowired
    private QwenChatModel qwenChatModel;

    //localhost:8082/chat/test
    @GetMapping("/chat/test")
    public String chat() {
        String result1 = assistant.chat("我是小王");
        System.out.println(result1);
        String result2 = assistant.chat("你知道我是谁吗?");
        System.out.println(result2);
        return "chat";
    }

    //localhost:8082/chat/memory/v1
    @GetMapping("/chat/memory/v1")
    public String chatMemoryV1() {
        //第一轮对话
        UserMessage userMessage1 = UserMessage.userMessage("我是小王");
        ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);
        AiMessage aiMessage1 = chatResponse1.aiMessage();
        //输出大语言模型回复
        System.out.println(aiMessage1.text());

        //第二轮对话
        UserMessage userMessage2 = UserMessage.userMessage("你知道我是谁吗");
        ChatResponse chatResponse2 = qwenChatModel.chat(
                Arrays.asList(
                        userMessage1,
                        aiMessage1,
                        userMessage2
                )
        );
        AiMessage aiMessage2 = chatResponse2.aiMessage();
        //输出大语言模型的回复
        System.out.println(aiMessage2.text());
        return aiMessage2.text();
    }

}

启动项目后,调用上面的接口,通过返回结果发现,这种方式可以实现多轮会话的记忆功能

3.2.2 基于ChatMemory 实现会话记忆

Langchain4j 提供了ChatMemory组件,基于该组件可以实现会话记忆的功能

从源码中不难发现,ChatMemory是一个接口,理论上,只要是实现了该接口,均可以实现会话记忆功能,在接口的默认实现中,可以看到基于当前的依赖引入下,有下面两个实现

以MessageWindowChatMemory这个实现来说,顾名思义,说明这个实现情况下,会话记忆是存储在内存中,一旦程序重启了会话记忆就丢失了,使用该组件进行一个会话记忆功能,参考下面的代码

java 复制代码
//localhost:8082/chat/memory/v2?userMessage=我是小王
@GetMapping("/chat/memory/v2")
public String chatMemoryV2(@RequestParam("userMessage") String userMessage) {
    //创建chatMemory
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
    //创建AIService
    Assistant assistant = AiServices
            .builder(Assistant.class)
            .chatLanguageModel(qwenChatModel)
            .chatMemory(chatMemory)
            .build();
    //调用service的接口
    String answer1 = assistant.chat(userMessage);
    System.out.println(answer1);
    String answer2 = assistant.chat("我是谁");
    System.out.println(answer2);

    return answer2;
}

调用上面的接口,可以看到通过这种方式可以实现会话记忆的功能

3.2.3 基于ChatMemory 会话记忆升级

上一步基于ChatMemory实现了会话记忆功能,但是细心的同学会发现,这种写法比较繁琐,不方便全局使用和代码复用,于是可以想到,既然上一步用到了AiServices , 那就可以借助AiServices 将会话记忆配置到全局的bean中进行管理。

1)自定义一个全局的ChatMemory的配置类

参考下面的代码,将MessageWindowChatMemory配置为全局bean

java 复制代码
package com.congge.assistant;

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MemoryCharConfig {

    @Bean
    public ChatMemory chatMemory() {
        //设置聊天记忆记录的message数量
        return MessageWindowChatMemory.withMaxMessages(20);
    }

}

2)自定义Assistant

添加一个自定义的Assistant接口,在接口的注解中,引用上一步自定义的chatMemory

java 复制代码
package com.congge.assistant;

import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;

@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "qwenChatModel",
        chatMemory = "chatMemory"
)
public interface ChatMemoryAssistant {

    String chat(String userMessage);

}

3)添加测试接口

添加一个自定义接口用于效果测试,参考下面的代码

java 复制代码
@Resource
private ChatMemoryAssistant chatMemoryAssistant;

//localhost:8082/chat/memory/v3?userMessage=我是小王
@GetMapping("/chat/memory/v3")
public String chatMemoryV3(@RequestParam("userMessage") String userMessage) {
    //调用service的接口
    String answer1 = chatMemoryAssistant.chat(userMessage);
    System.out.println(answer1);
    String answer2 = chatMemoryAssistant.chat("我是谁");
    System.out.println(answer2);
    return answer2;
}

4)效果测试

调用上面的接口,通过返回结果可以看到,通过这种方式也能达到存储会话记忆的功能

3.3 会话隔离实现

在上面的基于chatMemory 实现会话记忆中,细心的同学可以发现一个问题,那就是这样的实现方式无法做到会话隔离,而在实际应用中,不同的用户进行对话,是必须要做会话隔离的,否则A用户的历史会话信息会被B用户看到了,这就出问题了,此时需要借助AiService注解中的 chatMemoryProvider 这个属性配置参数来实现,下面来看代码具体实现过程。

3.3.1 自定义一个Assistant

自定义一个Assistant 接口

  • 在该chat方法中,使用了2个新增的注解@MemoryId和@UserMessage,对传入的参数类型进行了限定

  • 在接口注解中,配置了chatMemoryProvider 这个属性值,在后面还需要配置一个这样的bean

java 复制代码
package com.congge.assistant;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;

@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "qwenChatModel",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatAssistant {

    /**
     * 分离聊天记录
     * @param memoryId 聊天id
     * @param userMessage 用户消息
     * @return
     */
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

3.3.2 自定义chatMemoryProvider 配置bean

自定义一个类,并配置chatMemoryProvider 的bean

java 复制代码
package com.congge.config;

import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SeparateChatAssistantConfig {

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(20)
                .build();
    }
}

3.3.3 添加测试接口

添加一个测试接口,参考下面的代码

java 复制代码
@Resource
private SeparateChatAssistant separateChatAssistant;

//localhost:8082/chat/memory/v5?userId=1&userMessage=我是小王
//localhost:8082/chat/memory/v5?userId=1&userMessage=你知道我是谁吗?
//localhost:8082/chat/memory/v5?userId=2&userMessage=我是小李
//localhost:8082/chat/memory/v5?userId=2&userMessage=你知道我是谁吗?
@GetMapping("/chat/memory/v5")
public String chatMemoryV3(@RequestParam("userId") Integer userId,@RequestParam("userMessage") String userMessage) {
    String answer1 = separateChatAssistant.chat(userId,userMessage);
    return answer1;
}

3.3.4 效果测试

依次调用上面的4个接口,可以看到下面的效果,说明上面的实现方案是正常生效了

1)第一次调用

2)第二次调用

3)第三次调用

4)第四次调用

四、基于Redis实现会话记忆持久化存储

通过上面的案例操作,我们掌握了Langchan4j中会话记忆存储的常用实现方式,但是细心的同学可以发现,这种方式是基于内存的实现,一旦服务重启,或者服务异常宕机了,历史会话就丢失了,而在实际应用开发中,这种方式是肯定不允许存在的,接下来我们使用Redis作为持久化存储数据源来实现。

4.1 前置准备

4.1.1 导入redis依赖

基于上面的工程,在pom文件中导入下redis依赖

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.1.2 添加redis配置信息

在配置文件中添加关于redis的配置信息

java 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379

4.1.3 自定义redis序列化类

为了更好的管理redis中存储的数据,这里添加一个redis的自定义配置类

java 复制代码
package com.congge.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

4.2 会话记忆代码改造

基于会话隔离中的代码我们做一些改造即可。

4.2.1 自定义ChatMemoryStore

ChatMemoryStore是存储记忆会话必须要使用的对象,如果想要实现会话记忆的自定义数据源存储,需要实现该接口,并实现里面的三个核心方法,从源码中可以清楚看到这一点

参考下面的代码

java 复制代码
package com.congge.config;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;

public class RedisMemoryStoreConfig implements ChatMemoryStore {

    private RedisTemplate redisTemplate;

    public RedisMemoryStoreConfig(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String value = (String) redisTemplate.opsForValue().get("chat:" + memoryId.toString());
        if(value == null || value.isEmpty()){
            return List.of();
        }
        return ChatMessageDeserializer.messagesFromJson(value);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        String messages = ChatMessageSerializer.messagesToJson(list);
        redisTemplate.opsForValue().set("chat:" + memoryId.toString(), messages);
    }

    @Override
    public void deleteMessages(Object memoryId) {
        redisTemplate.delete("chat:" + memoryId.toString());
    }
}

4.2.2 ChatMemoryProvider 配置bean改造

在上一节中我们了解到,ChatMemoryProvider 是连接大模型对话的核心配置bean,只需要在ChatMemoryProvider的配置bean中,指定chatMemoryStore使用上面自定义的这个RedisMemoryStoreConfig 即可,参考下面的代码

java 复制代码
package com.congge.config;

import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class SeparateChatAssistantConfig {

    @Resource
    RedisTemplate  redisTemplate;

    //基于内存的实现
//    @Bean
//    public ChatMemoryProvider chatMemoryProvider() {
//        return memoryId -> MessageWindowChatMemory.builder()
//                .id(memoryId)
//                .maxMessages(20)
//                .chatMemoryStore(redisMemoryStoreConfig)
//                .build();
//    }

    //基于redis的实现
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(20)
                .chatMemoryStore(new RedisMemoryStoreConfig(redisTemplate))
                .build();
    }

}

4.2.3 接口效果测试

到这一步,改造就完成了,其实核心就是将chatMemoryStore使用自定义的使用redis的自定义的类替换即可,下面通过几个接口调用验证下效果如何。

1)调用userId=1的接口

依次调用userId=1的两个接口

调用成功后,检查redis中存储的信息,可以看到redis中存储了userId=1的两个序列化之后的会话信息

2)调用userId=2的接口

依次调用userId=2的两个接口

调用成功后,检查redis中存储的信息,可以看到redis中存储了userId=2的两个序列化之后的会话信息

五、写在文末

本文通过较大的篇幅并结合实际案例,详细介绍了Langchain4j会话记忆存储的功能,最后给出了基于外部存储组件Redis实现会话存储的代码演示,有兴趣的同学还可以基于此继续深入研究,本篇到此结束,感谢观看。