【LangChain4j+Redis】会话记忆功能实现

一、介绍

接着上文继续实现会话记忆功能

前置后端内容: https://blog.csdn.net/2401_84926677/article/details/151962893

前置前端内容:https://blog.csdn.net/2401_84926677/article/details/152051570

Langchain4j 的会话记忆功能核心作用是为 AI 对话提供 "上下文感知能力",解决大语言模型(LLM)默认 "无状态" 的问题 ------ 即让 AI 能记住多轮交互中的历史消息、用户偏好和对话逻辑,从而实现更连贯、个性化且符合场景的对话体验。

二、步骤

1. 定义会话记忆对象

创建config包下的CommonConfig类

java 复制代码
@Configuration
public class CommonConfig {

    @Bean 
    public ChatMemory chatMemory() {

        MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
                .maxMessages(20)
                .build();
        return memory;
    }

}

@Bean表示加入到 IOC 容器里

2. 配置会话记忆对象

在@AiService注解里设置chatMemory,配置会话记忆对象

java 复制代码
@AiService(
  wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "openAiChatModel",
        streamingChatModel = "openAiStreamingChatModel",
        chatMemory = "chatMemory"   //这里的chatMemory是配置类@Bean注解下的方法名
)
public interface ConsultantService {
    // @SystemMessage(fromResource = "system.txt")

    @UserMessage("你是一位厨师,{{love}}")
    public Flux<String> chat(@V("love")String message);
}

3. 效果测试

后端请求的日志输出,已经携带了之前的会话历史记录:

但是!如果另一个用户进行访问,这个会话记忆是什么样的?

我们打开另一个浏览器访问,并继续提问:

---------------发现它仍然和上一个用户的关联着,没有实现隔离!----------------

三、会话记忆隔离实现

1. 定义会话记忆对象提供者

在config包下的,CommonConfig类中加入ChatMemoryProvider的定义

java 复制代码
    //构建ChatMemoryProvider对象
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        ChatMemoryProvider chatMemoryProvider=new ChatMemoryProvider(){
            @Override
            public ChatMemory get(Object memoryId) {
                return MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(20)
                        .build();
            }
        };
        return chatMemoryProvider;
    }

2. 配置会话记忆对象提供者

修改@AiService注解里的内容:加入chatMemoryProvider,去掉chatMemory

java 复制代码
@AiService(
  wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "openAiChatModel",
        streamingChatModel = "openAiStreamingChatModel",
        // chatMemory = "chatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface ConsultantService {
     @SystemMessage(fromResource = "system.txt")
     //@UserMessage("你是一位厨师,{{love}}")
    public Flux<String> chat(String message);
}

3. ConsultantService接口方法中添加参数MemoryId

在接口方法的参数加入memoryId,并加上相关注解标明这两个参数

java 复制代码
@AiService(
  wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "openAiChatModel",
        streamingChatModel = "openAiStreamingChatModel",
        // chatMemory = "chatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface ConsultantService {
     @SystemMessage(fromResource = "system.txt")
     //@UserMessage("你是一位厨师,{{love}}")
    public Flux<String> chat(@MemoryId String memoryId,@UserMessage String message);
}

4. Controller中chat接口接收memoryId

在控制器方法和返回值中加入memoryId参数

java 复制代码
@RestController
@RequestMapping("/api")
@CrossOrigin
public class ChatController {

    @Autowired
    private ConsultantService consultantService;

    @GetMapping(value = "/chat",produces = "text/html;charset=utf-8")
    public Flux<String> chat(@RequestParam String memoryId, @RequestParam String message) {
        return consultantService.chat(memoryId, message);
    }

}

5. 前端页面请求时传递memoryId

新增会话按钮,设置点击事件:

html 复制代码
 <button @click="newSession" class="new-session-btn">+</button>
javascript 复制代码
/**
 * 创建新会话
 */
const newSession = () => {
  // 生成新的memoryId
  memoryId.value = Date.now().toString()
  // 清空消息
  messages.value = []
}

