【Langchain4j-Java AI开发】05-对话记忆管理

LangChain4j 对话记忆管理

概述

对话记忆(Chat Memory)是实现上下文对话的关键。LangChain4j 提供了多种记忆管理策略,让 AI 能够记住之前的对话内容,实现连贯的多轮对话。

为什么需要记忆?

无记忆的对话

java 复制代码
ChatLanguageModel model = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName(GPT_4_O_MINI)
        .build();

String response1 = model.generate("我叫张三");
System.out.println(response1);  // 你好,张三!

String response2 = model.generate("我叫什么名字?");
System.out.println(response2);  // 对不起,我不知道您的名字。
// ❌ 模型无法记住之前的对话

有记忆的对话

java 复制代码
interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(model)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(10))  // 添加记忆
        .build();

String response1 = assistant.chat("我叫张三");
System.out.println(response1);  // 你好,张三!

String response2 = assistant.chat("我叫什么名字?");
System.out.println(response2);  // 你叫张三。
// ✅ 模型记住了之前的对话

记忆类型

1. MessageWindowChatMemory(消息窗口记忆)

按消息数量限制记忆,保留最近的 N 条消息。

代码示例

java 复制代码
package dev.langchain4j.example;

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;

import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;

public class MessageWindowExample {

    interface Assistant {
        String chat(String message);
    }

    public static void main(String[] args) {

        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName(GPT_4_O_MINI)
                .build();

        // 保留最近 10 条消息(5 轮对话)
        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(model)
                .chatMemory(chatMemory)
                .build();

        // 对话测试
        System.out.println(assistant.chat("我叫张三"));
        System.out.println(assistant.chat("我今年 30 岁"));
        System.out.println(assistant.chat("我住在北京"));
        System.out.println(assistant.chat("总结一下我的信息"));
        // 输出: 你叫张三,今年30岁,住在北京。
    }
}

特点

  • ✅ 简单直接
  • ✅ 易于控制对话长度
  • ❌ 可能因消息过长导致超出 Token 限制

2. TokenWindowChatMemory(Token 窗口记忆)

按 Token 数量限制记忆,更精确地控制上下文长度。

