06 · 记忆与会话:模型的金鱼脑与你的药方
本片目标:理解”模型无状态”的本质,学会三种让对话连贯的方式:① list 手搓(零依赖,看懂本质);②InMemoryChatMessageHistory(官方,会话隔离);③ 知道为什么不用弃用的RunnableWithMessageHistory。为第 09~10 篇”对话式 RAG”打底。 新增规定性:6(会话状态:你要自己管理历史消息 + session 隔离) 数据字典:BaseChatMessageHistory / InMemoryChatMessageHistory。 进程线程模型:单会话串行;多会话靠”会话 id → 独立历史存储”隔离。 网络模型:无新增(历史拼进消息列表一起发)。
1. 上集回顾
第 05 篇输入输出都齐了。但有个致命问题从第 01 篇就存在:2. 三种历史方案:从手搓到官方
核心问题只有一个:历史消息存在哪、怎么取、怎么写回。三种方案的区别就是这三件事的实现方式不同。2.1 方案一:list 手搓(零依赖,理解本质)
最原始的方案:一个list 就够了。 你手动 append 消息对象:
2.2 方案二:InMemoryChatMessageHistory(官方,langchain_core 内置,不依赖 LangGraph)
为什么它能用:InMemoryChatMessageHistory在langchain_core.chat_history模块,1.x 官方活跃维护(第 00 篇已实测导入成功)。它不碰 LangGraph、不碰弃用包——符合本系列原则。接口和上面手搓的ListHistory一模一样(messages/add_user_message/add_ai_message),所以你的对话函数换存储不用改逻辑。
2.3 方案三(弃用):RunnableWithMessageHistory —— 为什么不用它
旧教程的经典写法:把”历史存储 + 模板槽位”封装成链的一层,链自己管历史。实测(本机 1.5.1)它在实例化时立刻抛出弃用警告:
2.4 三方案对比表
2.5 会话隔离模式(多用户的关键)
session_id(会话 id)= 用户身份的钥匙。这个模式对方案一/二都适用——store 里装 ListHistory 或 InMemoryChatMessageHistory 都一样。
3. 关键方法(手动滚雪球,完整流程)
chat 函数只依赖 history.messages / add_user_message / add_ai_message 三个接口——方案一换方案二,chat 函数一行都不用改。
4. 进程线程模型
InMemoryChatMessageHistory是纯内存——进程重启就丢。生产要持久化(第 14、15 篇讲 Redis/DB 方案思路)。- 并发注意:多个线程同时写同一个 session 的 history 有竞态。单用户单线程(FastAPI 异步单协程)下没问题;多线程共享时要加锁(第 11、13 篇)。
5. 网络模型
6. 验证:跑起来
配套代码code/06_memory.py:
- 无记忆演示(模型真的不记得);
- 方案一 list 手搓(零依赖,看消息列表增长);
- 方案二 InMemory 官方 + 会话隔离(两个 session 互不干扰);
- 方案三 弃用的
RunnableWithMessageHistory——实例化时捕获真实弃用警告; - 打印每次发送的消息列表,看历史如何增长。
7. 边界
- 历史无限增长会爆上下文——长对话要裁剪(
trim_messages)或摘要(第 15 篇)。 RunnableWithMessageHistory已弃用——本机 1.5.1 实测:实例化时抛LangChainDeprecationWarning,弃用理由明确指向 LangGraph。本系列不用 LangGraph,用方案一/二的手动管理。- 别 append 重建的消息——写回历史时直接存原始
AIMessage对象(含 tool_calls/usage_metadata),重建会丢字段。 - 内存历史只适合开发——生产必须换持久化存储(Redis/DB),接口不变(方案二实现
BaseChatMessageHistory,换持久化实现即可,chat 函数不动)。
推荐资料(延伸阅读)
- LangChain 官方文档 · 短期记忆 —— 消息历史的官方概念页
- langchain-core API 参考 · chat_history —— BaseChatMessageHistory 家族