DeerFlow 记忆系统:让 AI Agent 真正“认识“你

为什么 AI 总是"失忆"?

你有没有遇到过这种情况:跟 AI 聊了半天,换了个新对话窗口,它就把你忘得一干二净。

你得重新告诉它你是做什么的、你喜欢什么风格、你上次让它改的那个 bug 修好了没有。每次开新对话都像第一次见面,非常累。

这不是 AI 笨,而是大语言模型天生就没有"记忆"------每次对话对它来说都是全新的,上一次聊了什么,它完全不知道。

DeerFlow 的记忆系统就是为了解决这个问题。它做的事情用一句话概括:每次聊完自动记笔记,下次聊的时候自动把笔记递给 AI 看。

整体思路:记笔记 + 看笔记

想象你有一个很靠谱的秘书。每次你和客户开完会,秘书会帮你整理一份会议纪要,存进档案柜。下次你再见同一个客户之前,秘书会把之前的纪要放在你桌上,让你快速回忆起之前的上下文。

DeerFlow 的记忆系统就是这个秘书。整个流程分四步:

复制代码
1. 你和 AI 聊完了
2. 系统在后台悄悄把对话内容做一次"结构化反思"------提取关键信息
3. 提取出来的信息存到一个 JSON 文件里
4. 下次你开新对话,系统把之前提取的信息塞进 AI 的上下文里

就这样,AI 在跟你聊天之前就已经"知道"你是谁了。

捕获:聊完之后悄悄记下来

什么时候记?

每次 AI 回复完你之后,系统会自动触发一次"记忆捕获"。这个过程你完全感知不到,不会影响回复速度。

记什么?

原始对话里有很多"噪音"------工具调用的 JSON、文件上传的记录、AI 的中间推理步骤------这些东西对理解"用户是谁"没有帮助,反而会干扰提取。所以第一步是过滤:只保留你说了什么、AI 最后回答了什么。

过滤之后,系统还会扫描你有没有说出以下两类话:

"你搞错了"------比如你说"不对"、"错了"、"重新来"。系统会标记这是一个"纠正信号",后续提取记忆时会特别注意识别哪些旧信息是错的。

"就是这个"------比如你说"很好"、"就是这样"、"perfect"。系统会标记这是一个"正向强化",说明 AI 之前的理解是对的,值得记住。

一个容易踩的坑

你可能会问:为什么不在 AI 处理的过程中记,而要等到处理完之后?

因为记忆更新不是同步的------它会在后台排队等一会儿再处理。而 Python 的线程之间是互相隔离的,如果等到后台线程处理时才去获取"这是哪个用户",就会拿到空值,导致张三的记忆混进李四的档案里。

所以系统在入队的那一刻就把用户 ID 快照下来,绑在这批消息上,后面不管什么时候处理都不会搞混。

防抖:别每句话都记一次

你和 AI 聊天时经常会连续发好几条消息:"帮我写个接口"、"要支持分页"、"返回格式用 JSON"。如果每条消息都触发一次记忆提取,一来浪费钱------每次调用 LLM 都要花 token 费,二来质量也不好------单看一条消息,LLM 很难判断你到底在做什么。

所以系统用了防抖机制:你发第一条消息时启动一个 30 秒倒计时,如果 30 秒内又有新消息,倒计时重新开始。直到你停下来 30 秒没说话,系统才把这段时间的所有消息打包发给 LLM 做一次提取。

这就好比你不会每说一句话就让秘书记一次笔记,而是等一段话说完了再统一整理。

长对话的保护机制

DeerFlow 有一个上下文摘要功能:当对话太长、超出模型的上下文窗口时,会把早期的消息压缩成摘要,腾出空间。

这带来一个问题:被压缩掉的消息如果还没来得及提取记忆,就永远丢失了。

所以系统加了一个保护钩子:在摘要压缩即将发生的那一刻,先把即将被丢弃的消息紧急塞进记忆队列,确保不会遗漏。这就像秘书发现你要扔掉一叠旧文件,赶紧先翻一遍看看有没有重要的东西。

提取:让 AI 帮你"结构化反思"

防抖时间到了之后,系统调用 LLM 来做记忆提取。这里的设计很有讲究------不是简单地让 AI "总结一下这段对话",而是要求它做一次结构化反思

反思什么?

