第五章Langchain4j之基于内存和redis实现聊天持久化

📁langchain4j基于内存的聊天记忆实现

创建子模块langchain4j-08chat-memory

📦1.pom.xml配置文件

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.yumeko.stduy</groupId>
        <artifactId>langchain4j</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>langchain4j-08chat-memory</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--langchain4j-open-ai + langchain4j + langchain4j-reactor-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

⚙️2.application.properties配置文件

复制代码
server.port=9008
spring.application.name=langchain4j-08chat-memory
# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

🔧3.service服务

复制代码
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

public interface ChatMemoryAssistant {

    /**
     * 聊天带记忆缓存功能
     * @param userId 用户 ID
     * @param prompt 消息
     * @return {@link String }
     */
    String chatWithChatMemory(@MemoryId Long userId, @UserMessage String prompt);
}

🤖4.AI模型配置类

复制代码
import com.yumeko.study.service.ChatAssistant;
import com.yumeko.study.service.ChatMemoryAssistant;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.TokenCountEstimator;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiTokenCountEstimator;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {
    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("qwen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean(name = "chat")
    public ChatAssistant chatAssistant(ChatModel chatModel) {
        return AiServices.create(ChatAssistant.class, chatModel);
    }


    /**
     * @Description: 按照MessageWindowChatMemory方式配置聊天记忆
     * 知识出处,https://docs.langchain4j.dev/tutorials/chat-memory/#eviction-policy
     * <p>
     * 该Bean提供基于消息数量限制的聊天记忆功能:
     * 1. 使用MessageWindowChatMemory实现聊天历史记录的存储和管理
     * 2. 限制最大保存100条消息记录(包括用户和AI的消息)
     * 3. 当消息超过限制时,会自动移除最旧的消息以保持窗口大小
     * 4. 通过memoryId参数区分不同用户的聊天上下文
     * <p>
     * MessageWindowChatMemory特点:
     * - 基于消息条数进行驱逐策略(eviction policy)
     * - 简单直接,适合对token数量控制要求不严格的场景
     * - 每个memoryId维护独立的聊天上下文
     */
    @Bean(name = "chatMessageWindowChatMemory")
    public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel) {

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                // 按照memoryId对应创建了一个chatMemory,每个memoryId代表一个独立的聊天上下文
                // MessageWindowChatMemory.withMaxMessages(100) 设置最大保存100条消息
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
                .build();
    }

    /**
     * @Description: 按照TokenWindowChatMemory方式配置聊天记忆
     * <p>
     * 该Bean提供基于Token数量限制的聊天记忆功能:
     * 1. 使用TokenWindowChatMemory实现聊天历史记录的存储和管理
     * 2. 限制聊天历史记录的总Token数量不超过1000个Token
     * 3. 当Token数量超过限制时,会自动移除最旧的消息以保持Token窗口大小
     * 4. 通过memoryId参数区分不同用户的聊天上下文
     * <p>
     * TokenWindowChatMemory特点:
     * - 基于Token数量进行驱逐策略(eviction policy),更精确地控制内存使用
     * - 适合需要精确控制token消耗的场景,避免超出模型的token限制
     * - 每个memoryId维护独立的聊天上下文
     * <p>
     * 注意事项:
     * - 需要TokenCountEstimator来计算每条消息的token数量
     * - 这里使用OpenAiTokenCountEstimator作为token计算器
     */
    @Bean(name = "chatTokenWindowChatMemory")
    public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel) {
        // 创建TokenCountEstimator,默认使用OpenAI的token分词器
        // TokenCountEstimator用于计算ChatMessage的token数量
        // 这里使用"gpt-4"模型的分词器进行token计算
        TokenCountEstimator openAiTokenCountEstimator = new OpenAiTokenCountEstimator("gpt-5");

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                // 配置chatMemoryProvider,使用TokenWindowChatMemory
                // TokenWindowChatMemory.withMaxTokens(1000, openAiTokenCountEstimator)
                // 第一个参数:最大token数量限制为1000
                // 第二个参数:token计数器,用于计算每条消息的token数量
                .chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000, openAiTokenCountEstimator))
                .build();
    }
}

🌐5.Web控制器

