
1. 没有记忆的智能体本质上是在跟陌生人反复自我介绍我在把智能体从Demo推向能用的过程中第一次被难住的不是模型选型不是工具调用而是记忆。具体场景是这样的我让智能体帮我对接业务系统它需要逐步完成拉取上周销售数据、对比环比、生成异常标注、输出报告这四件事。拆成单个步骤它都做得很好但一旦连起来跑问题就暴露了——执行第三步时它已经忘了第一步的结果甚至在多轮对话里用户十分钟前说过的偏好到后面它完全当没听过。这几乎是所有智能体项目都会撞上的墙。模型本身是无状态的每轮调用像一次失忆重启它看到的只有当前请求里的那几行字。所谓让智能体拥有记忆本质就是在模型外面再加一层存储和检索机制把跨轮次、跨会话、甚至跨天跨周的信息保留下来在需要的时候重新喂回去。这个技术方向看起来不难理解真正动手做的时候细节非常多。存什么、怎么存、什么时候写、什么时候读、读多少、读完之后怎么塞进上下文不把Prompt撑爆每一条都足以让一个看起来能跑的方案在线上彻底翻车。我在实际项目中走完了一轮从零搭记忆系统的过程踩了不少坑这篇就把完整链路拆开讲清楚。文章适合谁看你正在做智能体项目需要让Agent在真实业务里连续工作不再一问三不知或者你已经在用Coze、Dify这类平台编程式地搭过Agent但想知道底层记忆到底怎么设计——这篇内容应该对你有实际帮助。2. 记忆分三层工作记忆、情景记忆、语义记忆先别急着选数据库。在设计存储方案之前必须把记忆这个概念拆开因为它不是单一的东西。我参考认知科学里对记忆的分类方法把智能体的记忆分成三个层面每一层的读写策略和存储介质完全不同。2.1 工作记忆每次决策的临时工作台工作记忆对应模型在单轮调用时的上下文窗口也就是当前Prompt里的全部内容。它天然由模型架构决定不需要我们额外存储但它的容量限制直接影响记忆系统的设计目标——Prompt塞得太满模型注意力会被稀释回答质量明显下降。我在项目中实测过一个规律上下文超过窗口长度的40%之后模型在长文本里找重点的能力开始变弱超过70%时回答质量会显著下滑甚至出现答非所问。这说明工作记忆不仅是容量问题还是利用率问题。记忆系统的核心任务之一就是把最相关的信息塞进这个有限空间而不是把所有历史都堆进去。2.2 情景记忆发生过的具体事实情景记忆是大多数人理解的记忆某个时间点发生过什么事。用户上次修改了哪个参数、订单状态是什么、之前商量过什么结论。这类记忆的特点是具体、有时间戳、一次性使用频率高。比如一个客服智能体用户上次反馈发票抬头填错了这次再来问售后进度如果智能体完全不知道这回事用户就必须重新解释一遍。情景记忆要解决的问题就是把这类上次的上下文捞回来。2.3 语义记忆提炼后的长期知识语义记忆是情景记忆经过沉淀之后抽象出来的用户画像、业务规则、偏好习惯。它不记录具体事件而是记录这个用户习惯怎么做事。比如用户偏好简洁回复这位客户的所有订单都走加急审批用户对价格敏感报价前需要先确认预算。我的设计原则是情景记忆直接原样存语义记忆必须经过一层LLM提炼再存。直接拿原始对话记录当长期记忆用效果很差因为噪音太多、信息密度太低召回时还会把不相关的东西捞出来。要抽象成主语的长期特征才值得进长期存储。2.4 三层之间的流转关系这三层不是割裂的。工作记忆是每一轮动态生成的它从情景记忆里召回相关内容又会在对话结束后产生新的情景记忆写入存储语义记忆则是情景记忆经过定期加工后的产物反过来又影响下一轮召回时的工作记忆构建。我画了一张流转图给自己看这里用文字描述对话产生事件 → 事件持久化为情景记忆 → 定期对情景记忆做LLM提炼 → 生成/更新语义记忆 → 新对话开始时从情景和语义两层分别召回 → 组装进工作记忆Prompt → 模型决策 → 产生新事件。这个循环一旦转起来智能体才真正算有记忆。3. 存储层的选型JSON文件、SQLite还是专业向量库很多人第一步就卡在存储选型上。我见过不少方案直接在项目里用Python字典加JSON序列化当记忆存储做Demo没问题一旦数据量上来或者要按语义检索立刻抓瞎。我自己也走过这条路可以给你讲讲各个方案的真实边界。3.1 第一版方案JSON文件方案以及它的致命伤我第一个版本确实图省事所有记忆对象序列化到一个memories.json里追加写入、启动时加载进内存。跑了两周业务对话之后问题来了文件扩展到几十兆每次全量加载要几百毫秒追加写入频繁之后碎片化严重最麻烦的是语义检索完全没法做——我只能用关键词匹配用户说那个改过的价格和存储里的把报价调整为12000根本对不上。结论是JSON文件只适合做本地调试和保存配置不适合当记忆系统的主存储。别在这上面浪费感情。3.2 SQLite做结构化记忆的优势后来我把有明确结构的数据迁到了SQLite。用户ID、事件类型、创建时间、关键参数这些字段用关系型存储非常顺手。SQLite单文件、零运维并发读写对单机项目完全够用还支持SQL查询按时间范围、按用户ID过滤极其方便。我那个客服智能体的订单状态记录全部走的SQLite查询某用户最近5条交互记录就是一条SQL的事稳、快、不出错。3.3 向量库负责语义检索两者是互补关系光有SQLite还不够因为语义召回是记忆系统的核心能力。用户说的上次那个报错和你存储里写的validator raised exception at line 42在字面上没有重合但语义上是同一件事。这就需要向量化——把文本映射成高维向量检索时用余弦相似度或内积找相近的向量。向量库选型上我对比过几款主流方案。开源的Qdrant、Milvus都很成熟但如果你只是单机项目、数据量在百万条以下完全没必要上重型分布式的Milvus。Qdrant单机模式就够用甚至更轻量的话可以用sqlite-vec直接把向量存在SQLite里减少一套中间件维护负担。我最后采用的方案是SQLite存结构化字段 Qdrant存向量和原文。两条存储以memory_id为关联键结构化查询走SQL语义召回走向量检索各干各擅长的事。3.4 存储字段的完整设计这是我最终使用的存储schema你可以直接抄。字段类型说明memory_idTEXT/UUID记忆唯一标识user_idTEXT归属用户隔离不同用户的记忆空间session_idTEXT来源会话用于追溯memory_typeTEXT情景记忆 / 语义记忆contentTEXT记忆内容原文或LLM提炼后的文本summaryTEXT用于向量化的浓缩文本embeddingVECTORcontent/summary的向量表示importance_scoreFLOAT重要性评分用于淘汰决策access_countINTEGER被召回次数用于热度和衰减计算created_atTIMESTAMP创建时间last_accessed_atTIMESTAMP最近访问时间metadataJSON扩展字段存业务相关信息这里有个关键点summary字段。直接给整段原文做Embedding向量维度大、噪音多、检索精度差。我实践下来的做法是每次写入前先让LLM把原文压缩成一句100字以内的核心摘要用摘要做Embedding存原文做展示。召回时先用向量命中摘要再取出完整原文喂给模型。这个双字段设计让召回的精确度明显提升因为摘要里的信息密度比原文高很多。4. 写入路径不解决记什么存什么都是白搭存储选好了下一个问题更棘手什么值得记什么时候记如果每轮对话的每一句话都存记忆库会迅速熵增——全是流水账召回的噪音能把系统拖垮。我给写入路径设了三道闸门。4.1 触发条件什么事件需要进记忆我实际用下来以下三类事件是必须写入的优先级从高到低用户明确指令类用户说记住我下次要改这个方案以后都用简体中文回复这个客户的账期是45天。这类显式记忆要求是最高优先级必须立即写入并且importance_score拉高。事实变更类系统操作改变了某个实体状态比如订单状态从待付款改成已付款、任务执行结果成功/失败、API调用后返回的关键业务数据。这类是运行时状态写入后用于后续步骤的数据衔接。可提炼的模式类用户连续三次在收到长回复后都说太长了系统应该能提炼出该用户偏好简短回复。这类不是一条条存而是定期由离线任务从对话历史里批量提炼。记不住规则的话写代码时给自己立个规矩凡是下轮决策需要知道而当前Prompt里没有的信息就必须落库。这条比任何技巧都管用。4.2 写入链路的具体实现写入路径的完整流程分五步解析当前对话上下文判断是否有触发条件命中的信息构造MemoryEntry对象填充用户ID、类型、原文、时间戳调用LLM生成100字以内的核心摘要对摘要调用Embedding模型生成向量分别写入SQLite和Qdrant并带上重要性评分。另外真实业务里会有大量低频但重要的信息比如用户偶尔提到的一个偏好当时没让记住但对话结束后才知道它重要。所以写入要分实时触发和离线批量提炼两条线。实时线处理显式指令和状态变更离线线每天晚上跑任务对当天对话做聚类提炼抽取出有长期价值的语义记忆。两条线缺一不可只靠实时线长期记忆永远长不出来。4.3 防重复与防冲突踩过一个实际的坑同一个信息可能被反复写入因为多轮对话里用户会重复说记住啊。我后来在写入前加了个查重步骤——取当前写入内容的Embedding在已有记忆的向量库里做一次相似度检索如果相似度超过0.92说明是重复内容转为更新已有记录的last_accessed_at和access_count而不是新增一条。这样记忆库的膨胀速度下降非常明显。还有个更深的冲突问题用户先说了以后都用邮件汇报本周又说最近用企业微信就行邮件太慢了。新信息与旧记忆矛盾时不能并存否则召回的模型会看到两条互相打架的记忆决策变得随机。我的做法是写入新记忆时做一次语义冲突检测如果发现高相似度但结论相反的记忆旧的那条打上superseded_by标记逻辑删除而不是物理删除方便追溯历史。5. 读取路径从记忆库到Prompt的组装过程写入做对了读取决定效果。记忆系统最怕的不是没存东西而是存了一堆东西却取不出来或者取出来一堆没用的占满上下文。我花了大量时间打磨的是读取链路的三步。5.1 召回向量检索加结构化过滤读取的第一步是精准备选。以当前对话的最后几轮内容作为查询Query做两类检索语义召回把Query向量化在Qdrant里按余弦相似度取top_k。项目里的经验值是把top_k设置在5到8之间数量太少容易漏掉关键记忆太多则噪音上升。结构化过滤如果是客服类场景先WHERE user_id ? AND last_accessed_at ?把候选集缩小再排序。这个过滤能极大提升召回的准确率因为很多业务记忆是强用户相关的跨用户召回没意义。两类结果合并时我给语义召回更高的权重因为它的泛化能力强结构化过滤的价值是缩小范围防止向量检索在数据量大时打偏。5.2 重排与剪枝向量相似不代表真实用召回之后不能直接用。向量相似度高的记忆在业务上可能完全没用。比如用户问上次开会提到的新品定价策略语义召回可能把新品发布会邀请函模板也捞出来字面相关但实际无用。所以召回之后必须过一层重排。我在项目里用LLM做重排把召回的候选记忆列表和当前问题一起发给模型让它挑选对当前回答最有帮助的2到4条记忆并说明理由。这一步虽然增加了一次LLM调用但效果立竿见影——喂给主模型的记忆从宽泛相关变成精准相关回答质量显著提升。重排过后还有一层剪枝按重要性评分、时效性、访问频次再做总分计算。我用的一个简化打分公式是score 0.4 × relevance 0.3 × importance_score 0.2 × recency 0.1 × access_frequencyrelevance是重排模型给的语义相关度recency根据last_accessed_at做指数衰减越久没用到权重越低。最后按总分取前N条进PromptN一般控制在3到5条以内每条约100到200字这样对上下文窗口的占用是可控的。5.3 压缩与格式化让模型一眼看懂这是记忆选好的记忆不能直接拼进Prompt得格式化。我的做法是给记忆加一个统一的前缀标记标注记忆的来源时间和类型形如[记忆-2025-06-12 用户偏好] 用户偏好简短回复控制在200字以内。 [记忆-2025-06-11 事实记录] 订单ORD-20250611-003状态已变更为已付款。加上来源时间和类型标签能让模型在阅读时自动区分对话上下文和历史记忆减少混淆。这个细节我在A/B对比中发现加了格式化标签之后模型对记忆的引用准确率高了十几个百分点。6. 遗忘、冲突与隐私边界记忆系统的另一半存储和检索做好之后真正让系统可信的反而是那些删除和克制的逻辑。很多人做记忆系统只做加法从来不做减法结果系统跑到一定阶段就崩了。遗忘不是缺陷它是记忆系统里必不可少的设计维度。6.1 遗忘机制重要性评分加时间衰减记忆库无限增长会带来两个问题召回变慢、噪音变大。我在系统里实现了一套三级遗忘机制主动遗忘importance_score低比如小于0.3且超过90天未被访问的记忆标记为可删除。被动遗忘access_count长期为0、且存放超过180天的记录直接进入冷存储不参与常规召回。冲突淘汰同一主题产生新记忆且确认替代旧记忆时旧记忆逻辑删除。这套机制跑下来我的记忆库规模稳定在大约三个月活跃数据的水平不会再无限膨胀。6.2 冲突处理的兜底策略第4节提过写入时做冲突检测但实际中冲突不可能完全被算法预判。有些冲突需要到读取时才能发现——比如模型在重排阶段看到两条记忆指向相反的结论这时不能把两条都塞给主模型我的兜底策略是以最近一条为准并把那条旧记忆标记为待复核状态。另外LLM在判断新旧记忆冲突时不一定可靠因为它只会根据文本判断看起来矛盾无法判断业务上是否真的互斥。所以我设了一个白名单机制某些核心业务字段订单状态、审批结果的更新直接走结构化字段覆盖不依赖LLM的语义判断保证关键数据不会因语义误判而出现双版本。6.3 数据合规记忆系统的合规红线这块容易被忽略但我必须提醒记忆系统存的是用户的个人数据和交互历史这涉及到隐私合规和数据最小化原则。设计上至少要满足三点支持按用户维度一键导出和删除全部记忆敏感字段如手机号、身份证不进入向量库只存结构化且做脱敏记忆数据有独立的生命周期策略超期自动清除不默认永久保留。我在项目里的做法是记忆数据跟业务数据分库存储运维上单独备份所有对记忆的删除操作记录审计日志。别觉得这是小题大做一旦系统上了真实用户这些都是合法合规的基本要求提前设计好总比上了线再被要求整改省事太多。7. 一次完整会话中的记忆运转演示理论讲了不少我们用一条真实风格的对话串起来看记忆系统在端到端流程里是怎么配合的。场景是一个项目管理智能体用户每天都会跟它同步项目进展。7.1 第一轮写入情景记忆用户说本周开发进度延迟了原因是第三方支付接口联调阻塞预计下周才能恢复。这一轮解析出的关键信息是支付接口联调阻塞、进度延迟、预计恢复时间。系统实时写入一条情景记忆内容压缩为2025-06-16项目进度支付联调阻塞导致延期预计下周恢复同时把项目的project_status字段更新为延期。此时语义记忆还没有变化需要等离线提炼。7.2 次日语义召回生效用户第二天问今天的风险项汇总发我看看。系统检索时Query向量化后命中了昨天那条情景记忆同时命中更早的一条语义记忆该用户每日需要风险汇总日报偏好条目式输出。两条记忆重排后都进入Prompt。模型根据记忆得知延期背景输出日报时主动带上支付阻塞的风险说明并按条目式排版。这就是记忆带来的体验差异——模型不再是只回答当前风险项为空而是知道真正的风险是什么、在哪一天出现的。7.3 一周后语义记忆更新离线任务在一周后重新梳理这些对话发现支付接口联调不再是阻塞项项目已经恢复于是把旧的延期语义记忆下调重要性写入一条新的语义记忆项目已恢复正常状态支付接口联调完成后未再出现阻塞。这一周里系统跟用户的每次交互都因为记忆的存在而更连续。7.4 从演示到生产必须想清楚的三件事这套流程在小流量下演示完全没问题真上生产之前还有三件事要额外处理。第一是并发写入一致性。多轮对话可能同时触发多条写入SQLite写入要开事务向量库写入要确认幂等否则偶发重复或半截数据会污染记忆库。第二是Embedding模型的稳定性。换Embedding模型会导致新旧向量不在同一个语义空间检索结果错乱。要么上线前确定模型就不动要么提供全量重向量化任务。第三是可观测性。我会为每条进入Prompt的记忆打印来源ID和召回分数出现归因问题时能回查是哪条记忆影响模型回答。没有这个记忆系统就是个黑盒出了问题只能干瞪眼。8. 落地路线从最简单的单文件到可扩展架构分三步走如果你是从零开始别一上来就搭全套SQLite加Qdrant加离线任务那样太容易淹没在细节里。我建议分三步每一步都能跑通闭环再逐步替换组件。8.1 第一步工程化落地的最小闭环用JSON文件做存储但这版直接用原文存不做摘要不做向量化。检索用关键词匹配。这个版本只需要一个MemoryStore类几十行代码跑通对话循环。它的价值在于帮你验证记忆 注入这个闭环是否真的能让体验变好观察哪些环节最需要记忆参与。8.2 第二步SQLite加向量检索把存储从JSON文件迁到SQLite加Embedding字段用hnsw索引或sqlite-vec做语义检索。这个阶段引入摘要压缩并实现基本信息过滤按用户ID过滤。到这里所有核心机制其实都已经落地了系统已经能在业务对话中表现出明显的记忆感。8.3 第三步离线提炼与遗忘机制加上离线批量任务对会话数据做聚类和提炼生成长期语义记忆。同时实现遗忘和淘汰机制把数据生命周期管起来。这一步做完记忆系统就具备长期稳定运行的能力了。三步时间分配我的经验是第一步一两周第二步两三周第三步一个月左右慢慢打磨。不要着急直接上最重的方案记忆系统的难点在于链路协同而不是单个组件有多复杂。至于更偏重的Memory Server、MemGPT这类脑洞更大的思路把记忆当成额外工具调用、让模型自己决定怎么操作记忆我在项目里短暂尝试过效果确实惊艳但当前成本还是偏高更适合远程状态依赖非常重的实验性项目。对于大多数业务型智能体一套落地的SQLite 向量列 LLM提炼方案已经足够了。记忆系统这个东西真正拉高体验下限的永远是基础的读写链路是否扎实而不是招法有没有多花哨。做记忆系统做到最后我的体会是这一节8.2的意义不只是给智能体加一个缓存而是让Agent从一个每次都是第一次见面的问答机器变成一个能积累、能成长、能基于历史做判断的工作伙伴。这些细节踩过一遍之后再看任何号称有记忆能力的产品你会清楚地知道它底层到底是真的会记还是只是把上一轮对话原封不动塞回去而已。