
先说个真实场景。最近在迭代一个个人助理型Agent用户问得最多的一句是“你怎么又忘了”上一轮刚说过喜欢简洁的回复风格、常用的技术栈是Python下一次对话又得从头介绍一遍。这个问题折腾了我一周最后发现不是提示词写得不够好而是整个Agent的记忆系统压根没有设计过。这是“走近AI Agent”系列的第三篇。前两篇我分别拆过Agent的基础架构和工具调用机制这一篇专门聊一个在大模型时代被反复提起、却又很难做扎实的能力——让Agent记住你。我把它定义为Agent能够跨会话保存、检索、更新用户相关的信息并在恰当的时机用这些信息影响自己的行为。这不是往上下文里塞历史记录那么简单而是一套从存储、写入、召回到遗忘的完整机制。如果你正准备做个人助理、客服、编程助手这类产品或者正在准备AI Agent相关的面试这篇应该能帮你把记忆这项能力拆明白。我会从记忆分类讲起然后落到技术选型和LangGraph MCP的落地实现上最后分享我踩过的几个坑。1. 为什么Agent必须拥有记忆1.1 没有记忆的Agent等于一个“金鱼大脑”一个没有记忆系统的Agent本质上是一个高配版聊天机器人。用户第一次提问它回答得不错第二次带着新的上下文来它就像一个完全陌生的客服什么都得从头问起。这在前几轮对话里可能还能忍可一旦Agent开始承接真实任务比如订票、写周报、整理资料这种“金鱼大脑”就会变成灾难。举个例子我做一个客服Agent的时候用户第一轮说“我是会员订单号后四位是7231”第二轮问“我的退款到哪一步了”。如果Agent不记得会员身份和订单号就得让用户再报一遍。一次两次还行高频场景下用户就会明显不耐烦然后流失。这其实反映了一个核心问题大模型本身是无状态的。每次调用API模型都不记得前面发生过什么。所谓“记忆”完全靠我们这些做工程的人在外部系统里补。1.2 记忆缺失背后的真问题很多人一听到“让Agent记住你”第一反应就是“把历史消息都塞进上下文不就行了”。这确实是最简单的方案但它只能算缓存不叫记忆。原因很简单第一上下文窗口是有限的塞满了旧消息真正有用的空间就被挤占了第二历史消息里的信息密度极低对话越久噪声越大模型反而更容易被带偏第三这种方式没法跨会话生效用户换一天再来一切照旧。我自己一开始也是这么干的后来踩了上下文爆掉的坑才明白记忆系统的本质是“信息管理”。它要解决的是在海量交互数据里把最重要的信息以合适的形态放在最合适的位置在合适的时间取出来。这比单纯堆上下文复杂得多但也正是Agent能不能长期服务同一个用户、累积个性化价值的关键。1.3 谁最需要“让Agent记住你”不是所有Agent都需要复杂的记忆系统但以下几类场景几乎是刚需。首先是个人助理Agent。用户的使用习惯、日程偏好、常用的外卖口味、写作风格都是需要跨会话保存的。其次是客服Agent。用户的会员等级、历史订单、投诉记录、沟通偏好直接影响服务质量。第三是编程助手Agent。一个真正好用的编程助手应该记得项目的技术栈、代码风格、常用依赖库甚至记得开发者上次改到一半的需求。第四是教育或陪伴类Agent。这类产品天然要求长期关系记忆是它们的情感纽带。判断标准也很简单如果你的Agent和同一个用户会发生多次交互且两次交互之间存在可复用的信息那就需要记忆系统。2. 先把记忆分个类再动手写代码2.1 认知科学里借来的四类记忆模型我在设计记忆系统之前先看了一圈业界方案发现很多人把“记忆”当成一个黑盒什么都往里塞最后召回质量一塌糊涂。后来我借用了认知科学里对记忆的分类方式把Agent的记忆分成四类。第一类是短期工作记忆也就是当前会话里的上下文。它存在Agent的运行状态里对话结束就消失。第二类是情景记忆记录用户在过去某个时间做了什么事比如“上周五用户询问了去杭州的航班”。第三类是语义记忆是用户相对稳定的偏好和事实比如“用户习惯使用Python喜欢简洁回复”。第四类是程序记忆是Agent自身累积的技能和工作流比如“遇到报销问题时先检查发票再走审批节点”。这四类记忆的性质完全不同。短期记忆是动态的、易失的情景记忆是事件导向的语义记忆是概括的、长期稳定的程序记忆则是Agent可复用的行为模式。把它们混在一起存储和检索都会很难设计。2.2 四类记忆分别该放哪短期记忆不用额外设计直接利用LangGraph这类编排框架的State就可以。真正需要建设的是后面三类。情景记忆适合用向量数据库存。因为它是“事件”检索时更多依赖语义相似度比如用户问“我之前问过的杭州航班”这跟“上周五用户询问了去杭州的航班”在语义上是相近的向量召回效果很好。语义记忆则适合用结构化存储比如PostgreSQL或者Redis里的JSON字段。用户的偏好本质上是一组键值对比如“preferred_language: python”用结构化方式存更新也方便。程序记忆比较复杂通常体现在Agent流程定义、工具配置里属于工程资产不太需要运行时自动更新我在项目里一般用配置文件管理。这里有一个很关键的心得不要把所有记忆都丢进向量库。结构化事实用结构化存储比向量检索更可靠、更可控。比如用户的名字、会员等级、常用收货地址用向量库存反而容易出现召回不准的问题。2.3 一个容易踩坑的分类误区很多教程会把记忆简单分成“短期记忆”和“长期记忆”但落地的时候你会发现这个分类太粗了。长期记忆里既有“用户喜欢喝美式咖啡”这种语义记忆也有“上周二用户在咖啡店抱怨过价格”这种情景记忆。检索逻辑完全不同一个更适合按字段查一个更适合按语义查。所以我在团队里经常强调记忆系统的设计第一步不是选数据库而是先把“要记住什么”想清楚。你可以先列一张表把Agent在实际交互中可能遇到的、需要跨会话保留的信息全部列出来标上类型、更新频率、优先级、是否敏感然后再决定每类信息用什么存储、怎么写入、怎么召回。这个动作看起来简单但能帮你省掉后面大量返工。3. 技术选型记忆系统不只是向量数据库3.1 向量数据库到底怎么选在确定了记忆类型后向量数据库是情景记忆的核心载体。这里有几种常见选择。ChromaDB非常轻量适合快速原型和个人项目我最早就是用ChromaDB把整套链路跑通的。FAISS则更偏底层它其实是个向量索引库不是完整的数据库需要自己管理持久化。pgvector的优势是如果团队已经在用PostgreSQL不需要额外引入基础设施而且能跟业务数据放在一起做联合查询。Milvus更适合数据量大、并发高的场景但也更重部署运维成本不低。Qdrant是另一个很成熟的向量数据库性能好但同样是独立服务。我个人的选型建议是小于50万条向量的场景ChromaDB或pgvector足够超过这个量级再上Milvus或Qdrant。不要一开始就给一个个人项目配一套分布式集群你的Agent还没强大到需要那个规模。3.2 Embedding模型和距离度量向量数据库只是容器真正决定检索质量的是Embedding模型。OpenAI的text-embedding-3-small质量稳定缺点是数据要发给外部API隐私敏感场景要慎重。本地方案里BGE-M3是我目前用下来多语言表现不错的模型对中英文混合内容友好。如果数据是某个垂直领域比如医疗、法律强烈建议用领域语料微调一个Embedding模型效果差距会非常明显。索引类型和距离度量也需要留意。大多数情况下余弦距离就够用也就是看两个向量的方向是否一致。如果是归一化后的向量内积和余弦是等价的。维度方面常见的是384、768、1536。维度越高信息容量越大但计算和存储成本也越高。小项目用384或768完全够了。我这里有一个经验一定要把原始文本和向量一起存。后期调试检索结果时直接看原始文本比看一堆数字直观太多排查问题的效率至少翻倍。3.3 编排层为什么选LangGraph记忆系统不是独立存在的它必须嵌在Agent的推理流程里。我目前的主力编排框架是LangGraph原因有几条。第一它用图结构编排Agent节点记忆的“写入”和“召回”可以被设计成显式节点整个流程清晰可控。第二LangGraph的State机制天然支持跨节点的状态传递短期工作记忆可以直接挂在State上。第三它提供了Checkpointer能力可以持久化每一轮图的状态相当于顺带把会话状态也管理起来了。当然如果用LangChain的Agent executor也能做但那个模式更适合简单工具调用复杂的记忆读写、条件分支、异步写入都会显得别扭。LangGraph虽然上手比LangChain略陡但一旦你开始做生产级Agent它的控制力会帮你省很多事。3.4 MCP协议把记忆做成标准工具最近MCPModel Context Protocol热度很高很多AI Agent开发都开始围绕MCP做工具层。在我的记忆系统里MCP的作用是把“记忆读写”封装成标准工具让Agent可以通过工具调用去查询或写入记忆而不是在系统提示词里写一堆“请回忆以下内容”的规则。比如我在本地跑一个MCP Server暴露remember和recall两个工具。Agent在对话中判断需要记录重要信息时会主动调用remember在回答用户问题前会判断是否需要调用recall来检索相关记忆。这样做的好处是记忆能力的边界很清晰Agent知道自己在“调用外部工具获取用户信息”而不是把记忆藏在某段隐藏上下文里调试起来也直观很多。4. 落地实操给Agent装上记忆系统4.1 整体架构写、收、忘记忆系统的完整链路我拆成三个环节写入、召回、遗忘。写入的核心问题是“记住什么”不能每句话都存必须有选择地抽取重要信息。召回的核心问题是“什么时候取什么”不是每轮对话都要把用户全部历史翻出来而是根据当前query精准取用。遗忘的核心问题是“什么时候该丢”包括上下文摘要压缩、过期记忆清理、低价值记忆降权。三者缺一不可只做写入和召回的系统就像一个只进不出的仓库时间久了必然堆积大量垃圾信息。我在项目里用LangGraph搭建这套架构时定义了一个AgentState里面包含user_id、messages、memory_hits、user_profile等字段。每个节点都从State里读取数据处理后写回新的字段。这个State就是短期工作记忆的载体而长期记忆散落在向量库和结构化存储里。4.2 记忆写入流程写入流程我通常放在一轮完整对话结束后触发让一个专门的“记忆提炼节点”去异步处理。这个节点会把当前轮次的所有消息发给大模型让模型抽取三样东西用户画像更新、值得记录的事件、需要遗忘或修正的旧记忆。import json from typing import TypedDict from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI class AgentState(TypedDict): user_id: str messages: list memory_hits: list user_profile: dict pending_memory_updates: list llm ChatOpenAI(modelgpt-4o-mini, temperature0) def extract_memory_updates(state: AgentState) - AgentState: conversation state[messages] prompt f 你是记忆提炼模块。请阅读以下对话抽取需要长期保存的记忆。 输出JSON格式 {{ profile_updates: [{{key: 偏好的字段名, value: 值}}], events: [{{summary: 事件摘要, time: 事件时间, importance: 0到1}}], stale_memory_keys: [需要删除的旧偏好键] }} 对话内容{conversation} result llm.invoke(prompt) parsed parse_json(result.content) return {pending_memory_updates: parsed}这个节点返回的pending_memory_updates会交给一个MemoryStore服务分别写入结构化存储和向量库。profile_updates走KV更新events向量化之后写入ChromaDB。这里有个细节写入前要对profile_updates做字段名归一化。不同的表达方式可能映射到同一个偏好比如“喜欢简洁回复”和“回复风格偏好简洁”如果不做归一化用户画像里会出现大量冗余字段。4.3 记忆召回流程召回环节比写入更考验设计。我在每个用户对话开始前只会加载一次用户画像把它拼进系统提示词里。然后在每一轮用户提问之后Agent会判断是否需要语义召回如果用户问的问题跟历史相关就调用recall工具把当前query拿去向量库搜索取top-k条记忆经过重排后作为附加上下文交给模型。def recall_node(state: AgentState) - AgentState: last_user_msg state[messages][-1] query_text last_user_msg[content] candidates vector_store.search(query_text, top_k20, user_idstate[user_id]) # 重排按时间衰减 与query的相似度 事前重要性 reranked rerank(candidates, query_text) state[memory_hits] reranked[:5] return state召回结果不是越多越好。我后来实测发现top-5到top-8是最合适的一个区间。少了信息不够多了模型容易分心还会挤占上下文配额。另外一定要按user_id过滤否则一旦Agent服务的用户多了很容易把别人的记忆拉进来这个错误在开发环境不明显上了生产就是事故。4.4 记忆压缩与遗忘策略记忆系统里最容易被人忽略的是遗忘。大模型的上下文窗口再大也装不下无限增长的历史。我用的方案是三层。第一层对当前会话做滚动摘要超过20轮的对话会压缩成摘要保留关键结论和待办。第二层对长期记忆做重要性评分重要性低于阈值、且长时间未被命中的记忆会被降权或删除。第三层对临时性事件设置TTL过期时间比如“用户这周在看的房子”这类信息两周后就不再参与召回。重要性评分公式我用得非常简单但实测有效score 0.5 * similarity_to_query 0.3 * pre_assigned_importance 0.2 * recency排名时把相似度、人工或模型标注的重要性、时间衰减综合起来。这个公式可以根据业务调整权重但核心思想是不能被相似度一个指标绑架不然召回结果会偏向“形似但无价值”的旧消息而忽略真正重要的事实。4.5 最小可运行链路把这几个节点串起来一个最简的LangGraph图大致长这样from langgraph.graph import StateGraph graph StateGraph(AgentState) graph.add_node(load_profile, load_profile_node) graph.add_node(recall, recall_node) graph.add_node(agent, agent_node) graph.add_node(extract_memory, extract_memory_updates) graph.add_edge(load_profile, recall) graph.add_edge(recall, agent) graph.add_edge(agent, extract_memory) graph.add_edge(extract_memory, END) graph.set_entry_point(load_profile) app graph.compile()这个图看着简单但已经把记忆系统的核心闭环跑通了先加载用户画像再根据当前问题召回历史记忆然后把画像和召回结果一起交给Agent主节点生成回答最后在回答结束后异步提炼新记忆。你完全可以把这里面的底层存储换成你自己的实现比如用SQLite加JSON先跑通再平滑替换到专业向量库。5. 踩坑实录那些文档里不会写的问题5.1 上下文被记忆撑爆了我第一次给Agent加记忆时贪多求全每轮都把用户画像、最近20轮对话、top-20条记忆全塞进prompt。结果模型回复质量不升反降还经常出现“忘记”更早前指令的情况。排查下来发现上下文里塞了太多低价值记忆真正关键的指令反而被稀释了。解决办法是给记忆预算设硬上限。我在系统提示词里预留“记忆摘要区”只允许填充300个token以内的用户画像摘要语义召回结果限制在1000个token以内历史对话全文不进主上下文只进摘要。超出预算的内容宁可不要也不能硬塞。这个改动让回复质量明显回升。5.2 向量召回“相关幻觉”拉回来一堆不相关记忆向量检索的相似度并不等于业务相关性。用户问“杭州天气怎么样”向量库可能召回一个月前“杭州旅游攻略”的长篇大论相似度还很高但对当前问题一点用没有。这是所有记忆系统都会遇到的问题绕不开。我的排查思路分几步。第一步把召回的原始文本打印出来逐条看确认是Embedding模型的问题还是存储数据的问题。第二步在写入时给每条记忆打上type标签召回后用metadata过滤比如天气类问题只召回非闲聊类记忆。第三步引入重排模型用LLM对召回结果做一个相关性打分。这三步下来召回质量能提升不少。5.3 Embedding模型升级后旧向量全部失效这个坑特别隐蔽。我有一版用OpenAI的text-embedding-3-small做了几千条记忆向量后来为了本地化换成了BGE-M3检索效果瞬间崩盘。原因是不同模型的向量空间不一致同一句话在两个模型里的向量方向完全不同拿新模型的query去跟旧模型的向量比相似度结果基本是随机的。解决办法也很简单但要做在前面。一是给向量记录加一个embedding_model字段召回时过滤相同模型版本的数据。二是重大模型升级时直接做一次全量向量重建。三是无论什么时候都要保留原始文本没有原始文本向量一毁记忆就全毁了。5.4 并发写入导致用户画像被覆盖我的Agent支持多轮异步对话结果两个节点同时更新同一个用户的画像字段后写的一方把先写的一方覆盖了。具体现象是用户的“常用语言Java”和“常用语言Python”在同一次对话里交替出现最后存下来的是运气好排在后面的那条。后来我改成字段级合并更新而不是整条覆盖。update操作基于JSON Path定位到具体字段相同字段再做优先级或者时间戳判断。同时给每个用户画像加了一个version字段乐观锁防止并发写丢失更新。确认过几轮并发测试后这个问题就很少再出现了。5.5 用户跟你说“忘了我吧”做记忆系统绕不开隐私问题。用户有权要求清除自己的数据这个能力我叫它“被遗忘权”。我在设计里预留了一个delete_user_memory接口可以按user_id一键删除该用户的画像、事件记忆、历史向量。测试的时候一定要注意不只是删向量库结构化存储、日志、备份里的数据也要同步处理否则删了等于没删。这一点在面试中也经常被问到。面试官问“你怎么设计Agent的记忆系统”时如果你能主动提到写入、召回、遗忘三环节再带上隐私删除和多租户隔离的考虑通常能让对方觉得你有生产意识而不是只会贴示例代码。6. 最后分享一点我的体会从零开始给Agent做记忆系统断断续续改了一个多月最大的感受是少即是多。不是因为存储不够而是因为召回噪声和上下文污染会侵蚀模型能力。你现在看到的所有炫酷记忆方案本质上都是在跟“信息过载”做对抗。如果你准备动手做我的建议是不要一上来就铺全套基础设施。先用SQLite存用户画像用ChromaDB跑通向量检索用LangGraph把闭环搭起来跑几天真实对话把所有检索结果一条条人工检查一遍。等真正摸清了哪些记忆有用、哪些是噪声再考虑要不要上Milvus要不要引入重排模型要不要做分布式架构。另外记忆的格式一定要设计成可观测的。我踩过的所有坑最后几乎都是靠“把记忆内容打印出来人工看”才定位到问题。一个看不见内部状态的记忆系统是没法在生产环境里长期维护的。以上就是这期的全部内容。如果你也在做AI Agent的记忆相关功能欢迎交流你在召回和存储上踩过的坑。下一篇我准备写多Agent协作时的记忆同步问题那个比单Agent记忆更有意思。