「LangChain」LangChain 如何实现长短期记忆?
0 前言
本文内容整理自公众号「小林面试笔记」的文章 6. LangChain 如何实现短期记忆和长期记忆?,用于记录与总结 LangChain 框架相关的核心面试知识点。
「LangChain 如何实现短期记忆和长期记忆?」,可以这样作答:
记住一条主线就够:短期记忆 = State + thread_id + Checkpointer,长期记忆 = namespace/key + Store。短期记忆属于当前会话线程——Agent State 保存消息、当前步骤和中间结果,Checkpointer 按
thread_id保存状态快照,同一thread_id再次调用就能恢复对话与执行状态;长期记忆不绑定线程,存进 Store——namespace 通常由租户、用户、记忆类型组成,用户新建线程后,只要可信身份相同,仍能读取自己的长期偏好。模型只负责生成任务参数,用户 ID、租户、权限等可信身份由应用运行时经 ToolRuntime 注入;工具通过runtime.state读当前线程状态、runtime.store读跨线程长期数据,两个入口不能混用。长对话还要治理上下文:裁剪、删除、摘要是三种不同策略,各有风险;长期记忆不是把聊天记录全存进去,写入要讲究时机(明确被要求记住的实时写,推断出的偏好后台提炼),并且做去重、脱敏、冲突处理。生产环境要盯四件事:数据库型持久化、可信身份、记忆治理(过期/纠错/隐私)和召回评测。
面试对话里藏着这道题的两个雷区:一是拿旧版 ConversationBufferMemory、ConversationSummaryMemory 作答——那是 Chain 时代的抽象,LangChain v1 新项目讲的是 Checkpointer、Store、thread_id 和 namespace;二是把「能落盘」当成「一个就够」——Checkpointer 和 Store 作用域完全不同。前面几篇我们讲过架构里的 State/Context/Store 数据分工、构建步骤里的「补齐状态与安全」、工具注册里的 ToolRuntime 可信参数注入,这篇就把「记忆」这一环补完整:该记住什么、短期怎么存、长期怎么存、怎么跨线程读、怎么治理。
1 先分清:该记住什么
记忆设计的第一步不是选数据库,而是定作用域——先回答「这条信息属于哪个作用域」。
举个杭州旅行的例子:对话里聊到的出行日期、预算、下一步计划,属于当前线程的短期状态——这场对话结束就过时了;而「不吃辣、喜欢住地铁附近」这种一周后新开一个会话仍然成立的信息,才是长期记忆。
区分标准就一句话:短期记忆回答「这场对话进行到哪」,长期记忆回答「这个用户是谁」。对话中的进度、草稿是前者,用户身上稳定的偏好、经验、规则是后者:

把作用域定清楚后,两条主线就诞生了:
1 | |
2 短期记忆如何实现
create_agent 底层运行在 LangGraph 上,Agent State 是每次执行的核心载体:默认包含 messages,也可以扩展订单号、当前步骤、工具调用次数等业务字段。但要注意——State 在内存里跑,进程退出就没了。要让短期记忆跨进程、跨实例存活,需要 Checkpointer 持久化:
1 | |
两个要点:一是 InMemorySaver 只适合本地演示,生产环境要换数据库型持久化实现;二是 Checkpoint 保存的是图执行中各步的 State 快照,这正是「暂停恢复、人工审批、故障恢复」这些能力的地基——上一篇文章构建步骤里说过的「游戏存档」就应验在这里:同一 thread_id 是同一个存档位,读档就接着跑。
3 消息太多怎么办
Checkpointer 能保存状态,不等于每次都要把全部历史全量喂给模型——消息越多,Token 成本、延迟和注意力干扰都越大。处理超长历史有三类策略,各有利弊:
| 策略 | 作用 | 主要风险 |
|---|---|---|
| 裁剪 | 只选择部分消息进入本次模型上下文 | 持久状态仍会继续增长 |
| 删除 | 从 State 中永久移除旧消息 | 信息不可恢复 |
| 摘要 | 把早期历史压缩成简短语义摘要 | 可能遗漏细节或逐轮失真 |

