
1. 先搞清楚长期记忆到底在解决什么问题1.1 一个让所有智能体开发者头疼的场景先讲个真实的尴尬瞬间。我去年做过一个客服Agent用户在对话里明确说了我习惯用微信支付结果过了两天同一个用户换了会话窗口来问你们家能不能微信付款Agent一脸茫然地反问请问您指的是哪种支付方式——那一刻你就能深刻体会到没有长期记忆的智能体本质上就是个每天都要重新认识的陌生人。字节的DeerFlow在设计长期记忆时核心要解决的正是这个问题。大模型本身的上下文窗口是用完即焚的一轮对话结束后状态信息就彻底清零了。短期记忆充其量依赖对话历史塞给模型但跨会话、跨请求的用户画像偏好约束任务进度必须靠一套外部持久化机制来承载。所谓长期记忆通俗讲就是给Agent配一本笔记本把值得记住的事情写下来下次见面能翻出来用。1.2 长期记忆、会话记忆、RAG别把三件事混为一谈我在社区里经常看到有人把长期记忆和RAG搞混这里必须掰扯清楚。三者的定位完全不同机制保存什么有效期典型场景会话记忆当前会话的聊天轮次会话结束即失效多轮对话上下文长期记忆用户的偏好、约束、事实信息跨会话持久存在个性化推荐、状态恢复RAG外部知识库中的文档切片随知识库更新问答、知识检索用一个生活化的类比会话记忆是聊天时摊在桌上的便签聊完就扔长期记忆是随身带的笔记本重要的事情翻来覆去记RAG是图书馆你需要什么资料就去查什么资料。DeerFlow做的是笔记本这一层但它本身也依赖向量检索技术——也就是图书馆的检索系统——来快速找到对应的笔记页面。所以记忆模块的设计难点从来不是存下来而是知道该记什么、该忘什么、该翻哪一页。2. DeerFlow长期记忆的分层设计与核心组件2.1 记忆分层不要把所有的信息堆在一个桶里DeerFlow对记忆不是简单一刀切而是分了层。以开源实现来看至少能区分三个层级对话内上下文、会话级记忆和全局记忆。对话内上下文就是当前run的输入输出由框架自动维护会话级记忆作用在某个会话周期内全局记忆则是跨会话、跨请求共享的通常按用户维度组织。这个分层思路很关键。如果所有东西都塞进全局记忆检索时噪音会非常大——用户今天想喝咖啡和用户三个月前想买打印机这两条记忆在语义空间里可能相距不远同时检索出来会导致Agent犯昏。DeerFlow的MemoryScope参数就是用来做这件事的。我在实际项目中通常会为每个用户维护一套全局偏好记忆同时把任务进度类的信息放在会话级记忆里两者分开存储、分开检索。2.2 MemoryStore抽象换存储后端就像换插座DeerFlow把记忆的物理存储抽象成了MemoryStore接口。你不需要关心底层是Chroma、Redis还是某个云向量库只需要在初始化时声明后端类型。这个抽象价值很大——开发阶段用本地文件型的Chroma零依赖跑起来上线时切到生产级向量库业务代码一行都不用改。伪代码大概是这个样子from deerflow.agent import Agent from deerflow.memory import MemoryConfig, VectorMemoryStore # 开发环境本地Chroma memory_store VectorMemoryStore( collection_nametravel_prefs, embedding_providerbge-m3, vector_dbchroma, persist_directory./memory_db, ) agent Agent( modelyour-llm, memoryMemoryConfig( storememory_store, scopeglobal, auto_extractTrue, top_k3, min_score0.65, ), )注意这里的embedding_provider和vector_db是解耦的。embedding模型负责把文本变成向量向量库负责存储和ANN检索。DeerFlow支持多种embedding来源中文场景我强烈建议用bge系列比如bge-m3在中文短文本语义匹配上比通用的text-embedding-ada-002效果好不少。2.3 自动提取哪些对话内容值得写进长期记忆长期记忆最核心的环节不是存储而是提取策略。DeerFlow的auto_extract机制会在多轮对话结束后抽取其中对未来交互可能有用的信息。默认情况下它会把最近几轮对话送给LLM做一次轻量抽取产出自然语言形式的记忆条目。为什么是自然语言短句而不是结构化JSON我自己踩过这个坑早期项目里用JSON存记忆{preference: seat, value: window}后来发现检索回来的片段要拼回prompt时还要先做一层文本渲染而且LLM抽取时经常把字段搞错。DeerFlow直接用短句存比如用户偏好高铁靠窗座位不喜欢过道位一句话就是一个记忆条目。这种格式embedding效果好、LLM直接能消费、即使抽取时有一点语义损失也不会造成结构性破坏。当然如果你的业务确实需要结构化记忆比如记住用户订阅的日期、金额DeerFlow也允许你自定义extract函数返回JSON然后把渲染逻辑写在prompt层。2.4 记忆检索与注入不是把所有记忆都塞给模型检索环节同样有讲究。DeerFlow在收到新的用户输入时会用当前输入构造查询向量到向量库做相似度检索取回top_k条候选记忆。但检索到和注入prompt是两回事——候选记忆还要经过过滤、排序和截断才会真正进入上下文。我常用的注入模板长这样System: 你是一个旅行规划助手。请基于用户的长期偏好提供个性化建议。 [长期记忆] - 用户偏好高铁靠窗座位不喜欢过道位 - 用户近期计划前往杭州倾向避开人流密集的景点 [当前任务] 用户说推荐一下杭州的行程吧一个容易忽略的细节是检索query不应该只拿当前用户消息。把最近一轮助手回复也拼进query检索效果会好很多。比如用户问那杭州呢单独拿这句话去检索语义是不完整的但如果拼上前文帮我订了去杭州的车票靠窗位置就能准确命中相关记忆。DeerFlow的memory.search接口允许自定义query构造逻辑这个我在后面SSE封装章节会再展开。3. 一个示例看懂全流程旅行助手记忆Agent3.1 最小可运行代码骨架纸上谈兵没用直接上一个完整示例。场景是一个旅行规划助手我给它接上长期记忆让它记住用户的出行偏好。代码骨架如下from deerflow.agent import Agent from deerflow.memory import MemoryConfig, VectorMemoryStore memory_store VectorMemoryStore( collection_nametravel_prefs, embedding_providerbge-m3, vector_dbchroma, persist_directory./memory_db, ) agent Agent( modeldoubao-1.5-pro, memoryMemoryConfig( storememory_store, scopeglobal, auto_extractTrue, top_k3, min_score0.65, ), ) # 第一轮对话用户表达偏好 r1 agent.run(帮我订一张后天去杭州的高铁票尽量靠窗我不喜欢坐过道) print(r1) # 模拟新会话Agent重启或换session # 第二轮对话用户不提任何历史信息 r2 agent.run(推荐一下杭州的行程吧) print(r2)跑完这个示例你会发现第二轮回复里Agent主动带了根据您偏好靠窗座位、或者考虑到您希望避开人流这类话——这就是长期记忆生效了。3.2 第一轮对话记忆是怎么被写进去的分解一下第一轮对话发生的事。用户说尽量靠窗我不喜欢坐过道这两句话就是典型的可长期记忆信息。DeerFlow在生成回复后会触发auto_extract流程拿这轮对话可能连同上文一两轮去让LLM抽取可记忆条目得到类似用户偏好高铁靠窗座位不喜欢过道位的短句然后计算embedding写入向量库。写入时每条记忆会带上一组metadata我建议至少包含这些字段字段示例值作用user_idu_123456多用户隔离session_ids_789回溯来源created_at1720000000时效性判断updated_at1720000000冲突时覆盖依据importance0.8记忆权重注意这里有个细节DeerFlow的auto_extract是异步触发的它不影响主对话的响应延迟。上线的Agent不要因为记忆写入慢而拖慢首token时间。如果你用的是同步实现建议把写入调用放到后台任务队列里。3.3 第二轮对话记忆是怎么被用起来的第二轮的关键在于这是同一个Agent实例、但用户开的是全新会话。此时对话历史是空的Agent手里的牌只有用户当前输入推荐一下杭州的行程吧以及——从向量库里检索回来的记忆。流程是这样的用户消息进入Agent后memory模块先用当前输入构造query在向量库里做相似度检索命中了用户偏好靠窗座位用户计划去杭州等记忆条目过滤掉相似度低于阈值的噪音然后拼进system prompt。最终LLM看到的不只是用户的一句话还有背后几轮对话沉淀下来的用户画像。我做这个示例时最喜欢对比的一条是关闭记忆重新跑一遍第二轮Agent给的推荐是通用、模板化的什么杭州西湖十景不容错过打开记忆后Agent会主动避开热门拥挤景点推荐一些冷门但风景好的地方甚至说考虑到您不喜欢等待建议避开周末高峰。这就是长期记忆的价值——个性化不是调prompt调出来的而是数据积累出来的。3.4 底层调用顺序记忆在Agent运行链路里的位置把时序拉直会更清楚。DeerFlow里一次带长期记忆的Agent运行大致经历这几步接收用户输入构造memory检索query查向量库获取top_k候选记忆对候选记忆做相似度过滤和排序把命中的记忆渲染成文本拼入system prompt带着增强后的prompt进入LLM生成环节生成结束后异步抽取本轮的长期记忆写回向量库第2步和第6步是整个记忆系统的两个抓手——读和写。排查记忆问题时首先要确认的就是这两步有没有按预期执行。我后面讲可观测性的时候会再提。4. 生产化落地存储选型与参数调优4.1 向量库选型从Chroma到Redis/云向量库DeerFlow的MemoryStore抽象让换后端非常方便但选型仍然要花心思。开发阶段我都是用Chroma本地文件持久化重启不丢很适合跑通链路。生产环境就不能这么随意了要结合你的并发量和查询延迟来选方案优势劣势适用阶段Chroma轻量、零运维并发能力弱、单机瓶颈开发调试Redis 向量模块低延迟、生态成熟需要单独部署中等规模生产云向量数据库高可用、弹性扩缩容成本较高大规模生产我在生产环境里用的是云向量库核心原因是它的索引分片和自动扩缩容能力。记忆数据量涨起来很快——每个用户几十条记忆一万用户就是几十万条向量单机Chroma在这量级上查询延迟会明显劣化。4.2 几个关键参数怎么定记忆模块的参数看似简单实际调起来全是坑。我最常调的是三个top_k、min_score和embedding模型。top_k默认3到5之间比较合适。太低会漏掉重要记忆太高会把不相关的记忆也带进来反而干扰模型判断。我实测下来3条是最稳的基线——既能保住最关键的用户偏好又不会让prompt被记忆撑爆。min_score是相似度阈值默认0.6到0.7之间。低于这个阈值的记忆宁可不注入。原因很简单一条语义上完全不相关的记忆进入prompt后LLM会强行把它和当前任务扯上关系导致幻觉。这个阈值不是越高越好——我试过0.8结果很多有用的记忆全被过滤了Agent的个性化效果直线下降。embedding模型的选择对中文场景影响非常大。DeerFlow默认支持多种provider我强烈推荐在中文场景用bge-m3或同级别的中文embedding模型英文场景可以混用。选错embedding模型最典型的现象是记忆明明写进去了但检索永远命中不了因为向量空间根本不匹配。4.3 记忆更新与遗忘策略长期记忆系统最容易被忽视的是遗忘能力。只记不忘的笔记本迟早会变成一团乱麻。DeerFlow里常见的策略有几个覆盖策略对同一类信息新的记忆条目优先于旧的。比如用户先说喜欢靠窗后来说其实靠不靠窗都行系统应该识别出这是对旧记忆的更新而不是新增一条矛盾记忆。重要性衰减每条记忆都有importance字段长时间没有被检索到的记忆importance会衰减达到阈值后进入清理候选。TTL过期给记忆设置有效期比如用户计划7月18日去杭州这种任务型记忆日期过后就可以清了。我自己的经验是记忆清理任务跑一个定时任务就好每天凌晨扫一遍把importance低于阈值和超过TTL的记忆删掉。不要小看这件事——我见过一个项目运行半年后记忆库膨胀了三倍检索延迟从20毫秒飙到200毫秒最后全量清理才救回来。5. 实测踩坑记录记忆污染、冷启动、多用户隔离5.1 记忆污染一次错误写入如何带偏整个Agent长期记忆最严重的坑不是记不住而是记错了。我遇到过最典型的案例用户在对话里说我不喜欢坐过道不过偶尔也会妥协auto_extract把这句话抽成了用户不喜欢坐过道忽略了转折。之后Agent任何一次行程推荐都优先避开过道位把好好的选择空间变窄了。排查这类问题时有一个重要的检查项记忆条目的可追溯性。每条写入的记忆必须带session_id这样一旦发现记忆污染能快速定位到是哪一轮对话产生的。我在项目里还加了一种处理就是关键词级别的矛盾检测写入新记忆前先检索已有记忆中是否存在同主题的条目如果语义向量相似度很高但情感极性相反就把决策权交给人工或走冲突解决策略。5.2 冷启动与记忆置信度新用户没有任何历史记忆时检索结果自然为空这时Agent自动退化为无记忆的通用模式。这个逻辑本身不复杂但有个隐藏问题如果用户刚开始用的前几轮就被auto_extract抽到了垃圾记忆后面整个生命周期的记忆质量都会受影响。解决方案是引入置信度机制。DeerFlow的记忆条目里可以附加来源置信度——比如单次对话抽取的置信度为0.6多次对话验证一致的提升到0.9被用户明确纠正过的直接拉黑。我实测下来这个机制非常有用它能把记忆系统的容错率提上去避免一两条坏记忆毁掉整个用户画像。5.3 多用户隔离别让A的记忆串到B身上这个坑我愿称之为线上事故级别的隐患。向量库的相似度检索是全局的如果查询时没有强制过滤user_idA用户的记忆极有可能被B用户检索到。尤其是冷启动用户没有记忆时检索结果里混入别人记忆的概率会增大这时候DeerFlow会把别人的偏好当作当前用户的偏好注入prompt效果完全不可控。正确的做法是在检索时把user_id作为强制过滤条件而不是后置过滤。也就是在向量检索阶段就限定partition或添加filter先圈定当前用户的子集再做相似度搜索。DeerFlow的memory.search支持metadata过滤参数强烈建议所有生产环境强制开启。不然等你发现这个用户怎么莫名喜欢某个从没聊过的话题时可能已经跑了一个月了。6. 二次开发实战记忆与SSE流式、可观测、人机协同的配合6.1 封装SSE流式接口时的记忆处理顺序很多团队基于DeerFlow做二次开发时第一步就是封装SSE流式接口给前端用。这里有个顺序问题很容易被忽略记忆检索必须在流式生成开始之前完成而不是边推流边检索。import json from fastapi import FastAPI from fastapi.responses import StreamingResponse async def build_memory_augmented_prompt(user_input: str) - str: # 流式开始前先同步完成记忆检索 memories await agent.memory.asearch(user_input, top_k3) return render_prompt_with_memories(memories) app.post(/chat/stream) async def chat_stream(payload: dict): user_input payload[message] system_prompt await build_memory_augmented_prompt(user_input) async def event_source(): async for chunk in agent.astream(user_input, system_promptsystem_prompt): yield fdata: {json.dumps(chunk, ensure_asciiFalse)}\n\n yield data: [DONE]\n\n return StreamingResponse(event_source(), media_typetext/event-stream)这样做有两个考虑。第一SSE的流式语义是生成过程逐token推送中间穿插一次向量检索会让事件流变得混乱前端解析状态机要额外兼容检索事件和生成事件。第二流式场景里用户最关心首字延迟TTFT记忆检索如果放在生成前只增加一次固定延迟放在生成过程中会让首字时间不可预测。顺带说一个小优化同一会话连续多轮流式对话中如果用户没有明显切换话题上一轮检索到的记忆可以直接复用缓存省掉这次的向量检索。这种优化对TTFT的降低非常明显实测能把首字延迟从300毫秒降到80毫秒左右。6.2 用可观测面板排查记忆没生效的问题DeerFlow自带可观测能力这个千万别浪费。在观测面板里每个Agent run都会记录记忆模块的关键指标memory_search_count本轮触发了几次记忆检索。如果是0说明记忆压根没被调到问题出在配置而不是记忆本身。memory_hits检索命中了多少条候选记忆。如果hits为0去看embedding模型和相似度阈值。命中记忆的平均相似度如果普遍在0.7以上embedding选型基本没问题如果普遍在0.5以下大概率是向量空间不匹配。我排错时的固定动作是先看memory_search_count再看memory_hits最后才看prompt。大部分记忆不生效的问题根源都在前两步。还有一种情况是命中了但被min_score过滤掉了调整阈值前要先确认是不是embedding模型选错了。这批排查链路不看可观测面板全靠猜的话效率会低得令人发指。6.3 人机协同场景记忆如何参与人工决策DeerFlow的人机协同机制我是在一个订单审批类项目里实际用到的。Agent在执行业务动作前会进入pending状态等待人工确认。这个场景下长期记忆能帮上大忙审批人面对的是一个Agent准备下单的请求如果能把相关的用户偏好记忆一起展示出来审批效率会高很多。比如Agent准备为用户订一张高铁票审批面板上同时呈现用户偏好靠窗座位用户之前明确表示不接受一等座以上价位。有了这些记忆作为参考上下文人工确认时就不需要再翻完整对话记录了。更进一步的话人工在审批时做的决策批准/驳回/修改参数可以反写回记忆库——比如审批人把座位从一等座改成了二等座这个修改结果作为更准确的事实记录下来后续Agent就不会再犯同样的错。这套记忆辅助人审、人审反哺记忆的闭环是我认为DeerFlow长期记忆在生产环境里最有想象力的用法。也是真正让我觉得长期记忆不只是一个技术点而是一整套Agent工作流里不可缺少的底座。我个人折腾DeerFlow记忆模块这么久最深的体会是长期记忆的作用不是存得多而是找得准。你不需要什么花哨的技巧把分层设计、提取策略、检索过滤这几件事做扎实效果就已经吊打大部分简单拼接RAG的伪记忆方案了。如果你也在基于DeerFlow做智能体建议先拿我这个旅行助手示例跑一遍全流程再逐步替换成自己的业务场景。上手之后你会发现记忆这两个字背后藏着很多值得抠的细节。