langchain4j+redis+持久化存储记忆

langchain4j+redis+持久化存储记忆

一、前言

Langchain4j官方默认模型记忆用内存存储,但是这样只要重启进程,记忆就会消失。这里我们利用redis的持久化存储RDB+AOF来解决这个问题,这样的搭配高效且方便。

二、环境和依赖

Java 17(langchain4j最低支持)

复制代码
<dependency>

    <groupId>dev.langchain4j</groupId>

    <artifactId>langchain4j</artifactId>

    <version>1.3.0</version>

</dependency>



<dependency>

    <groupId>dev.langchain4j</groupId>

    <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>

    <version>1.1.0-beta7</version>

</dependency>



<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

    <version>3.1.5</version>

</dependency>

当然还需要本地redis环境。

三、实战

配置:

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

MemoryConfig

复制代码
@Configuration
public class MemoryConfig {


    // redis
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }

    @Bean
    public RedisChatMemoryStore redisChatMemoryStore(
            StringRedisTemplate redisTemplate,
            ObjectMapper objectMapper) {

        return new RedisChatMemoryStore(redisTemplate, objectMapper);
    }


}

RedisConfig

复制代码
@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // key序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // value序列化(JSON + 支持多态)
        GenericJackson2JsonRedisSerializer jsonSerializer =
                new GenericJackson2JsonRedisSerializer();

        // 设置序列化
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

ChatMessageDTO

复制代码
package cn.langchain4j.ai.dto;



import lombok.Getter;

@Getter
public class ChatMessageDTO {

    private String role;   // user / ai / system
    private String content;

    public ChatMessageDTO() {}

    public ChatMessageDTO(String role, String content) {
        this.role = role;
        this.content = content;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

ChatMessageConverter

复制代码
package cn.langchain4j.ai.memory;


import cn.langchain4j.ai.dto.ChatMessageDTO;
import dev.langchain4j.data.message.*;

public class ChatMessageConverter {

    // LangChain4j -> DTO
    public static ChatMessageDTO toDTO(ChatMessage msg) {

        if (msg instanceof UserMessage m) {
            return new ChatMessageDTO("user", m.singleText());
        }

        if (msg instanceof AiMessage m) {
            return new ChatMessageDTO("ai", m.text());
        }

        if (msg instanceof SystemMessage m) {
            return new ChatMessageDTO("system", m.text());
        }

        throw new IllegalArgumentException("Unknown message type: " + msg.getClass());
    }

    // DTO -> LangChain4j
    public static ChatMessage toDomain(ChatMessageDTO dto) {

        return switch (dto.getRole()) {
            case "user" -> UserMessage.from(dto.getContent());
            case "ai" -> AiMessage.from(dto.getContent());
            case "system" -> SystemMessage.from(dto.getContent());
            default -> throw new IllegalArgumentException("Unknown role: " + dto.getRole());
        };
    }
}

RedisChatMemoryStore

复制代码
package cn.langchain4j.ai.memory;

import cn.langchain4j.ai.dto.ChatMessageDTO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.data.message.ChatMessage;

import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.ArrayList;
import java.util.List;

public class RedisChatMemoryStore implements ChatMemoryStore {

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;

    public RedisChatMemoryStore(StringRedisTemplate redisTemplate,
                                ObjectMapper objectMapper) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
    }

    private String buildKey(Object memoryId) {
        return "chat_memory:" + memoryId;
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        try {
            String json = redisTemplate.opsForValue().get(buildKey(memoryId));

            if (json == null) {
                return new ArrayList<>();
            }

            List<ChatMessageDTO> dtoList =
                    objectMapper.readValue(json,
                            new TypeReference<List<ChatMessageDTO>>() {});

            return dtoList.stream()
                    .map(ChatMessageConverter::toDomain)
                    .toList();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        try {

            List<ChatMessageDTO> dtoList = messages.stream()
                    .map(ChatMessageConverter::toDTO)
                    .toList();

            String json = objectMapper.writeValueAsString(dtoList);

            redisTemplate.opsForValue().set(buildKey(memoryId), json);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

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

AiCodeHelperServiceFactory

复制代码
@Configuration
public class AiCodeHelperServiceFactory {

    @Resource
    private ChatModel qwenChatModel;

    @Resource
    private ContentRetriever contentRetriever;

    @Resource
    private McpToolProvider mcpToolProvider;

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Resource
    private StreamingChatModel streamingChatModel;


    @Bean
    public AiCodeHelperService aiCodeHelperService(){
        // 会话记忆
   
        // 构建
        AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
                .streamingChatModel(streamingChatModel)
                .chatModel(qwenChatModel)

                .chatMemoryProvider(memoryId ->
                        MessageWindowChatMemory.builder()
                                .id(memoryId)
                                .maxMessages(10)
                                .chatMemoryStore(redisChatMemoryStore) // Redis记忆
                                .build()
                )

//                .contentRetriever(contentRetriever) // 内容检索 (启用 RAG)
                .tools(new JavaInfoTool(), new RagTool()) // 工具
                .toolProvider(mcpToolProvider) // mcp
                .build();
        return aiCodeHelperService;
    }

}
相关推荐
野生技术架构师2 小时前
牛客网热门Java 面试题汇总,查漏补缺;多线程 +spring+JVM 调优 + 分布式 +redis+ 算法
java·jvm·spring
txxzjmzlh2 小时前
Thread 类的基本用法
java·开发语言
好家伙VCC2 小时前
**基于RISC-V架构的嵌入式系统开发:从零开始构建高效低功耗应用**在当前物联网(IoT)和边缘计
java·python·物联网·架构·risc-v
wyu729612 小时前
Spring框架学习笔记:从IoC到声明式事务
java
qqacj2 小时前
Spring Security 官网文档学习
java·学习·spring
Rsun045512 小时前
10、Java 桥接模式从入门到实战
java·开发语言·桥接模式
金銀銅鐵2 小时前
[Java] 从 class 文件看 cglib 对 MethodInterceptor 的处理 (下)
java·后端
lee_curry3 小时前
Java中关于“锁”的那些事
java·线程·并发·juc
pearlthriving3 小时前
c++当中的泛型思想以及c++11部分新特性
java·开发语言·c++