更关键的是策略要跟业务挂钩,不能按固定条数一刀切:客服 Agent 要优先保护当前工单信息和给用户的承诺,代码 Agent 要保住最新的报错和修改记录——选择哪些消息留下,要综合 Token 预算、消息角色和业务重要性来判断。
4 长期记忆如何实现
这里先澄清一个容易误解的点:新的 thread_id 默认不继承旧线程的 State——这是刻意的线程隔离,不是缺陷。跨线程仍然要用的信息,需要被「提炼」出来写入 Store。
Store 的定位三要素:
1 | |
- namespace:一组层级标签,通常按「租户、用户、记忆类型」组织,天然把不同租户、不同用户的数据隔开——这是防串读的第一道墙;
- key:某条记忆的稳定标识,比如「当前偏好」记录固定用
main; - value:记忆内容本身,JSON 格式。
读取时,已知 key 就精确读取;想按语义查找(「我记得他说过饮食相关的事」),可以给 Store 配向量索引做语义检索。但要记住:向量检索只是召回方式,不代表所有聊天记录都要变成长期记忆。
那长期记忆里通常放什么?分类设计数据结构,而不是要求「全都保存」:
| 类型 | 示例 |
|---|---|
| 用户事实与偏好 | 不吃辣、偏好中文、常用 Java |
| 历史经验 | 上次如何成功处理支付超时 |
| 工作规则 | 退款前必须核验订单归属 |
写入之前,还要过一遍「质检」:去重、脱敏、冲突处理、质量判断——长期记忆像写进档案室的档案,不是往流水账里记每一句话。
5 如何跨线程读取
用户换一个会话(新 thread_id),怎么还能调出自己的偏好?答案在 ToolRuntime——它给工具三个入口,不能混成一个数据袋:
runtime.state:当前线程的短期状态;runtime.context:可信的用户身份与权限(由应用注入,模型不可见);runtime.store:跨线程的长期数据。
模型只生成任务参数(比如偏好内容),user_id 由已认证的应用经 Context 注入——这与「工具注册」那篇的可信参数边界一脉相承。看个完整例子:
1 | |
这套设计的隔离逻辑是:不同 thread_id 的会话,只要可信 user_id 相同,就能访问同一份长期记忆;其他用户则由 namespace 和服务端权限隔离——谁也不能凭模型生成的身份读到别人的档案:

这就像医院的病历归档:护士只认病人手环上的身份码(可信身份),按手环码把病历放进对应档案格(namespace),绝不听任何人现场报名字——报名字既可能记错,也可能被人冒名。
6 什么时候写入长期记忆
「能记」不等于「该记」。闲聊全存只会制造噪声、冲突和隐私风险——记住每条「今天天气不错」,下次检索时真正的偏好反而被淹没。
写入时机分两种:
- 明确要求记住的(用户说「请记住我不吃辣」):主链路实时写入,立即生效;
- 模型推断出的偏好经验(从对话里隐隐看出的习惯):更适合会话结束后由后台任务提炼、去重、脱敏后写入——当场写容易把试探性言论当真。
无论哪种方式,写入的记录都应该保存来源、时间、置信度,并支持更新、纠错、删除——记忆是会错的,要给用户和管理员留出修正的通道。
还有一条铁律:订单金额、账户余额、库存这类实时事实,必须去查权威业务系统,长期记忆不代替真实数据库——记忆里放的是规律与偏好,不是会变动的账本。
7 生产环境要注意什么
把记忆系统搬上线,要过四道关:
- 可靠保存:内存实现随进程退出而丢,线上必须用数据库型 Checkpointer 和 Store,表结构与迁移纳入部署流程;
- 这是谁的记忆:
thread_id、tenant_id、user_id必须来自可信身份体系,不能信模型生成的身份,也不能让客户端随意指定他人的 namespace——记得越多,越要先回答「这是谁的记忆」,否则数据库越大,跨用户泄漏的风险越大; - 记忆会过时:去重、更新、过期淘汰是常态;用户要能查看、更正、导出、删除自己的记忆;敏感信息默认不记,确需保存则加密限权——日志与 Trace 也不能成为隐私泄漏口;
- 不是能存能查就合格:写入了多少条不是指标,要看召回链路——信息是否值得写入、相关问题能否召回、无关问题会不会误召回、注入记忆后答案是否真的变好。

