Skip to main content

06 · 记忆与会话:模型的金鱼脑与你的药方

本片目标:理解”模型无状态”的本质,学会三种让对话连贯的方式:① list 手搓(零依赖,看懂本质);② InMemoryChatMessageHistory(官方,会话隔离);③ 知道为什么不用弃用的 RunnableWithMessageHistory。为第 09~10 篇”对话式 RAG”打底。 新增规定性:6(会话状态:你要自己管理历史消息 + session 隔离) 数据字典:BaseChatMessageHistory / InMemoryChatMessageHistory。 进程线程模型:单会话串行;多会话靠”会话 id → 独立历史存储”隔离。 网络模型:无新增(历史拼进消息列表一起发)。

1. 上集回顾

第 05 篇输入输出都齐了。但有个致命问题从第 01 篇就存在:
模型每次调用都是无状态的——它不记得说过什么。对话 API 之所以能”记住”,是因为每次调用都把完整历史放进消息列表
所以”记忆”不是模型的能力,是你的责任——把历史消息存起来、每次拼进去。

2. 三种历史方案:从手搓到官方

核心问题只有一个:历史消息存在哪、怎么取、怎么写回。三种方案的区别就是这三件事的实现方式不同。

2.1 方案一:list 手搓(零依赖,理解本质)

最原始的方案:一个 list 就够了。 你手动 append 消息对象:
为什么先讲它:它证明”记忆”没有任何魔法——就是一个列表,你往里放消息。官方方案只是在它外面包了一层统一接口(方便以后换持久化实现时接口不变)。

2.2 方案二:InMemoryChatMessageHistory(官方,langchain_core 内置,不依赖 LangGraph

为什么它能用InMemoryChatMessageHistorylangchain_core.chat_history 模块,1.x 官方活跃维护(第 00 篇已实测导入成功)。它不碰 LangGraph、不碰弃用包——符合本系列原则。接口和上面手搓的 ListHistory 一模一样(messages / add_user_message / add_ai_message),所以你的对话函数换存储不用改逻辑

2.3 方案三(弃用):RunnableWithMessageHistory —— 为什么不用它

旧教程的经典写法:把”历史存储 + 模板槽位”封装成链的一层,链自己管历史。实测(本机 1.5.1)它在实例化时立刻抛出弃用警告:
弃用理由指向 LangGraph——它把记忆绑定到 LangGraph 生态。而本系列不用 LangGraph,所以也不用它。方案一/二已覆盖全部功能(存、取、拼进消息、写回),接口更透明、不依赖 LangGraph、无弃用风险。

2.4 三方案对比表

2.5 会话隔离模式(多用户的关键)

为什么必须隔离:生产环境有 N 个用户同时聊天。如果不隔离,所有人的历史混在一个列表里,模型会乱。session_id(会话 id)= 用户身份的钥匙。这个模式对方案一/二都适用——store 里装 ListHistoryInMemoryChatMessageHistory 都一样。

3. 关键方法(手动滚雪球,完整流程)

三个环节缺一不可取历史 → 拼进消息 → 写回历史。漏掉任何一个,对话就”失忆”。而你的 chat 函数只依赖 history.messages / add_user_message / add_ai_message 三个接口——方案一换方案二,chat 函数一行都不用改

4. 进程线程模型

  • InMemoryChatMessageHistory 是纯内存——进程重启就丢。生产要持久化(第 14、15 篇讲 Redis/DB 方案思路)。
  • 并发注意:多个线程同时写同一个 session 的 history 有竞态。单用户单线程(FastAPI 异步单协程)下没问题;多线程共享时要加锁(第 11、13 篇)。

5. 网络模型

历史消息通过网络逐条发给模型——这就是”记忆”的物理真相:不是模型记住了,是每次把历史完整地发一遍。代价是历史越长,token 越多、越贵、越慢(第 15 篇讲裁剪)。

6. 验证:跑起来

配套代码 code/06_memory.py
  1. 无记忆演示(模型真的不记得);
  2. 方案一 list 手搓(零依赖,看消息列表增长);
  3. 方案二 InMemory 官方 + 会话隔离(两个 session 互不干扰);
  4. 方案三 弃用的 RunnableWithMessageHistory——实例化时捕获真实弃用警告;
  5. 打印每次发送的消息列表,看历史如何增长。
预期输出(节选)

7. 边界

  • 历史无限增长会爆上下文——长对话要裁剪(trim_messages)或摘要(第 15 篇)。
  • RunnableWithMessageHistory 已弃用——本机 1.5.1 实测:实例化时抛 LangChainDeprecationWarning,弃用理由明确指向 LangGraph。本系列不用 LangGraph,用方案一/二的手动管理。
  • 别 append 重建的消息——写回历史时直接存原始 AIMessage 对象(含 tool_calls/usage_metadata),重建会丢字段。
  • 内存历史只适合开发——生产必须换持久化存储(Redis/DB),接口不变(方案二实现 BaseChatMessageHistory,换持久化实现即可,chat 函数不动)。

推荐资料(延伸阅读)


8. 未完待续

对话连贯了。但新的问题出现:模型只知道你教它的,不知道你们公司的新人培训手册、政策文件。让模型回答”转正需要什么条件”这种公司内部问题,它只能瞎编。 怎么把公司文档变成模型可用的知识?第一步:把文档变成一个个”块”——这就是第 07 篇:文档与切分。 07 · 文档与切分