【LangChain4j系列06】ChatMemory 对话记忆管理

LLM 是无状态的。ChatMemory 负责在多次调用之间维护对话上下文。本章深入解析逐出策略、持久化实现、多用户隔离、以及 Memory vs History 的关键区别。


6.1 Memory vs History

LangChain4j 做一个关键区分:

arduino 复制代码
History(历史)  --- 用户看到的完整对话记录
                  "用户和 AI 之间的所有消息,保持不变"

Memory(记忆)  --- 发给 LLM 以模拟"记住"的信息
                  可能被逐出、摘要、改写、注入额外信息

当前 LangChain4j 只提供 Memory,不提供 History。如果你需要展示完整对话历史给用户,需要自己维护。


6.2 为什么需要逐出策略?

三个核心驱动力:

  1. Context Window 限制:每个模型有最大 Token 限制(如 GPT-4o 128K,但超长 context 会降低质量)
  2. 成本控制:每个 Token 都计费,历史对话越长越贵
  3. 延迟控制:更多 Token 意味着更慢的推理速度

6.3 两种内置实现

MessageWindowChatMemory(按消息数)

java 复制代码
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
// 保留最近 10 条消息(简单但不精确,无法控制 Token 数)

TokenWindowChatMemory(按 Token 数)

java 复制代码
ChatMemory memory = TokenWindowChatMemory.builder()
    .maxTokens(2000, new OpenAiTokenCountEstimator("gpt-4o-mini"))
    .build();
// 保留最近 2000 Token 的消息
// 消息不可分割:一条消息如果超出剩余空间,会被整体逐出

对比

特性 MessageWindow TokenWindow
粒度 消息条数 Token 数量
精确性 低(消息长度差异大) 高(精确控制 Token)
性能 快(无 Token 计数) 需要 TokenCountEstimator
适用场景 快速原型 生产环境

6.4 持久化:ChatMemoryStore

默认情况下,消息存储在 ArrayList 中,服务重启即消失。实现 ChatMemoryStore 接口实现持久化:

java 复制代码
public interface ChatMemoryStore {
    List<ChatMessage> getMessages(Object memoryId);
    void updateMessages(Object memoryId, List<ChatMessage> messages);
    void deleteMessages(Object memoryId);
}

Redis 实现示例

java 复制代码
class RedisChatMemoryStore implements ChatMemoryStore {
    private final RedisClient redis;

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String json = redis.get("chat:" + memoryId);
        if (json == null) return new ArrayList<>();
        // 使用框架提供的反序列化工具
        return ChatMessageDeserializer.messagesFromJson(json);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String json = ChatMessageSerializer.messagesToJson(messages);
        redis.set("chat:" + memoryId, json);
    }

    @Override
    public void deleteMessages(Object memoryId) {
        redis.del("chat:" + memoryId);
    }
}

// 使用
ChatMemory memory = MessageWindowChatMemory.builder()
    .id("user-12345")
    .maxMessages(20)
    .chatMemoryStore(new RedisChatMemoryStore())
    .build();

updateMessages() 的调用时机

scss 复制代码
用户发消息 → updateMessages() 被调用(新增 UserMessage)
LLM 回复   → updateMessages() 被调用(新增 AiMessage)
逐出发生   → updateMessages() 被调用(逐出后的完整列表)

即:每次与 LLM 交互,updateMessages() 通常被调用 2 次

重要语义

  • updateMessages() 的参数是完整的消息列表(不是增量)
  • 逐出后的消息也会从 ChatMemoryStore 中同步删除
  • getMessages()memoryId 就是 builder 中设置的 id

6.5 多用户/多会话隔离

AI Services 中的 @MemoryId

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

ChatMemoryProvider provider = memoryId ->
    MessageWindowChatMemory.builder()
        .id(memoryId)
        .maxMessages(20)
        .chatMemoryStore(new PersistentChatMemoryStore())
        .build();

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

// 每个 memoryId 独立一份记忆
assistant.chat(100, "我叫张三");  // 用户100的记忆
assistant.chat(200, "我叫李四");  // 用户200的记忆(完全隔离)

⚠️ 并发警告 :同一个 @MemoryId 不要并发调用!ChatMemory 内部使用 ArrayList,没有同步保护。

运行时访问记忆

java 复制代码
interface Assistant extends ChatMemoryAccess {
    String chat(@MemoryId int memoryId, @UserMessage String message);
}

// 读取某用户的对话历史
List<ChatMessage> messages = assistant.getChatMemory(1).messages();

