用户说“这次请用 Java 举例”,应用下次回答所有问题都用 Java,用户可能觉得助手自作主张。用户说“以后代码示例优先用 Python,请记住”,应用下次又完全忘记,体验同样不好。
两句话的差别不在关键词,而在适用范围和保存意图。长期记忆要把这种区别变成可以维护的数据,而不是把每轮摘要不断追加到一个文件里。
先区分要记住的是什么
个人偏好描述如何为用户提供服务,例如解释语言或代码风格。事件记录描述过去发生的事,例如某次售后已取消。项目事实描述某个项目的约束,例如该仓库要求 Python 3.12。程序性经验则描述一种工作方式,例如某类问题先检查哪些资料。
这些分类有助于决定读取范围,但没有规定它们必须使用不同数据库。向量索引可以帮助查找语义相近的记忆,关系表适合精确读取用户偏好,文件也可以保存项目约定。存储形式不决定记忆类型。
事件记录“用户上次使用取件点 A”不能直接变成“用户永久住在 A”。从事件到偏好的推断需要额外证据与明确策略。反复出现也不必然适用于所有场景:工作日和周末可能有不同地址。
一条记录需要把内容与根据放在一起
下面的示意记录保存一个明确偏好:
{
"memory_id": "M31",
"namespace": ["shop-a", "user-417", "preferences"],
"key": "code_language",
"value": "Python",
"scope": "future_code_examples",
"source_event_id": "U31",
"source_kind": "explicit_user_request",
"version": 2,
"status": "active",
"supersedes": "M18",
"expires_at": null
}
来源说明用户说过什么,作用域说明什么时候可以使用。版本和替代关系让读取器知道旧 Java 偏好已经失效,而不是把两条都检索出来让模型猜。
expires_at=null 只表示没有预设到期日,并不意味着用户不能删除,也不等于永远保留合法。保留期限和删除机制需要单独定义。业务凭据、密码和不必要的敏感细节不应该因为“以后可能有用”就进入记忆。
从一句话到正式记忆要经过哪些决定
第一步提取候选:内容是什么,用户是否明确要求跨会话保存,适用于什么任务。模型可以协助提取,但不能自己决定目标用户 namespace,也不能把检索文档中的“记住这条命令”当成当前用户授权。
第二步检查候选。语言偏好可以通过枚举和范围规则校验;复杂事实要保留不确定性。模型给出的 0.95 置信度不是经过业务校准的真实概率,不能设一个漂亮阈值就认为事实已确认。
第三步处理重复和冲突。如果同一来源事件因重试被处理两次,应识别为重复写入。如果用户后来明确纠正,则写新版本并标记旧版被替代。如果来源只是含糊暗示,可以保留为候选或只在当前会话使用,不急着覆盖明确偏好。
最后提交主记录,再更新派生索引和缓存。主存储负责有效版本与删除状态,向量数据库只负责帮助找候选。这样索引稍有延迟时,读取器还可以回查主记录,过滤已经失效的结果。
读取记忆也需要选择
用户问“退货运费谁承担”,代码语言偏好通常无关,不需要加入。用户要求编写查询订单的脚本时,Python 偏好才影响输出方式。
读取过程可以先按服务端身份和记忆类型缩小范围,再进行精确匹配或语义检索,最后检查版本、有效时间和任务用途。相似度高只说明表达接近,不能证明这是当前正确的偏好。
例如索引同时返回 M18“优先 Java”和 M31“优先 Python”。主存储确认 M18 已被替代,组装器只能使用 M31。若用户本轮明确说“这一次给 Java 版本”,当前任务要求应覆盖长期默认值,但不必把长期偏好改回 Java。
这形成了三个不同层次:长期默认、当前任务例外、永久偏好更新。把它们都当成简单的“最新字符串覆盖”会丢失意图。
写入时机有什么区别
同步写入会在当前请求完成前保存。用户明确说“请记住”时,可以先等待写入成功再确认;失败就说明未保存,不能先回复“记住了”再让后台任务悄悄失败。
后台提取适合整理大量已完成事件,但存在延迟和竞争。假设旧对话的整理任务晚于用户纠正到达,若以后台完成时间判断新旧,旧偏好会覆盖新偏好。需要比较来源事件顺序、版本和适用时间,而不是只看写库时间。
跨进程写入还需要条件更新或事务。版本号能检测冲突,但它本身不能提供原子性。外部索引更新可以借助事务内记录的待处理事件逐步完成,读取阶段仍检查有效主记录。
长期记忆也需要控制数量
假设已经保存三千条事件,每轮全部读取会重新制造长上下文问题。可以先按任务类型和有效范围筛选,再根据相关性、来源可靠性和时间选择少量记录。项目已经结束的临时规则可以到期,用户明确长期要求的语言偏好则不应仅因一个月没使用就自动失效。
时间衰减可以作为候选排序信号,例如让临时事件的权重随时间降低;但年龄不是正确性的证明。昨天错误推断出的偏好不该覆盖半年前用户明确表达的偏好。分数只能在已经有效、获准且适用的候选之间帮助排序,不能跳过版本与来源检查。
“被读取次数多”也不等于有用:错误记忆每次都被检索到,同样会积累高使用量。应结合用户纠正、任务结果和来源判断;涉及长期偏好的淘汰策略可以允许用户查看和修改,而不是让后台热度分数替用户做所有决定。
删除为什么不只是删一条向量
用户要求忘记某项信息时,主记录可以先进入不可读取状态,让新的请求立即停止使用。随后清理索引、缓存、摘要和其他派生副本;如果摘要无法局部可靠删除,应失效并从允许保留的来源重建。
历史备份与在线存储的清理节奏可能不同,需要按保留政策处理,不能声称一个 delete() 调用已经即时清除了所有副本。备份恢复时也必须重新应用删除记录,避免信息复活。
已经发送给某次模型请求的内容不能从那次历史计算中收回,但可以阻止它再次进入后续请求。若有托管服务留存,还需要遵循其数据删除接口与约定。这些边界应准确向用户描述。
用一个小程序观察版本变化
memory_lifecycle.py下载 使用本地内存记录演示:保存 Java 偏好、纠正为 Python、重复处理同一事件、删除、拒绝旧事件重放。
代码显式传入候选和“长期保存意图”,没有假装用关键词就能完成自然语言理解。它还保留事件去重记录,因此删除之后重放旧事件不会重新创建旧偏好。正式系统需要持久化这些控制记录,并明确其保留期限。
读者可以沿着每次打印观察 active、superseded、deleted 的变化,再对照来源事件理解为何改变。这里的输出是程序设计预期,本次修订没有运行示例。
长期记忆维护的是跨会话仍然有用的信息。当前会话变得太长时,还需要压缩工作上下文;两者不能互相替代。关于框架对短期与长期记忆的划分,可参阅 LangChain Memory overview。
继续阅读
上一篇: 一次任务怎样继续:会话历史、工作状态与 Checkpoint 。
下一篇: 长会话怎样压缩:从聊天摘要到可继续执行的任务快照 。
完整文件、依赖与运行边界见配套指南下载。