长对话的短期记忆如何存储、压缩和淘汰?¶
- ID:Q038
- 难度:进阶 / 系统设计
- 标签:Short-term Memory、Conversation State、Summary、Compaction、Token Budget
可视化图解¶
flowchart LR
H[完整对话与事件流] --> W[滑动窗口保留最近轮次]
H --> S[阶段摘要]
H --> F[事实 决策 待办单独抽取]
W --> C[Context Builder]
S --> C
F --> C
C --> M[模型]
M --> H
核心结论¶
长对话管理不是把最旧消息删掉,而是把会话拆成“可恢复的完整记录、结构化任务状态、可检索历史和当前模型上下文”。 模型每轮只看到完成当前决策所需的信息,原始历史仍可回溯。
一、四份不同的数据¶
1. Event Log:完整不可变消息与工具事件
2. Run State:目标、计划、槽位、事实、待办
3. Summary / Memory Blocks:压缩后的阶段信息
4. Model Context:本轮选择后的有限输入
只维护 List[Message] 会导致:
- 上下文无限增长;
- 服务重启难恢复真实任务状态;
- 摘要后无法审计;
- 旧工具结果重复占用 Token;
- 关键事实和闲聊权重相同。
二、存储层¶
L1 进程内状态¶
当前正在执行 Run 的热状态,速度快但不可作为唯一来源。
L2 Redis / 数据库¶
保存 Session 状态、Checkpoint、最近消息和锁,支持多实例和故障恢复。
L3 对象存储 / 事件库¶
保存完整对话、工具大结果和审计 Trace,按需读取,不直接全部进入 Context。
这不是 GPU/CPU/磁盘的 KV Cache 分层。应用会话存储与模型推理 KV Cache 是不同层次。
三、压缩策略¶
1. 滑动窗口¶
保留最近若干消息。简单,但容易丢失早期目标、约束和承诺,只适合作为最外层兜底。
2. 滚动摘要¶
阶段性把旧消息压缩为摘要,再保留最近消息。
摘要应结构化:
{
"goal": "...",
"confirmed_facts": [],
"decisions": [],
"user_constraints": [],
"open_questions": [],
"failed_attempts": [],
"evidence_refs": []
}
比一段自由文本更容易更新和验证。
3. 实体与槽位抽取¶
把服务名、环境、时间范围、用户选择等关键字段放入显式 State,避免每轮从聊天历史重新推断。
4. 事件分段¶
按任务阶段、主题切换或子任务边界形成 Segment。当前 Context 加载当前 Segment,历史 Segment 按需检索。
5. 大结果外置¶
工具输出保存在 Artifact Store,消息中只保留摘要、统计和引用。
四、压缩触发条件¶
不应固定“每 N 轮”作为唯一条件,可综合:
- Token 使用达到预算比例;
- 子任务完成;
- 主题明显切换;
- 工具结果过大;
- Context Build 预计超限;
- Run 即将 Checkpoint 或暂停。
在任务边界压缩通常比任意轮数更不容易丢失语义。
五、哪些信息不能被普通摘要覆盖¶
- System / Policy;
- 用户明确约束;
- 高风险审批状态;
- 未完成任务;
- 关键实体和版本;
- 原始证据引用;
- 工具副作用结果;
- 幂等键和外部任务 ID。
这些应进入结构化 State 或 Pin 区,而不是寄希望摘要模型记住。
六、淘汰优先级¶
优先移除:
- 已被结构化状态吸收的重复表达;
- 已完成且无需后续依赖的中间过程;
- 可通过引用重新加载的大工具结果;
- 与当前任务无关的闲聊;
- 低价值重复观察。
最后才考虑压缩关键历史。输出 Token 预留和安全规则永远不能被挤占。
七、摘要漂移¶
滚动摘要多次“摘要上一份摘要”会累积错误。缓解:
- 定期从原始事件重建;
- 摘要输出结构化字段;
- 关键字段与 State 做一致性校验;
- 保存 source_event_ids;
- 用户纠正时更新事实并使旧摘要失效;
- 高风险决策回到原始证据。
八、Context Builder¶
def build_context(run, budget):
parts = [policy, run.goal, run.structured_state]
parts += select_recent_messages(run)
parts += retrieve_relevant_segments(run)
parts += select_evidence(run)
return fit_to_budget(parts, reserve_output=True)
不同内容按优先级和独立预算选择,不能只按时间倒序塞满。
九、评估¶
- 长会话任务完成率;
- 关键约束保留率;
- 摘要事实错误率;
- 用户纠正后旧事实残留率;
- 平均输入 Token;
- 压缩调用成本;
- 从原始事件恢复的成功率;
- 不同对话长度下的质量曲线。
常见错误回答¶
保留最近 20 轮,旧内容用 LLM 摘要。
这是可用起点,不是完整设计。没有结构化状态、证据引用、任务边界和摘要漂移控制。
LRU 淘汰记忆。
对话有顺序和依赖,普通缓存 LRU 不知道哪条信息是目标、权限或未完成任务。
面试口述版¶
我会分开保存完整 Event Log、结构化 Run State、阶段摘要和本轮 Model Context。短期热状态可在内存或 Redis,完整消息和大工具结果持久化,Context Builder 按目标、约束、当前计划、近期消息和相关历史分配 Token。压缩优先在子任务结束或预算超限时触发,摘要使用结构化字段并保留 source_event_ids。用户约束、审批状态、外部任务 ID 和证据引用进入显式 State,不能只靠摘要。这样服务可恢复、历史可审计,也能控制长对话 Token。
延伸阅读¶
结合个人项目¶
Claude Code 长会话中,代码文件和日志应外置;当前任务目标、已修改文件、未完成测试和用户限制进入结构化 State;已完成探索压缩为带文件引用的阶段摘要。