05 - 记忆蒸馏:Deep Dream 的 LLM 驱动记忆整理
学习目标
理解 CowAgent 的 Deep Dream 机制如何实现定期记忆蒸馏,包括输入材料收集、LLM 蒸馏、输出解析与梦境日记写入。
前置知识
记忆蒸馏的通用原理见 记忆蒸馏:LLM 驱动的记忆整理。本章聚焦 CowAgent 的具体实现。
项目实践
蒸馏触发链路
CowAgent 的 Deep Dream 由每日刷新定时器触发:
每日摘要(Daily Summary)
每日 23:55(带 50-55 分钟的随机抖动),_daily_flush_loop 触发:
- Phase 1:对所有活跃 Agent 调用
create_daily_summary()- 提取当前 session 的消息
- LLM 摘要为”用户问了 X → 助手回答了 Y”格式
- 写入
memory/YYYY-MM-DD.md
- Phase 2:选一个 candidate 执行
deep_dream()- 读取 MEMORY.md + 最近 N 天的每日记忆
- LLM 蒸馏输出两部分
Deep Dream 蒸馏细节
# 伪代码 — Deep Dream 核心流程def deep_dream(self, user_id=None, lookback_days=1, force=False): # 1. 收集材料 memory_content = self._read_main_memory(user_id) # MEMORY.md daily_content, has_content = self._read_recent_dailies(user_id, lookback_days)
if not has_content: return # 无近期日记,跳过
# 2. 去重:同一天相同输入不重复蒸馏 dedup_key = f"{today_str}:{daily_hash}" if dedup_key == self._last_dream_input_hash and not force: return
# 3. LLM 蒸馏(动态 max_tokens,基于输入大小) dream_max_tokens = max(2000, min(input_chars, 8000)) response = self.llm_model.call(request)
# 4. 解析输出 new_memory, dream_diary = self._parse_dream_output(raw)
# 5. 写入 MEMORY.md(覆盖) main_file.write_text(new_memory)
# 6. 写入梦境日记 if dream_diary: self._write_dream_diary(dream_diary, user_id)规则摘要降级
当 LLM 不可用时,_extract_summary_fallback() 使用规则生成摘要:
- 用户: 询问如何部署 Docker → 回复: 提供了 docker compose 配置文件说明- 用户: 分享了 CowAgent 的架构 → 回复: 分析了三层记忆设计提取算法:
- 提取用户消息的前 120 字符
- 提取助手回复的第一条有意义的内容(跳过 markdown 标题、分隔符等噪声)
- 格式化为
"- 用户: X → 回复: Y"的紧凑格式
调度器消息过滤
Deep Dream 在整理记忆时自动过滤调度器注入的消息:
def _strip_scheduler_pairs(cls, messages): """Drop scheduler-injected user/assistant pairs from a flush batch.""" # 识别以 "[SCHEDULED]" 开头的用户消息,跳过其配对的助手回复这避免了定时任务的执行日志(如 “11:28 price=1013, normal”)污染长期记忆。
问题与规避
| 问题 | CowAgent 的规避策略 |
|---|---|
| 蒸馏后 MEMORY.md 丢失信息 | 蒸馏 prompt 要求”合并提炼”而非”删除”,且保留梦境日记作为审计记录 |
| 蒸馏输入无新内容 | 输入哈希去重,相同输入不重复蒸馏 |
| LLM 蒸馏输出格式错误 | _parse_dream_output() 严格解析 [MEMORY]/[DREAM] 标记,无 [MEMORY] 部分则跳过写入 |
| 蒸馏阻塞主回复流 | 每日摘要异步执行(后台线程),Deep Dream 在摘要完成后执行 |
| 调度器消息干扰蒸馏 | _strip_scheduler_pairs() 自动过滤 |
设计取舍
为什么每日摘要和 Deep Dream 分开执行?
每日摘要记录”今天发生了什么”,Deep Dream 提炼”哪些值得长期记住”。分开执行使得:
- 每日摘要轻量(500 tokens max_tokens),每次 trim 都触发
- Deep Dream 重量(2000-8000 tokens),每日执行一次
- 即使 Deep Dream 失败,每日记忆仍然保留
为什么使用覆盖写入而非追加?
MEMORY.md 需要保持精炼(目标 50 条以内)。追加会导致膨胀,覆盖写入配合 LLM 的合并提炼能力确保密度。代价是如果 LLM 输出不完整,可能丢失旧条目——因此蒸馏 prompt 要求输出”完整的更新后内容”而非”新增条目”。