LLM 拿到对话内容后,需要回答三个问题:

  1. 有没有犯错? 对话中 AI 的回答有没有出错?用户有没有纠正它?
  2. 用户是什么样的人? 用户的技术背景、偏好、工作习惯是什么?
  3. 用户在做什么? 当前的目标、关注点、项目上下文是什么?

提取出来的记忆长什么样?

LLM 会输出一个 JSON,里面包含两类内容:

摘要段落------对用户画像和历史背景的自然语言描述。比如"后端开发工程师,主要用 Python 和 FastAPI,偏好简洁的代码风格"。这些摘要分六个维度:工作背景、个人偏好、当前关注、近期历史、早期历史、长期背景。

结构化事实------一条一条的独立信息,每条都有分类和置信度评分。比如:

  • "偏好 TypeScript + Next.js 做前端"------分类是 preference,置信度 0.92
  • "使用 uv 作为包管理器"------分类是 knowledge,置信度 0.95
  • "之前给的数据库方案有问题"------分类是 correction,置信度 0.95

置信度是 LLM 自己判断的:用户明确说了的给 0.9 以上,明显暗示的给 0.7 到 0.8,推断出来的给 0.5 到 0.6。后续注入时,置信度高的事实优先展示。

合并:新旧记忆怎么融合?

LLM 返回结果后,系统需要把它合并到已有的记忆里。这个过程有几个规则:

  • 摘要段落直接覆盖------新的描述替换旧的
  • 新事实要和旧事实去重------如果已经有了"喜欢 TypeScript",就不会再存一条一模一样的
  • 置信度低于 0.7 的事实丢弃------不够确定的信息不记
  • 事实总数超过 100 条时,淘汰置信度最低的------给新记忆腾空间
  • 被用户纠正的旧事实删除------如果 LLM 判断"之前记的方案 X 是错的",就把那条删掉
  • 关于文件上传的句子剥离------上传文件是临时的,不应该记住

存储:一个人一个档案柜

记忆数据存在 JSON 文件里,每个用户一个独立的文件,物理上完全隔离。

复制代码
users/
  ├── alice/
  │   └── memory.json      ← Alice 的记忆
  ├── bob/
  │   └── memory.json      ← Bob 的记忆
  └── default/
      └── memory.json      ← 未登录用户

为什么用文件而不是数据库?

因为数据量很小------一个用户通常也就几十条 fact,一个 JSON 文件完全够用。文件的好处是简单、没有额外依赖、出了问题直接打开文件就能看、备份就是复制文件。

如果将来数据量大了需要换数据库,也只需要替换存储层的实现,上层逻辑完全不用改------因为存储层用了抽象接口设计。

写入安全:原子操作

写文件时有一个风险:如果写到一半进程崩溃了,文件就会损坏,之前存的记忆全没了。

系统的解决方案是原子写入:先写到一个临时文件里,写完了再一步到位替换掉原文件。在 Linux 和 macOS 上,文件替换是原子操作------要么完全成功,要么完全不发生,不存在"写了一半"的中间状态。

读取加速:内存缓存

每次读记忆都去磁盘太慢了。系统维护了一个内存缓存,读取时先检查文件有没有被改过------通过文件的修改时间判断。没改过就直接用缓存,改过了才重新读文件。

注入:怎么让 AI "看到"记忆

记忆存好了,下一步是在 AI 聊天之前把它"递"给 AI 看。

为什么不直接写进 system prompt?

你可能会想:把记忆写进 system prompt 不就行了?这样每轮对话 AI 都能看到。

问题是:system prompt 是 LLM 处理的第一段文本,所有对话共享。如果把记忆写进去,每个用户的 system prompt 都不一样,LLM 的 prefix cache 就没法复用------这会导致每次请求都要重新处理 system prompt,既慢又贵。

所以系统把记忆放在了 HumanMessage 里------system prompt 保持不变,记忆作为一条独立的隐藏消息插进对话历史。这条消息标记了"前端不显示",你看不到它,但 AI 能看到。

什么时候注入?

  • 第一轮对话:注入完整的记忆内容,包括用户画像、历史背景、所有事实
  • 同一天的后续轮次:不注入,因为记忆已经在历史里了
  • 跨天对话:如果对话跨越了午夜,额外注入一条日期更新提醒,让 AI 知道"今天"变了

ID-Swap:一个精巧的优化

