【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 限制

下一步学习

参考资料

相关推荐
Warren2Lynch8 小时前
利用 AI 协作优化软件更新逻辑:构建清晰的 UML 顺序图指南
人工智能·uml
ModelWhale8 小时前
当“AI+制造”遇上商业航天:和鲸助力头部企业,构建火箭研发 AI 中台
人工智能
码出财富8 小时前
SpringBoot 内置的 20 个高效工具类
java·spring boot·spring cloud·java-ee
ATMQuant8 小时前
量化指标解码13:WaveTrend波浪趋势 - 震荡行情的超买超卖捕手
人工智能·ai·金融·区块链·量化交易·vnpy
weixin_509138348 小时前
语义流形探索:大型语言模型中可控涌现路径的实证证据
人工智能·语义空间
soldierluo8 小时前
大模型的召回率
人工智能·机器学习
Gofarlic_oms18 小时前
Windchill用户登录与模块访问失败问题排查与许可证诊断
大数据·运维·网络·数据库·人工智能
童话名剑8 小时前
人脸识别(吴恩达深度学习笔记)
人工智能·深度学习·人脸识别·siamese网络·三元组损失函数
我是小疯子668 小时前
Python变量赋值陷阱:浅拷贝VS深拷贝
java·服务器·数据库
_YiFei8 小时前
2026年AIGC检测通关攻略:降ai率工具深度测评(含免费降ai率方案)
人工智能·aigc