代码示例 (参考:tutorials/src/main/java/_05_Memory.java

java 复制代码
package dev.langchain4j.example;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiTokenizer;

import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;

public class TokenWindowExample {

    public static void main(String[] args) {

        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName(GPT_4_O_MINI)
                .build();

        // 创建 Token 计数器
        OpenAiTokenizer tokenizer = new OpenAiTokenizer(GPT_4_O_MINI);

        // 限制为 1000 个 tokens
        ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(1000, tokenizer);

        // 添加系统消息
        SystemMessage systemMessage = SystemMessage.from(
                "你是一位资深的 Java 开发工程师,擅长解决技术问题。"
        );
        chatMemory.add(systemMessage);

        // 添加用户消息
        UserMessage userMessage1 = UserMessage.from(
                "如何优化 Spring Boot 应用的启动速度?"
        );
        chatMemory.add(userMessage1);

        // 发送到模型
        String response1 = model.generate(chatMemory.messages()).content().text();
        System.out.println("AI: " + response1);

        // 将 AI 响应也添加到记忆
        chatMemory.add(AiMessage.from(response1));

        // 继续对话
        UserMessage userMessage2 = UserMessage.from("具体说说懒加载怎么实现?");
        chatMemory.add(userMessage2);

        String response2 = model.generate(chatMemory.messages()).content().text();
        System.out.println("\nAI: " + response2);

        chatMemory.add(AiMessage.from(response2));

        // 查看当前记忆中的消息数量
        System.out.println("\n当前记忆中的消息数: " + chatMemory.messages().size());
    }
}

特点

  • ✅ 精确控制 Token 使用
  • ✅ 避免超出模型上下文限制
  • ✅ 适合长对话场景

3. 自定义记忆策略

你可以实现自己的记忆策略:

java 复制代码
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.ChatMemory;

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

public class SummaryMemory implements ChatMemory {

    private final List<ChatMessage> messages = new ArrayList<>();
    private final ChatLanguageModel summaryModel;
    private final int maxMessages;

    public SummaryMemory(ChatLanguageModel summaryModel, int maxMessages) {
        this.summaryModel = summaryModel;
        this.maxMessages = maxMessages;
    }

    @Override
    public void add(ChatMessage message) {
        messages.add(message);

        // 当消息过多时,总结前面的对话
        if (messages.size() > maxMessages) {
            String summary = summarizeOldMessages();
            messages.clear();
            messages.add(SystemMessage.from("之前的对话摘要:" + summary));
            messages.add(message);
        }
    }

    @Override
    public List<ChatMessage> messages() {
        return new ArrayList<>(messages);
    }

    @Override
    public void clear() {
        messages.clear();
    }

    private String summarizeOldMessages() {
        // 使用 LLM 总结历史对话
        String conversation = messages.stream()
                .map(ChatMessage::text)
                .collect(Collectors.joining("\n"));

        return summaryModel.generate("总结以下对话的要点:\n" + conversation);
    }
}

多用户记忆管理

使用 ChatMemoryStore 持久化记忆

java 复制代码
package dev.langchain4j.example;

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;

import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;

public class MultiUserMemoryExample {

    interface Assistant {
        String chat(@MemoryId int userId, @UserMessage String message);
    }

    public static void main(String[] args) {

        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName(GPT_4_O_MINI)
                .build();

        // 创建共享的内存存储(可以替换为数据库实现)
        ChatMemoryStore store = new InMemoryChatMemoryStore();

        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(model)
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(20)
                        .chatMemoryStore(store)
                        .build())
                .build();

        // 模拟多个用户的对话
        System.out.println("=== 用户 1 ===");
        System.out.println(assistant.chat(1, "我想买一台笔记本电脑"));
        System.out.println(assistant.chat(1, "预算 5000 元左右"));

        System.out.println("\n=== 用户 2 ===");
        System.out.println(assistant.chat(2, "我想买一部手机"));
        System.out.println(assistant.chat(2, "主要用来拍照"));

        System.out.println("\n=== 用户 1(继续) ===");
        System.out.println(assistant.chat(1, "我之前说预算是多少?"));
        // 输出: 你之前说预算是5000元左右。

        System.out.println("\n=== 用户 2(继续) ===");
        System.out.println(assistant.chat(2, "我之前说主要用来做什么?"));
        // 输出: 你之前说主要用来拍照。
    }
}

持久化到数据库

LangChain4j 支持多种持久化方案:

java 复制代码
// 使用 PostgreSQL
import dev.langchain4j.store.memory.chat.postgresql.PostgresChatMemoryStore;

ChatMemoryStore store = PostgresChatMemoryStore.builder()
        .dataSource(dataSource)
        .tableName("chat_memory")
        .build();

// 使用 MongoDB
import dev.langchain4j.store.memory.chat.mongodb.MongoDbChatMemoryStore;

ChatMemoryStore store = MongoDbChatMemoryStore.builder()
        .mongoClient(mongoClient)
        .databaseName("langchain4j")
        .collectionName("chat_memory")
        .build();

// 使用 Redis
import dev.langchain4j.store.memory.chat.redis.RedisChatMemoryStore;

ChatMemoryStore store = RedisChatMemoryStore.builder()
        .host("localhost")
        .port(6379)
        .build();

与 AI Service 集成

方式 1: 使用默认记忆

java 复制代码
Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(model)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
        .build();

方式 2: 使用 ChatMemoryProvider

适用于多用户场景:

java 复制代码
Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(model)
        .chatMemoryProvider(memoryId -> {
            // 为每个 memoryId 创建独立的记忆
            return MessageWindowChatMemory.builder()
                    .id(memoryId)
                    .maxMessages(10)
                    .chatMemoryStore(store)
                    .build();
        })
        .build();

方式 3: 使用 @MemoryId 注解

java 复制代码
interface Assistant {
    String chat(@MemoryId String sessionId, @UserMessage String message);
}

// 使用不同的 sessionId 隔离对话
assistant.chat("session-001", "你好");
assistant.chat("session-002", "你好");

记忆管理实战示例

客服机器人示例