复制代码
import cn.hutool.core.date.DateUtil;
import com.yumeko.study.service.ChatAssistant;
import com.yumeko.study.service.ChatMemoryAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class ChatMemoryController {

    @Resource(name = "chatMessageWindowChatMemory")
    private ChatMemoryAssistant chatMessageWindowChatMemory;

    @Resource(name = "chatTokenWindowChatMemory")
    private ChatMemoryAssistant chatTokenWindowChatMemory;

    /**
     * @Description: MessageWindowChatMemory实现聊天功能
     */
    @GetMapping(value = "/chatmemory/test2")
    public String chatMessageWindowChatMemory() {
        chatMessageWindowChatMemory.chatWithChatMemory(1L, "你好!我的名字是Java.");
        String answer01 = chatMessageWindowChatMemory.chatWithChatMemory(1L, "我的名字是什么");
        System.out.println("answer01返回结果:" + answer01);

        chatMessageWindowChatMemory.chatWithChatMemory(3L, "你好!我的名字是python");
        String answer02 = chatMessageWindowChatMemory.chatWithChatMemory(3L, "我的名字是什么");
        System.out.println("answer02返回结果:" + answer02);

        return "chatMessageWindowChatMemory success : "
                + DateUtil.now() + "<br> \n\n answer01: " + answer01 + "<br> \n\n answer02: " + answer02;

    }

    /**
     * @Description: TokenWindowChatMemory实现聊天功能
     */
    @GetMapping(value = "/chatmemory/test3")
    public String chatTokenWindowChatMemory() {
        chatTokenWindowChatMemory.chatWithChatMemory(1L, "你好!我的名字是mysql");
        String answer01 = chatTokenWindowChatMemory.chatWithChatMemory(1L, "我的名字是什么");
        System.out.println("answer01返回结果:" + answer01);

        chatTokenWindowChatMemory.chatWithChatMemory(3L, "你好!我的名字是redis");
        String answer02 = chatTokenWindowChatMemory.chatWithChatMemory(3L, "我的名字是什么");
        System.out.println("answer02返回结果:" + answer02);

        return "chatTokenWindowChatMemory success : "
                + DateUtil.now() + "<br> \n\n answer01: " + answer01 + "<br> \n\n answer02: " + answer02;
    }
}

🧪6.测试接口

http://localhost:9008/chatmemory/test2

http://localhost:9008/chatmemory/test3


📁langchain4j持久化聊天记忆

创建子模块langchain4j-10chat-persistence

📦1.pom.xml配置文件

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.yumeko.stduy</groupId>
        <artifactId>langchain4j</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>langchain4j-10chat-persistence</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--langchain4j-open-ai + langchain4j + langchain4j-reactor-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <!--spring-boot-starter-data-redis
            https://docs.langchain4j.dev/tutorials/chat-memory#persistence
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

⚙️2.application.properties配置文件

复制代码
server.port=9010

spring.application.name=langchain4j-10chat-persistence

# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true


# ==========config redis===============
spring.data.redis.host=192.168.88.129
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.password=yumeko
spring.data.redis.connect-timeout=3s
spring.data.redis.timeout=2s

🔧3.service服务

复制代码
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

public interface ChatPersistenceAssistant {
    /**
     * 聊天
     *
     * @param userId  用户 ID
     * @param message 消息
     * @return {@link String }
     */
    String chat(@MemoryId Long userId, @UserMessage String message);
}

🤖4.AI模型配置类

复制代码
import lombok.extern.slf4j.Slf4j;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactor);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

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 jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * Redis聊天记忆存储实现类
 * 
 * 该类实现了ChatMemoryStore接口,使用Redis作为聊天历史记录的持久化存储方案。
 * 它负责将用户的聊天记录序列化后存储到Redis中,并在需要时反序列化取出,
 * 实现了聊天上下文的持久化保存,即使应用重启也不会丢失历史记录。
 * 
 * 主要功能:
 * 1. 将聊天消息序列化并存储到Redis
 * 2. 从Redis中获取并反序列化聊天消息
 * 3. 删除指定用户的聊天记录
 */
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {

    /**
     * Redis中聊天记录的键前缀
     * 所有聊天记录将以 "CHAT_MEMORY:{memoryId}" 的格式存储在Redis中
     */
    public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";

    /**
     * Redis操作模板,用于执行各种Redis操作
     * 通过@Resource注解自动注入Spring容器中的RedisTemplate实例
     */
    @Resource
    private RedisTemplate<String, String> redisTemplate;


    /**
     * 根据memoryId获取存储的聊天消息列表
     * 
     * @param memoryId 用户会话标识符,用于区分不同用户的聊天记录
     * @return 该用户的所有聊天消息列表,如果不存在则返回空列表
     * 
     * 实现原理:
     * 1. 构造Redis键名:CHAT_MEMORY_PREFIX + memoryId
     * 2. 从Redis中获取对应的值(JSON字符串)
     * 3. 使用ChatMessageDeserializer将JSON字符串反序列化为ChatMessage列表
     */
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);

        return ChatMessageDeserializer.messagesFromJson(retValue);
    }

    /**
     * 更新指定memoryId的聊天消息列表
     * 
     * @param memoryId 用户会话标识符,用于区分不同用户的聊天记录
     * @param messages 需要更新的聊天消息列表
     * 
     * 实现原理:
     * 1. 使用ChatMessageSerializer将ChatMessage列表序列化为JSON字符串
     * 2. 构造Redis键名:CHAT_MEMORY_PREFIX + memoryId
     * 3. 将序列化后的JSON字符串存储到Redis中
     * 
     * 持久化特点:
     * - 数据以字符串形式存储在Redis中,便于查看和调试
     * - 每个用户的聊天记录独立存储,通过memoryId进行隔离
     * - Redis的高性能保证了聊天记录读写的效率
     */
    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        redisTemplate.opsForValue()
                .set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));
    }

    /**
     * 删除指定memoryId的聊天消息
     * 
     * @param memoryId 用户会话标识符,用于定位需要删除的聊天记录
     * 
     * 使用场景:
     * - 用户主动清除聊天历史
     * - 系统定期清理过期聊天记录
     * - 隐私保护需求下的数据删除
     */
    @Override
    public void deleteMessages(Object memoryId) {
        redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
    }
}

