用户说“还是按上次那个地址寄”,系统怎样知道“上次那个地址”指什么?这是聊天应用开始变得像助手的时刻,也是状态管理真正变麻烦的时刻。
第 6 章集中讨论 Memory。读这一章前,我容易把记忆理解成“把历史对话都塞回 Prompt”;读完以后,最大的变化是开始区分原始记录、当前上下文和长期事实。
版本说明:LangChain 的记忆与 Agent 状态接口已经历多次演进。这里讨论的是记忆设计问题,不建议直接照搬书中的旧类名。
模型本身不会记住上一轮
一次模型调用通常只看到本次请求提供的内容。所谓“记得”,其实是应用在调用前读取状态、把相关信息放进上下文,并在调用后保存新状态。
可以把它拆成三类:
| 类型 | 示例 | 保存方式 |
|---|---|---|
| 会话历史 | 用户和助手的原始消息 | 按会话持久化 |
| 工作记忆 | 本轮回答需要的最近消息或摘要 | 动态选择后放入上下文 |
| 长期记忆 | 用户偏好、地址、任务进度 | 结构化存储并按需读取 |
三者混成一个无限增长的消息列表,会同时带来成本、噪声、隐私和一致性问题。
常见策略各自牺牲了什么
完整缓冲保留全部消息,最容易实现,也最忠实。但对话越长,token 成本越高,早期关键信息还可能被大量闲聊淹没。
窗口记忆只保留最近若干轮,成本可控,却可能把前面已经确认的重要约束丢掉。
摘要记忆把旧对话压缩成摘要。它节省上下文,但摘要也是模型生成的,可能遗漏数字、否定条件和用户态度。摘要最好保留版本,并允许回查原始消息。
实体或结构化记忆抽取姓名、偏好、地址和任务状态,查询效率高,但需要处理更新、冲突和删除。例如用户说“以后不要寄到公司”,系统不能只新增一条偏好,而要正确替换旧状态。
一个预约助手该怎样记
假设用户先说“周五下午帮我约牙医”,几轮后又说“改到下周一上午”。
如果只保存聊天文本,模型每次都要从长记录里重新推断最终时间。更稳妥的做法是同时维护结构化状态:
{"service": "牙医","date": "下周一","period": "上午","confirmed": false
}
模型负责从自然语言中提取候选修改,程序负责日期解析、字段校验和最终写入。真正提交预约前,还要把明确日期和时间展示给用户确认。
这个例子也说明:Memory 不只是“聊天体验功能”,它已经接近业务状态管理。
记忆最难的是冲突,不是存储
用户可能先说喜欢简洁回答,后来又要求某个话题详细解释;资料库可能保存旧公司名称,当前会话里用户又给出新名称。
因此长期记忆至少需要来源、更新时间、作用范围和可信级别。遇到冲突时,可以按明确规则选择,或者向用户询问,而不是让模型默默猜一个。
敏感信息还要考虑授权、加密、保留期限和删除能力。能保存不代表应该保存,能从历史推断出来也不代表可以长期固化。
这章给我的启发
记忆模块真正要解决的,不是“怎样装下更多聊天记录”,而是“此刻哪些信息值得进入上下文”。
原始消息负责追溯,摘要帮助压缩,结构化状态支撑业务,长期记忆提供跨会话连续性。把这些职责分开,系统才不会一边声称记得用户,一边又把过期信息当成事实。