首轮注入时用了一个巧妙的技术:注入的隐藏消息替换了你发的第一条消息的位置,你的原始内容被挪到了后面。

为什么要这样做?因为所有用户的对话都以相同的记忆内容开头,LLM 处理时这部分可以命中 prefix cache,不用每次都重新算。就像你翻开一本书,前几页是目录------不管谁来读,目录都一样,可以直接跳过。

这个优化能把 token 消耗和响应延迟都降下来。

Token 预算:不能塞太多

记忆内容有 token 预算,默认 2000 个 token。系统会优先塞摘要段落,剩下的空间按置信度从高到低塞事实。预算满了就不再塞了------宁可少记几条,也不能把上下文窗口撑爆。

格式化时用 tiktoken 精确计算 token 数,不会超预算。

配置和 API

配置项

yaml 复制代码
memory:
  enabled: true                    # 总开关,关掉就完全不用记忆
  debounce_seconds: 30             # 防抖时间,等多久再提取
  model_name: null                 # 用哪个 LLM 做提取,null 就用默认的
  max_facts: 100                   # 最多存多少条事实
  fact_confidence_threshold: 0.7   # 低于这个置信度的事实不存
  injection_enabled: true          # 是否注入到对话里
  max_injection_tokens: 2000       # 注入的 token 预算

REST API

系统提供了完整的 API,可以手动查看、编辑、导入导出记忆:

方法 路径 说明
GET /api/memory 查看当前用户的所有记忆
DELETE /api/memory 清空记忆
POST /api/memory/facts 手动添加一条事实
PATCH /api/memory/facts/{id} 修改一条事实
DELETE /api/memory/facts/{id} 删除一条事实
GET /api/memory/export 导出为 JSON 文件
POST /api/memory/import 从 JSON 文件导入

所有操作都限定在当前用户范围内,不会看到别人的数据。

Q A

为什么异步而不是实时? LLM 调用要 1 到 3 秒,如果同步等待,你每次发消息都会多等这么久。放到后台异步处理,你完全感觉不到。

为什么用 HumanMessage 而不是 system prompt? 为了 prefix cache。所有用户共享同一个 system prompt,cache 命中率高。如果把记忆塞进去,每个用户都不同,cache 就废了。

为什么防抖而不是每条消息都处理? 成本和质量的双重考量。一次对话里的多条消息通常高度相关,打包处理比逐条处理更省钱,LLM 看到更完整的上下文提取质量也更好。

为什么需要保护钩子? 长对话会被压缩摘要,压缩掉的消息如果还没提取记忆就永远丢了。这个钩子确保"即将丢失"的消息被及时捕获。

为什么用 JSON 文件而不是数据库? 数据量小、简单可靠、无需额外依赖。万一出问题,直接打开文件就能看到所有记忆内容,排查成本极低。

总结

DeerFlow 的记忆系统做的事情,本质上和人类的记忆是一样的:

  • ------捕获对话,过滤噪音,识别重要信号
  • ------LLM 做结构化反思,提取关键信息
  • ------存到文件里,一个人一个档案柜
  • ------下次聊天之前把笔记递给 AI 看

整个过程对你完全透明。你不需要做任何事情,系统在后台默默运转,随着对话越来越多,AI 对你的理解也会越来越深。

相关推荐
prog_61033 小时前
【笔记】用cursor手搓cursor(六)deepseek v4
人工智能·笔记·agent·deepseek·claude code
Loo国昌3 小时前
从 Agent 编排到 Skill Runtime:企业 AI 工程化的下一层抽象
大数据·人工智能·后端·python·自然语言处理
凌波粒3 小时前
深度学习入门(鱼书)第2章笔记——感知机
人工智能·笔记·深度学习
南屹川3 小时前
【Python进阶】Python元类编程深度解析
人工智能
人工智能培训3 小时前
中国人工智能培训网—AI系列录播课
大数据·人工智能·机器学习·计算机视觉·知识图谱
liuyunshengsir3 小时前
PyTorch 最小模型转 ONNX 完整样例
人工智能·pytorch·python
_oP_i3 小时前
FFmpeg 如何与ai结合剪辑出效果好的视频
人工智能·ffmpeg·音视频
脑极体3 小时前
嗜血的AI
人工智能·chatgpt
z202305083 小时前
RDMA之RoCEv2 无损网络PFC 、DCQCN 和ECN (7)
linux·服务器·网络·人工智能·ai