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

下一步学习

参考资料

相关推荐
*星星之火*2 小时前
【大白话 AI 答疑】第10篇 数学可视化网站汇总
人工智能
啊阿狸不会拉杆2 小时前
《数字图像处理》-实验1
图像处理·人工智能·算法·计算机视觉·数字图像处理
糖炒狗子2 小时前
Textin模型加速器+火山引擎打造商业计划书智能体
人工智能·火山引擎
谅望者2 小时前
数据分析笔记15:Python模块、包与异常处理
开发语言·人工智能·python
lbb 小魔仙2 小时前
FP8魔力解锁:SD3.5 图像编辑、修复与增强全栈实战
人工智能·python·ai
一招定胜负2 小时前
计算机视觉入门:opencv基本操作
人工智能·opencv·计算机视觉
徐先生 @_@|||2 小时前
三式掌握知识法
java·python
董厂长2 小时前
温度设为 0 仍然不完全确定:LLM 推理非确定性从哪来、怎么测、如何缓解
人工智能·llm·agent
大白要努力!2 小时前
Android 项目历史提交远程仓库资源过大,如何清理历史提交中无用的大文件
android·git