import com.yumeko.study.service.ChatPersistenceAssistant;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {
    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("qwen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
     * @Description: 配置持久化聊天记忆助手
     * 
     * 该Bean提供具备持久化能力的聊天记忆功能:
     * 1. 使用Redis作为聊天记录的持久化存储
     * 2. 结合MessageWindowChatMemory实现聊天历史记录的管理
     * 3. 限制最大保存1000条消息记录
     * 4. 通过memoryId参数区分不同用户的聊天上下文
     * 
     * 实现原理:
     * 1. 创建ChatMemoryProvider,用于根据memoryId提供独立的聊天记忆实例
     * 2. 使用MessageWindowChatMemory作为聊天记忆的实现方式
     * 3. 通过redisChatMemoryStore实现聊天记录的持久化存储
     * 4. 构建AiServices服务,将chatModel和chatMemoryProvider整合
     * 
     * 持久化特点:
     * - 聊天记录存储在Redis中,即使应用重启也不会丢失
     * - 每个用户(通过memoryId区分)拥有独立的聊天上下文
     * - 支持大规模并发用户访问,通过Redis实现高性能读写
     * 
     * 相关组件:
     * - redisChatMemoryStore: Redis存储实现
     * - MessageWindowChatMemory: 基于消息窗口的聊天记忆管理
     * - ChatPersistenceAssistant: 持久化聊天助手接口
     */
    @Bean
    public ChatPersistenceAssistant chatMemoryAssistant(ChatModel chatModel) {

        // 创建聊天记忆提供者,用于根据memoryId提供独立的聊天记忆实例
        // MessageWindowChatMemory.builder() 构建基于消息窗口的聊天记忆管理器
        // .id(memoryId) 设置记忆实例的唯一标识
        // .maxMessages(1000) 设置最大保存1000条消息
        // .chatMemoryStore(redisChatMemoryStore) 设置使用Redis作为持久化存储
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(1000)
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        // 构建AiServices服务
        // 指定ChatPersistenceAssistant接口作为服务接口
        // .chatModel(chatModel) 设置使用的聊天模型
        // .chatMemoryProvider(chatMemoryProvider) 设置聊天记忆提供者
        return AiServices.builder(ChatPersistenceAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();
    }

}

🌐5.Web控制器

复制代码
import cn.hutool.core.date.DateUtil;
import com.yumeko.study.service.ChatPersistenceAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class ChatPersistenceController {
    @Resource
    private ChatPersistenceAssistant chatPersistenceAssistant;

    // http://localhost:9010/chatpersistence/redis
    @GetMapping(value = "/chatpersistence/redis")
    public String testChatPersistence() {
        chatPersistenceAssistant.chat(1L, "你好!我的名字是redis");
        chatPersistenceAssistant.chat(2L, "你好!我的名字是nacos");

        String chat = chatPersistenceAssistant.chat(1L, "我的名字是什么");
        System.out.println(chat);

        chat = chatPersistenceAssistant.chat(2L, "我的名字是什么");
        System.out.println(chat);

        return "testChatPersistence success : " + DateUtil.now();
    }

}

🧪6.测试接口

http://localhost:9010/chatpersistence/redis

相关推荐
IndulgeCui9 小时前
【金仓数据库产品体验官】KSQL Developer Linux版安装使用体验
linux·运维·数据库
半旧夜夏9 小时前
【分布式缓存】Redis持久化和集群部署攻略
java·运维·redis·分布式·缓存
一马平川的大草原9 小时前
基于n8n实现数据库多表数据同步
数据库·数据同步·dify·n8n
老华带你飞11 小时前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·商城推荐系统
一 乐11 小时前
物业管理系统|小区物业管理|基于SprinBoot+vue的小区物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
这周也會开心11 小时前
Spring框架
java·数据库·spring
gys989512 小时前
uniapp使用sqlite模块
数据库·sqlite·uni-app
哦你看看12 小时前
Redis Sentinel哨兵集群
linux·redis·bootstrap·sentinel
凌冰_13 小时前
Java Maven+lombok+MySql+HikariCP 操作数据库
java·数据库·maven