代码示例 (参考:tutorials/src/main/java/_09_ServiceWithPersistentMemoryForEachUserExample.java

java 复制代码
package dev.langchain4j.example;

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;

import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;

public class CustomerServiceBot {

    interface CustomerSupport {
        @SystemMessage({
                "你是一位专业的客服代表。",
                "你友好、耐心、乐于助人。",
                "你会记住用户之前提到的所有信息。"
        })
        String chat(@MemoryId String customerId, @UserMessage String message);
    }

    public static void main(String[] args) {

        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName(GPT_4_O_MINI)
                .build();

        CustomerSupport support = AiServices.builder(CustomerSupport.class)
                .chatModel(model)
                .chatMemoryProvider(customerId -> MessageWindowChatMemory.builder()
                        .id(customerId)
                        .maxMessages(100)
                        .chatMemoryStore(new InMemoryChatMemoryStore())
                        .build())
                .build();

        // 客户 A 的对话
        String customerA = "CUSTOMER_A";
        System.out.println("=== 客户 A ===");
        System.out.println(support.chat(customerA, "你好,我订单号是 12345"));
        System.out.println(support.chat(customerA, "我想查询物流信息"));
        System.out.println(support.chat(customerA, "我的订单号是多少?"));
        // 输出: 您的订单号是12345。

        // 客户 B 的对话(完全独立)
        String customerB = "CUSTOMER_B";
        System.out.println("\n=== 客户 B ===");
        System.out.println(support.chat(customerB, "我想退货"));
        System.out.println(support.chat(customerB, "订单号是 67890"));
        System.out.println(support.chat(customerB, "我的订单号是多少?"));
        // 输出: 您的订单号是67890。
    }
}

技术支持对话示例

java 复制代码
interface TechnicalSupport {
    @SystemMessage({
            "你是一位资深的技术支持工程师。",
            "你会记录用户的问题和已经尝试的解决方案。",
            "在提供新建议前,先回顾之前的对话。"
    })
    String assist(@MemoryId String ticketId, @UserMessage String message);
}

// 使用
TechnicalSupport support = AiServices.builder(TechnicalSupport.class)
        .chatModel(model)
        .chatMemoryProvider(ticketId -> TokenWindowChatMemory.builder()
                .id(ticketId)
                .maxTokens(2000)
                .tokenCountEstimator(new OpenAiTokenizer(GPT_4_O_MINI))
                .chatMemoryStore(new InMemoryChatMemoryStore())
                .build())
        .build();

String ticket = "TICKET-001";
support.assist(ticket, "我的应用启动失败");
support.assist(ticket, "错误信息是:OutOfMemoryError");
support.assist(ticket, "我已经设置了 -Xmx2g,还是失败");
support.assist(ticket, "总结一下我遇到的问题和已尝试的方案");

记忆清理与管理

清空记忆

java 复制代码
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);

// 添加消息
memory.add(UserMessage.from("你好"));
memory.add(AiMessage.from("你好!有什么可以帮你的?"));

// 清空记忆
memory.clear();

// 记忆已被清空
System.out.println(memory.messages().size());  // 0

手动管理消息

java 复制代码
import dev.langchain4j.data.message.ChatMessage;
import java.util.List;

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);

// 添加消息
memory.add(UserMessage.from("消息1"));
memory.add(AiMessage.from("回复1"));

// 获取所有消息
List<ChatMessage> messages = memory.messages();

// 手动过滤或处理消息
List<ChatMessage> filteredMessages = messages.stream()
        .filter(msg -> !msg.text().contains("敏感词"))
        .collect(Collectors.toList());

记忆策略对比

策略 优点 缺点 适用场景
MessageWindowChatMemory 简单直接 可能超出 Token 限制 短对话、简单场景
TokenWindowChatMemory 精确控制 Token 需要 Tokenizer 长对话、成本敏感
摘要记忆 保留完整上下文 需要额外 LLM 调用 超长对话
持久化记忆 跨会话保留 需要外部存储 多会话、生产环境

最佳实践

1. 根据模型选择记忆大小

java 复制代码
// GPT-4o-mini: 128k tokens
TokenWindowChatMemory.withMaxTokens(10000, tokenizer);  // 保留约 10k tokens

// GPT-3.5-turbo: 16k tokens
TokenWindowChatMemory.withMaxTokens(4000, tokenizer);   // 保留约 4k tokens

2. 为系统消息预留空间

java 复制代码
// 系统消息通常较长,计入 Token 限制
int systemMessageTokens = 500;
int maxContextTokens = 4000;
int availableTokens = maxContextTokens - systemMessageTokens;