// 清除某用户的记忆
assistant.evictChatMemory(2);

6.6 SystemMessage 的特殊处理

SystemMessage 在 Memory 中有特殊待遇:

  • 始终保留:不受逐出策略影响
  • 单例:同一时间只能有一个 SystemMessage
  • 替换规则:内容相同时忽略,不同时替换旧的
  • 位置控制
java 复制代码
// 默认:新 SystemMessage 追加到列表末尾
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);

// 始终放在开头(推荐,因为 LLM 更关注开头内容)
ChatMemory memory = MessageWindowChatMemory.builder()
    .maxMessages(10)
    .alwaysKeepSystemMessageFirst(true)
    .build();

6.7 工具消息的关联逐出

如果一条 AiMessage 包含 ToolExecutionRequest 被逐出,框架会自动逐出对应的 ToolExecutionResultMessage

原因:某些 Provider(如 OpenAI)禁止请求中出现孤立的工具结果消息(没有对应工具调用的结果消息)。

css 复制代码
逐出前:  [AiMessage(toolCall: getWeather), ToolExecutionResultMessage(...), UserMessage, AiMessage]
逐出后:  [UserMessage, AiMessage]
         ↑ 两个与 getWeather 相关的消息被一起删除

6.8 ChatMemory 在低层 API 中的使用

如果你使用低层 API(非 AI Services),可以独立使用 ChatMemory:

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

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

// 获取上下文消息
List<ChatMessage> context = memory.messages();

// 传给 LLM
ChatResponse response = model.chat(context.toArray(new ChatMessage[0]));

// 把回复也加进去
memory.add(response.aiMessage());

6.9 检索内容是否存入记忆

在 RAG 场景下,一个重要的配置:

java 复制代码
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .chatMemory(memory)
    .retrievalAugmentor(augmentor)
    .storeRetrievedContentInChatMemory(false)  // 默认 true
    .build();
行为
true(默认) 增强后的 UserMessage(问题 + 检索内容)存入记忆。LLM 收到什么,下次就记住什么
false 只存原始的 UserMessage(不含检索内容),但当前推理仍用增强版本

选择 false 的原因:避免检索内容污染下次对话(下次可能检索到更相关的新内容)。


6.10 与 Agent 集成

在 Agent 场景中,supervisorContextStrategy 决定 Supervisor 如何利用历史:

java 复制代码
public enum SupervisorContextStrategy {
    CHAT_MEMORY,                    // 使用完整的 ChatMemory
    SUMMARIZATION,                  // 使用对话摘要(节省 Token)
    CHAT_MEMORY_AND_SUMMARIZATION   // 两者结合
}

这块后续章节会进行补充讲解。


6.11 最佳实践

场景 推荐配置
客服机器人(简单) MessageWindowChatMemory.withMaxMessages(20)
长对话 + 成本敏感 TokenWindowChatMemory.maxTokens(4000)
多用户 SaaS ChatMemoryProvider + ChatMemoryStore(持久化)
RAG 问答 storeRetrievedContentInChatMemory(false)
高并发 禁止同一 MemoryId 并发;考虑用 ConcurrentHashMap 包装
相关推荐
netCode1 小时前
Git一文带你理解如何使用
后端
数据百晓通1 小时前
AI 重构治理逻辑:2026 数据治理厂商的能力进化与分水岭
人工智能·重构
跨境小彭1 小时前
Temu运营优化方案:活动库存自动化管控,解决断流、超卖与权重下跌问题
大数据·运维·人工智能·自动化·跨境电商·temu·temu电商运营
烟雨江南7851 小时前
医院门诊医患沟通如何实时转写?——灵声智库流式 ASR、医学术语与双角色记录实践
人工智能·语音识别·agent
风途科技~1 小时前
峰值日照时数精准测,这款光伏电站环境监测仪器助力电站发电量评估
大数据·人工智能
watersink1 小时前
机器学习随机森林
人工智能·随机森林·机器学习
AI多Agent协作实战派1 小时前
AI多Agent协作系统实战(三十九):AI修对了Bug,却越了权——自动化团队的第一条铁律
人工智能·自动化·bug
DS随心转小程序1 小时前
AI 导出鸭重构转化链路,全面优化腾讯元宝输出 word 文档办公效率
人工智能·重构·aigc·word·豆包·deepseek·ai导出鸭
Oflycomm1 小时前
IPC网络摄像机与安防监控:Wi-Fi 6模组、AOV低功耗与边缘AI的产业落地
网络·人工智能·安防监控·wifi6·ipc网络摄像机