发送请求时加入memoryId参数:

javascript 复制代码
 // 调用后端API,发送用户消息,包含memoryId参数
    const response = await fetch(`http://localhost:8080/api/chat?memoryId=${encodeURIComponent(memoryId.value)}&message=${encodeURIComponent(messageToSend)}`)
   

6. 效果测试

第一个用户页面:

第二个用户页面:

可见并没有携带第一个用户的会话,效果达成!

但是!如果系统重启,岂不是就都没有了?

重启后接着问:

可见,系统或者服务器重启后,会话记忆历史也就丢失了,那么就需要再次改进。

四、会话记忆持久化

想要实现持久化,那么必须通过外部的存储工具,例如redis,mysql或者其他存储工具

这里使用redis实现会话记忆持久化:

1. redis环境准备

这里使用VMware虚拟机linux的CentOS系统,通过Docker容器化安装部署Redis

bash 复制代码
sudo docker run -d \
  --name redis \
  -p 6379:6379 \
  -v /usr/local/redis/data:/data \
  redis:latest \
  redis-server --appendonly yes

然后安装Redis的可视化工具,这里使用Redis Insight创建连接:

2. pom.xml文件引入redis起步依赖

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

3. 配置redis连接信息

复制代码
配置application.yaml文件
javascript 复制代码
spring:
  data:
    redis:
      host: 192.168.1.128
      port: 6379

4. 提供ChatMemoryStore实现类

创建repository包,创建RedisChatMemoryStore类实现ChatMemoryStore接口

java 复制代码
@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
    //注入redisTemplate
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        // 获取会话消息
        String json = redisTemplate.opsForValue().get(memoryId);
        // 把json数据转成List<ChatMessage>
        List<ChatMessage> list = ChatMessageDeserializer.messagesFromJson(json);
        return list;
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        // 更新会话消息
        //1.把list转成json数据
        String json = ChatMessageSerializer.messagesToJson(list);

        //2.保存到redis中
        redisTemplate.opsForValue().set( memoryId.toString(), json, Duration.ofDays(1));
    }

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

5. 配置ChatMemoryStore

配置类配置ChatMemoryStore

java 复制代码
@Configuration
public class CommonConfig {

    @Autowired
    private RedisChatMemoryStore redisChatMemoryStore;  // 注入RedisChatMemoryStore

    @Bean
    public ChatMemory chatMemory() {

        MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
                .maxMessages(20)
                .build();
        return memory;
    }

    //构建ChatMemoryProvider对象
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        ChatMemoryProvider chatMemoryProvider=new ChatMemoryProvider(){
            @Override
            public ChatMemory get(Object memoryId) {
                return MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(20)
                        .chatMemoryStore(redisChatMemoryStore) // 设置存储对象
                        .build();
            }
        };
        return chatMemoryProvider;
    }

}

6. 效果测试

重启项目,进行测试

------------会话记忆正常-------------

那么我们重启一下后端再试试:

------------依旧可以!我们来看看后端-------------

下面我们打开redis看一下

会话历史记录已成功存入Redis缓存,实现了会话历史的限时持久化存储

相关推荐
StevenGerrad2 小时前
【读书笔记】深入理解JVM C6-9 虚拟机执行子系统
java·jvm
API开发2 小时前
apiSQL+GoView:一个API接口开发数据大屏
前端·后端·api·数据可视化·数据大屏·apisql
Z_z在努力2 小时前
【数据结构】队列(Queue)全面详解
java·开发语言·数据结构
aloha_7892 小时前
新国都面试真题
jvm·spring boot·spring·面试·职场和发展
你的人类朋友2 小时前
🍃说说Base64
前端·后端·安全
凯哥19702 小时前
Vue 3 + Supabase 认证与授权时序最佳实践指南
前端·后端
唐叔在学习2 小时前
pip安装太慢?一键切换国内镜像源,速度飞起!
后端·python
BingoGo2 小时前
PHP 8.2 vs PHP 8.3 对比:新功能、性能提升和迁移技巧
后端·php