第 9 章:记忆与上下文压缩 —— 三种“记住”不能混在一起
DeerFlow 同时有 checkpoint、summary 和 memory。它们都像记忆,却服务于完全不同的时间尺度。
三层记忆模型
| 层次 | 保存什么 | 主要消费者 | 生命周期 |
|---|---|---|---|
| Checkpoint | 精确 ThreadState | LangGraph / 恢复逻辑 | 每个图步骤与线程 |
| Summary | 被移出活跃窗口的对话语义 | 当前 Agent | 长对话内部 |
| Memory | 跨线程的用户/Agent 长期信息 | 后续会话 | 用户或 Agent 级 |
Checkpoint 不是模型记忆:存在数据库里不代表每次都放进 prompt。Summary 不是完整历史:它是有损的活跃上下文替代。Memory 也不是聊天备份:它只应保存稳定偏好、事实和长期背景。
Memory 的两种工作模式
Middleware 模式
每次 Agent 结束后,MemoryMiddleware.after_agent 把原始 messages 交给 MemoryManager。Manager 只选择用户输入和最终 assistant 响应,忽略工具过程,再通过 debounce 队列异步抽取/合并记忆。
异步路径使用 asyncio.to_thread 或 manager 的 async 接口,避免同步文件/网络 IO 阻塞 LangGraph event loop。user_id 和 trace_id 在入队时显式捕获,因为 debounce timer 运行在线程中,ContextVar 不会自动传播。
Tool 模式
模型通过 memory search/read/write 工具主动管理记忆,适合有检索后端的场景。若 backend 不实现 search,启动时直接报配置错误。某些后端仍需要 passive middleware 写入,因此“tool mode”不等于无条件移除 MemoryMiddleware。
MemoryManager 是可插拔后端
基类负责一致行为:输入规范化、冲突检测、队列、callbacks、用户/Agent 命名空间。具体 manager 可落到本地文件、数据库、OpenViking 等。manager_class 通过反射解析,允许第三方实现,但必须遵守 capability 声明,例如 supports_search。
自动摘要何时触发
DeerFlowSummarizationMiddleware 扩展 LangChain 的 SummarizationMiddleware。触发器可按 token 或消息数量配置。准备阶段:
- 给缺 ID 的消息补 ID;
- 把已有
summary_text也纳入 token 估算; - 判断阈值,或由
/compact强制执行; - 计算 cutoff,把消息分成 summarize 与 preserve;
- 抢救动态 context reminder 及其 ID-swap 同伴;
- 调摘要模型生成新 summary;
- 只有生成成功才执行 hooks 和替换状态。
失败时不删除任何消息。这个顺序是关键:如果先 flush memory、再发现摘要为空,系统会处于“长期记忆已更新但活跃上下文未压缩”的半提交状态。
摘要输入本身也要防膨胀
待摘要消息可能已经超过摘要模型窗口。中间件先用 trim_messages 按 token 裁剪;若已有旧 summary,把预算大致分给“旧摘要”和“新消息”。格式化后将文本放进 <existing_summary> 与 <new_messages>,并转义 < > &,防止用户消息伪造闭合标签改变摘要指令结构。
摘要调用打上 middleware:summarize 与 nostream 标签:前者让 RunJournal/token 统计正确归因,后者防止摘要文字冒充主回答发送给客户端。
状态替换语义
摘要成功后返回:
{
"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES), *preserved_messages],
"summary_text": new_summary,
}2
3
4
也就是说,活跃 messages 只保留近期尾部和受保护消息,旧内容由 summary_text 表示。但 UI 历史不是简单展示当前 checkpoint messages;Gateway 的 run event/history API 仍保存可见历史,因此用户看到的聊天不会因为 /compact 而消失。
DurableContext 是压缩后的桥
DurableContextMiddleware 在摘要前捕获:
- 已完成的 delegation,写入
delegations; - 真正加载过的技能文件,写入
skill_context。
模型调用前,它再把 summary、delegation ledger 和技能引用注入上下文。这样压缩不会让 Agent 忘记“子任务做过什么”和“之前遵循过哪个技能”。
手动 /compact
前端把 /compact 视为内置 composer 命令,调用 POST /threads/{id}/compact。Gateway 在没有消息或有活跃 run 时拒绝,防止摘要与正在写 checkpoint 的 Agent 竞争。强制 compact 绕过自动阈值,但仍走同一套摘要和状态替换逻辑。
设计取舍
- 异步 memory:降低主回答延迟,代价是最终一致;
- 有损 summary + 完整 UI 历史:模型窗口与用户审计需求分离;
- 摘要失败保持原状态:宁可晚点压缩,也不提交空摘要;
- 引用而非技能正文进 state:降低 checkpoint 体积,运行时再按可信路径重载。
源码锚点
agents/middlewares/memory_middleware.pyagents/memory/manager.pyagents/middlewares/summarization_middleware.pyagents/middlewares/durable_context_middleware.py
下一章看另一种上下文节约:外部工具和技能如何按需出现。