只有「写入、召回、使用」三步都有效,记忆系统才不是另一个不断膨胀的数据库。
8 旧 Memory 还能用吗
ConversationBufferMemory、ConversationSummaryMemory 这类名字,是 Chain 时代(LLMChain 那个时代)的常见抽象,如今主要位于 langchain-classic,适合维护存量项目——但它不再是 v1 新项目的主路径。
v1 新项目的推荐组合:
1 | |
对比旧版,这套组合把三件事拆得更清楚:状态作用域(线程内 vs 跨线程)、持久化机制(Checkpointer vs Store)、记忆治理(裁剪/摘要/写入策略交给 Middleware 或图节点)。对于有工具调用、暂停恢复、多用户隔离要求的 Agent,这种拆分几乎每一项都是刚需。
9 面试总结
回答这道题,最大的雷就是拿旧版 Memory 类名作答,或者把 Checkpointer 与 Store 混为一谈。先避开几个误区:
| 误区 | 正解 |
|---|---|
| 短期记忆 = 最近几轮对话塞进 Prompt,长期记忆 = 全部聊天记录存向量库 | 短期记忆是线程级 State + Checkpointer(thread_id),长期记忆是跨线程 Store(namespace/key);不是所有聊天都值得长期保存 |
| 用 ConversationBufferMemory / ConversonSummaryMemory 作答 | 旧版 Chain 时代抽象,在 langchain-classic;v1 讲 Checkpointer、Store、thread_id、namespace |
| Checkpointer 和 Store 都是持久化,选一个就够 | 作用域完全不同:Checkpointer 管当前线程状态,Store 管跨线程数据;混用不是换会话失忆,就是用户数据串读 |
| 上下文太长就固定裁剪最后 N 条 | 策略要按 Token 预算、消息角色、业务重要性定制——客服保工单与承诺,代码 Agent 保报错与修改记录 |
| 记忆系统能写能查就合格 | 当代系统像针对存档的档案室:写入要质检(去重、脱敏、质量判断),读取要隔离(namespace、可信身份),还要支持纠错、过期删除、隐私保护与召回评测 |
再按这张框架组织答案(短期与长期对照,不容易乱):
| 维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 作用域 | 当前线程(会话) | 跨线程 |
| 载体 | Agent State + Checkpointer | Store(namespace/key/value) |
| 关键标识 | thread_id | namespace=(tenant_id, user_id, memory_type) + key |
| 内容 | 消息、当前步骤、中间结果 | 用户偏好、历史经验、工作规则 |
| 读取入口 | runtime.state | runtime.store(语义检索可配向量索引) |
| 恢复能力 | 同 thread_id 续接,支持暂停恢复与人工审批 | 换线程后仍可读,namespace 隔离防串读 |
| 治理手段 | 裁剪、删除、摘要(按业务定制) | 去重、更新、过期淘汰、隐私保护、召回评测 |
追问预案:
- 「怎么判断一条信息该短期记还是长期记?」——按作用域:这场对话内还要用的(进度、草稿、中间结果)是短期;换一个会话仍然成立的(口味偏好、工作规则、处理经验)才是长期。等价表述:短期回答「对话进行到哪」,长期回答「用户是谁」。
- 「Checkpointer 和 Store 都能落盘,为什么不能二选一?」——作用域问题:Checkpointer 按 thread_id 保存当前线程的状态快照,新线程不继承;Store 按 namespace/key 保存跨线程长期数据。混用要么换会话就失忆(拿 Store 当线程存档),要么用户数据串读(拿线程状态当长期档案)。
- 「上下文太长怎么处理?」——三策略各有代价:裁剪只减本次模型输入、持久状态继续增长;删除不可恢复;摘要可能逐轮失真。要结合 Token 预算、消息角色与业务重要性定制,不能固定条数。
- 「长期记忆什么时候写、写什么?」——被明确要求记住的实时写入;推断出的偏好由后台任务提炼、去重、脱敏后写。写来源、时间、置信度,支持更新纠错删除;订单金额、余额这类实时事实查权威系统,不存进记忆。
- 「怎么防止模型读到别人的记忆?」——身份可信化 + 空间隔离:user_id 由应用经 ToolRuntime context 注入(模型不可见),namespace 按租户/用户隔离,服务端再做权限校验;模型生成的身份一个都不能信。
参考
- 6. LangChain 如何实现短期记忆和长期记忆? | 小林面试笔记
- 5. 在 LangChain 中,如何为 Agent 注册工具? | 小林面试笔记(相关:ToolRuntime 三个入口与可信参数注入)
- 4. 使用 LangChain 构建 Agent 的核心步骤是什么? | 小林面试笔记(相关:七步构建中的「补齐状态与安全」)
- LangChain 官方文档:Memory 概览
- LangChain 官方文档:Short-term Memory
- LangChain 官方文档:Long-term Memory
- LangGraph 官方文档:Persistence
- LangGraph 官方文档:Memory
- LangChain API:langchain-classic
本文图片均来源于公众号「小林面试笔记」,版权归原作者所有。