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

下一步学习

参考资料

相关推荐
Henry-SAP4 小时前
SAP引领ERP智能化转型
人工智能·云原生·sap·erp
峥嵘life4 小时前
Android16 311Y3 EAP-TLS 网络连接失败分析与修复总结
android·开发语言·人工智能·php
M-Robots echo4 小时前
轻量化裁剪方案解析:M-Robots 模块化构建,适配高低端各类机器人硬件平台
java·机器人·ros·开源社区·atomgit·m-robots
特立独行的猫a4 小时前
一切皆插件:DeepSeek Harness 的架构哲学,以及与主流 Agent 的对比
人工智能·架构·agent·deepseek·harness
电商API_180079052474 小时前
京东商品详情API技术文章
大数据·运维·人工智能·网络爬虫
不可求~4 小时前
多模型路由怎么落地:把任务分流写进项目的最小实现
java·前端·数据库
fīɡЙtīиɡ ℡5 小时前
AI Agent 记忆系统
人工智能
Kapaseker5 小时前
破坏性更新 - 解读 Jetpack Compose 1.12
android·kotlin
雨白5 小时前
我的 UML 学习笔记:结合 Android 实例看懂 4 种常用图表
android·架构
mldong5 小时前
用 DDD 设计工作流引擎:聚合根与充血模型
java·架构