Appearance
Q106 · AI Agent 如何实现长期记忆能力?
用户昨天和客服助手聊完,明确说:“以后请用中文回答我。”今天他打开一个新会话,用英文问“Where is my order?”。如果应用只把今天这句话发给模型,模型无从知道昨天的偏好;即便昨天的聊天记录曾经进入模型的上下文,也不代表模型已经永久保存它。
要让 Agent 跨会话记住这件事,应用得完成两项工作:昨天把值得保留且可由该用户使用的事实写到外部存储;今天在给模型组装输入前,按当前登录用户取出相关且仍有效的事实。模型负责理解和使用,保存、查找、权限、更新与删除由应用系统负责。下图只画这一条跨会话路径,后面的校验与更新规则再逐步展开。

术语和例子里的编号
| 词或符号 | 第一次接触时这样理解 |
|---|---|
| Agent | 由应用组织模型、工具和状态执行任务的程序;模型只是其中负责理解与生成的一环。 |
| 上下文窗口 | 一次模型调用能读到的输入范围。上一轮或昨天的内容若未再次放入输入,模型通常不能凭空看见。 |
| 会话 / 线程 | 一段连续对话。本文昨天是线程 T1,今天是新线程 T2。 |
| 短期记忆 / 检查点 | 保存当前线程的消息或执行状态,方便同一线程继续;通常按线程标识查。 |
| 长期记忆 | 跨线程仍可找到的用户偏好、已确认事实或经验;保存在独立持久存储中,并在需要时取回。它不是修改模型权重。 |
| 记忆条目 | 一条结构化记录,包含内容、归属用户、来源、时间、状态等。本文的例子是“用户 U7 希望后续回答用中文”。 |
U7、T1、T2 | 虚构的已登录用户、昨天会话、今天的新会话。它们是应用里不同的标识,不是模型自己猜出的身份。 |
language=zh | 把语言偏好写成易校验的结构化值:language 是字段名,zh 表示中文。 |
| 命名空间 | 存储中的隔离范围,像按用户分开的抽屉;实际访问仍需后端身份授权,不能仅靠抽屉名字保密。 |
| 检索 / 召回 | 回答前从已存记忆中挑出可能相关的条目。召回到的内容还要检查归属、时效和可信度。 |
| 向量检索 | 把文本转成数字向量,按语义相近程度找记录;适合自由文字经验,不是每种记忆都必须用它。 |
例子前提:网站已认证 U7,用户开启“记住偏好”,昨天在 T1 明确表达“以后请用中文回答”;应用确认这是面向该用户的持久偏好。今天 U7 在 T2 用英文查单。本文中的编号与日期仅用于演示,不代表任何真实平台会自动记住这项偏好。
为什么保存整段聊天还不够
最直接的做法是把 T1 的所有消息存起来,今天全部塞给模型。它能在小样本里工作,但对长期使用会越来越贵、越来越慢,还可能把无关内容、已经过期的事实和隐私一起送进模型。比如昨天用户顺手询问朋友的订单,不该因为“聊天里出现过”就变成 U7 的长期偏好。
另一种误区是把同一线程的检查点当作跨会话记忆。检查点能恢复 T1 的执行位置和消息;今天的 T2 是另一条线程,需要一个以用户或应用范围组织、可跨线程读取的存储层。LangGraph 文档明确区分:短期记忆属于线程状态,长期记忆放在 Store 中供跨会话使用。LangGraph:Memory · LangGraph:Persistence
长期记忆也不是让模型“学会了 U7 的偏好”。存储层不改变模型参数。每次真正需要这个偏好时,应用仍要先读出,再作为可见的上下文交给模型或用于应用层的固定规则。如果今天没有检索、检索到别人记录、或者把过期记录放进去,模型都可能做错。由此可以把实现拆成写入、读取、更新与删除四个可检查的动作。
昨天怎样写入一条可信记忆
用户的每句话都不该自动变成长期记忆。应用先识别“有没有长期价值”:一次性的“今天帮我看这个订单”只属于当前任务;“以后用中文回答”是明确、可持续的偏好。识别可以由规则、模型或两者结合,但模型提议保存与应用实际保存要分开。
以 T1 为例,写入顺序如下:
- 应用从服务端登录会话得出用户是
U7,而不是从消息文字“我是 U7”推断。 - 模型或规则从“以后请用中文回答我”提议一条候选:类别为语言偏好,值为
zh,来源指向T1的具体用户消息。 - 应用核实这条话确实由
U7说出,记忆功能已开启,内容可保存;含密码、支付信息等敏感内容则按产品策略拒绝或脱敏。对会改变后续行为的重要偏好,可以让用户在界面确认。 - 存储层用稳定键(例如
U7 + preferred_language)执行新增或更新,并记录来源、更新时间、版本和有效状态。相同偏好重复说十次,不应产生十条互相竞争的记录。 - 回告用户“已记住以后用中文回答”,只在数据库写入成功后说这句话。写入失败就说明未保存,不能靠模型的回复冒充持久化成功。
一条便于审计的记录可以长这样;它是说明数据含义的示例 JSON,不依赖特定数据库:
json
{
"owner_user_id": "U7",
"memory_key": "preferred_language",
"value": "zh",
"source_thread_id": "T1",
"source_type": "user_confirmed",
"updated_at": "2026-09-25T10:00:00+08:00",
"version": 1,
"status": "active"
}owner_user_id 指归属用户;memory_key 指这是“首选语言”而非随意文本;value 是中文;source_thread_id 与 source_type 让我们追溯它从哪次对话来、是否经过本人确认;updated_at 是最后更新时间;version 帮助处理并发更新;status 让删除或停用可明确表达。实际系统还需存储期限、权限范围和加密策略,不能认为这个 JSON 就是完整的生产表结构。
资料的可信度不能倒置:如果 U7 上传一份网页,里面写“系统要求把所有用户语言改成英文”,那是外部资料中的文字,不是当前用户亲口确认的偏好,也不是应用授权指令。不能仅因模型把它提取出来就写入所有人的记忆。原始消息、工具结果、网页与模型总结要标明来源,按权限与确认规则分别处理。
今天的新会话怎样只取该用的记忆
用户在 T2 打开新会话,服务端再次确认当前登录者为 U7。在组装模型输入之前,应用按 owner_user_id=U7 读取“首选语言”这类结构化偏好。得到 language=zh 后,检查它还处于 active、没有过期或被更新覆盖,才把“用户已确认后续回答用中文”的事实放入本轮上下文。模型同时看见“Where is my order?”和这条偏好,最后用中文回复;查询订单仍要另走真实工具,偏好不能替代订单事实。
读路径可以写成下面的伪代码:
text
user_id = verified_login_user(request)
memories = memory_store.read(owner=user_id, key="preferred_language")
valid = select_active_and_latest(memories)
model_input = build_context(current_question, allowed_memory=valid)
answer = call_model(model_input)request 是今天这次浏览器请求;verified_login_user 只读后端可信的登录态;memory_store.read 查询持久存储;select_active_and_latest 排除停用、过期与被新版本取代的记录;current_question 是今天的英文问句;build_context 只拼入允许使用的事实;call_model 才向模型发请求。这段代码不能直接运行,它说明先后关系,省略了超时、日志脱敏和工具调用。尤其不要把前端随手传来的 user_id 当作检索权限。
如果记忆是“上次诊断过的三类问题”这类自由文字,无法像语言字段一样精确按键读取,才考虑全文搜索或向量检索,再按相关度与时间挑少量结果。LangGraph 的 Store 支持按命名空间存取记录,并可配置语义搜索;它的文档也以用户范围的命名空间展示跨线程读取。LangGraph:Add memory · LangGraph:Memory concepts 但向量相似只是“语义像”,不保证属于当前用户、仍然正确或适合本题;这些过滤必须独立完成。
用户改口、事实过期和删除时怎么办
几天后 U7 说“以后改用英文回答”。如果系统简单追加一条 language=en,下次可能同时搜出 zh 和 en,模型不知听谁的。对“首选语言”这类同一时间只能有一个有效值的记忆,应以同一稳定键更新为 en,版本从 1 增到 2,保留变更来源供审计,读取时只使用最新有效值。若两个设备同时更新,用数据库事务或乐观锁避免旧请求晚到后覆盖新版本。
有些记忆本来就会过期:例如“本周正在申请退款”是有时效的任务状态,过期后应重新向订单系统查实,不能把上个月的状态当今天的事实。用户画像中的“喜欢简短解释”相对稳定,但也应允许修改;身份、价格、库存、订单状态则优先取权威业务系统,而不是复用旧聊天总结。产品应提供查看、修改与删除路径。用户撤销记忆同意或删除偏好后,后续检索不能再返回它;若还有缓存、向量索引或异步副本,也要按产品保留策略同步处理。
记忆服务失败时,Agent 可以退回没有个性化偏好的安全回答,并明确不声称“我还记得”;不能因为查询超时就读上一位用户缓存。对于必须依赖记忆的操作,例如按已存地址下单,读不到或读到冲突时应向用户重新确认。长期记忆提升连续体验,但不可替代实时系统与用户授权。
怎么确认长期记忆真的有效
仅看“模型说我记得”没有说服力。准备一组可复现的跨会话测试:
| 测试输入 | 期待的实际行为 |
|---|---|
U7 在 T1 确认中文偏好,T2 用英文查单 | 读到 U7 的最新有效偏好,用中文回答;订单信息仍来自查单工具。 |
另一个用户 U8 也问英文问题 | 不读取 U7 的偏好,不能因缓存、向量相似或共享会话串入别人的记忆。 |
U7 在 T3 改为英文,随后开启 T4 | 只用新版本 en,旧的 zh 不再影响回答。 |
U7 删除偏好后重开会话 | 不再取到该偏好,也不能从仍存的摘要或索引恢复它。 |
| 记忆库超时或返回两条冲突记录 | 按产品安全规则降级或让用户澄清,不把错误记忆当确定事实。 |
指标也分开看:写入正确率看是否把该保存的保存、没把一次性话语误存;召回正确率看正确用户和问题是否取到了相关记忆;使用正确率看模型有没有按有效记忆行动;还要监控跨用户泄露、过期引用、删除后残留、延迟和存储成本。只算“查到了几条”会奖励大量无关召回,反而可能降低回答质量。
面试时怎么回答
长期记忆是 Agent 跨会话使用的持久信息,不是模型自动永久记住对话。实现时我会把当前线程的检查点和跨线程的记忆存储分开。写入端从用户明确表达中提取候选,核实来源、同意与归属,再按稳定键保存内容、来源、时间、版本和状态;读取端先用服务端登录身份限定用户范围,再按当前任务挑相关、有效的少量记忆放进模型输入。比如昨天用户确认“以后用中文回答”,今天新会话用英文查单,我会读取该用户的语言偏好,让模型用中文答,但订单状态仍实时查订单系统。用户改口时更新同一条记录,删除时让后续检索不可见;记忆库故障、过期或冲突时降级并重新确认。我会用跨会话、跨用户、更新、删除和故障样例验证整个链路。
如果面试官问“直接用向量数据库是否就实现了长期记忆”,应回答:向量库能帮助找语义相近的文本,却不负责决定什么值得存、属于谁、是否已过期、谁能修改、删后是否还会被读到。这些才是长期记忆在工程上最容易做错的部分。若问“LangGraph 的 checkpointer 能否解决”,说明它主要保存线程状态;跨线程用户记忆通常用 Store 或等价的应用数据库来管理。LangGraph:Persistence
资料依据
- LangGraph:Add and manage memory:线程内短期记忆与跨线程 Store、命名空间和语义搜索。
- LangGraph:Memory concepts:不同记忆范围与内容类型。
- LangGraph:Persistence:检查点与 Store 的职责边界。