ChatMemory memory = TokenWindowChatMemory.withMaxTokens(availableTokens, tokenizer);

3. 定期摘要长对话

java 复制代码
public class SmartMemoryManager {

    private final ChatLanguageModel summaryModel;
    private final List<ChatMessage> allMessages = new ArrayList<>();
    private String summary = "";

    public void addMessage(ChatMessage message) {
        allMessages.add(message);

        // 每 20 条消息总结一次
        if (allMessages.size() % 20 == 0) {
            summary = summarizeHistory();
            allMessages.clear();
        }
    }

    public List<ChatMessage> getContextMessages() {
        List<ChatMessage> context = new ArrayList<>();
        if (!summary.isEmpty()) {
            context.add(SystemMessage.from("之前的对话摘要:" + summary));
        }
        context.addAll(allMessages);
        return context;
    }

    private String summarizeHistory() {
        String conversation = allMessages.stream()
                .map(ChatMessage::text)
                .collect(Collectors.joining("\n"));

        return summaryModel.generate("用3-5句话总结以下对话:\n" + conversation);
    }
}

4. 为敏感信息设置过期策略

java 复制代码
public class SecureMemory implements ChatMemory {

    private final List<MessageWithTimestamp> messages = new ArrayList<>();
    private final Duration expirationTime;

    public void add(ChatMessage message) {
        messages.add(new MessageWithTimestamp(message, Instant.now()));
        removeExpiredMessages();
    }

    public List<ChatMessage> messages() {
        removeExpiredMessages();
        return messages.stream()
                .map(MessageWithTimestamp::getMessage)
                .collect(Collectors.toList());
    }

    private void removeExpiredMessages() {
        Instant cutoff = Instant.now().minus(expirationTime);
        messages.removeIf(m -> m.getTimestamp().isBefore(cutoff));
    }

    static class MessageWithTimestamp {
        private final ChatMessage message;
        private final Instant timestamp;
        // constructor, getters...
    }
}

常见问题

Q1: 如何知道当前使用了多少 Token?

java 复制代码
OpenAiTokenizer tokenizer = new OpenAiTokenizer(GPT_4_O_MINI);
List<ChatMessage> messages = memory.messages();

int totalTokens = messages.stream()
        .mapToInt(msg -> tokenizer.estimateTokenCountInMessage(msg))
        .sum();

System.out.println("当前使用 Token: " + totalTokens);

Q2: 如何在不同会话间共享部分记忆?

java 复制代码
// 使用共享的系统消息
SystemMessage sharedContext = SystemMessage.from("用户偏好:喜欢简洁的回答");

ChatMemory session1 = MessageWindowChatMemory.withMaxMessages(10);
session1.add(sharedContext);

ChatMemory session2 = MessageWindowChatMemory.withMaxMessages(10);
session2.add(sharedContext);

Q3: 记忆满了会发生什么?

  • MessageWindowChatMemory: 删除最旧的消息
  • TokenWindowChatMemory: 删除最旧的消息直到满足 Token 限制

下一步学习

参考资料

相关推荐
那个村的李富贵4 小时前
光影魔术师:CANN加速实时图像风格迁移,让每张照片秒变大师画作
人工智能·aigc·cann
侠客行03176 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪6 小时前
深入浅出LangChain4J
java·langchain·llm
腾讯云开发者6 小时前
“痛点”到“通点”!一份让 AI 真正落地产生真金白银的实战指南
人工智能
CareyWYR6 小时前
每周AI论文速递(260202-260206)
人工智能
hopsky7 小时前
大模型生成PPT的技术原理
人工智能
禁默7 小时前
打通 AI 与信号处理的“任督二脉”:Ascend SIP Boost 加速库深度实战
人工智能·信号处理·cann
老毛肚7 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
心疼你的一切8 小时前
昇腾CANN实战落地:从智慧城市到AIGC,解锁五大行业AI应用的算力密码
数据仓库·人工智能·深度学习·aigc·智慧城市·cann
AI绘画哇哒哒8 小时前
【干货收藏】深度解析AI Agent框架:设计原理+主流选型+项目实操,一站式学习指南
人工智能·学习·ai·程序员·大模型·产品经理·转行