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;
    }

}
相关推荐
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ7 小时前
通过java后端代码来实现给word内容补充格式文本内容控件,以及 设置控件的标记和标题
java·c#·word
無限進步D8 小时前
Java 面向对象高级 接口
java·开发语言
逸Y 仙X9 小时前
文章二十七:ElasticSearch ES查询模板(Search Template)高效复用实战
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
二哈赛车手9 小时前
新人笔记---Spring AI的Advisor以及其底层机制讲解(涉及源码),包含一些遇见的Spring AI的Advisor缺陷问题的解决方案
java·人工智能·spring boot·笔记·spring
薪火铺子9 小时前
Redis 缓存三大问题与解决方案
redis·spring·缓存
AC赳赳老秦9 小时前
接口测试自动化:用 OpenClaw 对接 Postman,实现批量回归测试、测试报告自动生成与推送
java·人工智能·python·算法·elasticsearch·deepseek·openclaw
两年半的个人练习生^_^9 小时前
Java日志框架和使用、日志记录规范
java·开发语言·开发规范
pq21710 小时前
最简单的理解synchronized锁升级
java
杨凯凡10 小时前
【032】排查入门:jstack、heap dump、Arthas 初识
java·开发语言·后端