资讯动态

AI Agent记忆系统实战:从无状态到跨会话智能的完整指南

发布时间:2026/9/11 2:36:28 来源:尧图企业网站定制
先问你一个问题你上一次跟某个 AI 聊完天后下次再打开它是不是还得从头自我介绍一遍“我是谁、我在做什么项目、我上次要的是什么”如果你点头了那这篇就是为你写的。这是《走进 AI Agent》系列的第三篇。前两篇我们聊了 Agent 的基本结构、工具调用和任务拆解但一直有个绕不开的硬伤——Agent 没有记忆。没有记忆的 Agent 就像一个每次见面都把你当陌生人的店员你说“还是老样子”他只会一脸茫然。这篇我想把“让 Agent 记住你”这件事彻底讲透从记忆的分类、架构设计、代码实现到真实项目里踩过的坑一次性给全。1. 没有记忆的 Agent本质上只是个“高级玩具”1.1 从一次让人崩溃的对话说起我去年做一个客服类的 Agent 原型时遇到一个特别典型的场景。用户第一轮说“我的订单号是 20241015A我要查物流。”Agent 正常回答。可到了第三轮用户问“如果今天到不了我申请退款流程怎么走”Agent 居然反过来问“请问您的订单号是多少”这不是模型能力不行而是Agent 根本没有跨轮次的状态保持能力。每次大模型调用都是无状态的它处理完一个请求就把上下文忘了。你以为你在跟一个连续对话的“人”交流实际上你是在跟一个每一次都失忆的机器交流。没有记忆的 Agent 会导致几个很现实的问题重复提问用户要一遍遍提供背景信息对话效率极低。无法积累偏好Agent 记不住“这位用户喜欢简洁回复”“这位用户是后端工程师聊技术别用太多比喻”。任务断裂多步骤任务做到一半只要上下文窗口溢出或被截断前面的工作全白费。信任感崩塌用户一旦发现 AI 总是“翻脸不认人”就不太可能把复杂任务交给它。我在网上看到很多人在热搜词里搜“Agent 记忆”“AI Agent 运行逻辑”其实大家潜意识里已经感觉到了记忆才是 Agent 从“玩具”走向“工具”的分水岭。1.2 记忆不是缓存而是“用户模型的持续构建”很多人以为给 Agent 加记忆就是在代码里开个数组把历史对话存进去下次拼到 Prompt 里。这是最粗浅的理解。真正的记忆系统做的是三件事记录保存与用户交互过的关键信息但“关键”这个词需要设计规则。组织把零散的信息结构化比如区分“用户个人信息”“项目背景”“当前任务上下文”。利用在合适的时机把相关记忆召回、注入到模型中让模型“想起”该想的事。我习惯把记忆比作一个有分类归档的私人助理的记事本。记事本不是把用户说过的每句话都抄下来而是只记重点这位用户的习惯、偏好、正在进行的项目、上次聊到哪里。然后在用户开口的时候助理能迅速翻到对应页面把该带的资料放到桌面上。这也是为什么我在系统里从来不用“把全部聊天记录塞进上下文”这种方案。上下文窗口再大也有上限而人类的对话信息是持续增长的。记忆系统的核心能力不是存储而是筛选和召回。1.3 为 Agent 的记忆能力分个级和很多从业者交流下来大家普遍接受的一个分级方式是这样的级别记忆能力表现L0无记忆每次对话从零开始像个失忆症患者L1会话内记忆记住当前这一轮多轮对话的内容但关掉就忘L2跨会话用户画像记住用户的基础信息、偏好例如“用户是前端工程师”L3跨任务情景记忆记住“你上周让我调研过向量数据库”能主动关联L4主动学习型记忆Agent 自己判断什么值得记自动更新记忆结构目前市面上大多数开箱即用的 Agent停留在 L0 到 L1 之间。很多接了大模型 API 的“聊天机器人”其实连 L1 都做不好因为开发者没维护消息历史列表。而L2 到 L3才是“让 Agent 记住你”的核心目标。这篇文章后面讲的内容基本围绕如何把 Agent 从 L1 推到 L3再展望 L4 的玩法。2. 给 Agent 装上记忆骨架四类记忆模型2.1 工作记忆当前任务的“桌面草稿纸”工作记忆对应的是 Agent 正在处理的任务状态。比如用户问“帮我写一封给客户的邮件语气要正式一点”那么“收件人背景”“邮件主题”“语气要求”“已经起草到第几版”这些信息都属于工作记忆。它的特点是生命周期短、更新频率高、随任务结束而清空。实现上可以非常灵活最简单的方式就是用 Python 里的一个字典配上对话消息列表working_memory { task_type: write_email, customer_name: 王总, email_topic: 项目延期说明, tone: formal, draft_version: 3, }在框架层面LangChain 里的ConversationBufferMemory、ConversationSummaryMemory本质上都是在做工作记忆的管理。前者保留全部原始对话后者用模型把历史总结成摘要各有利弊。我在实际项目里的建议是工作记忆不要只存消息原文要额外维护一份“任务状态字段”。因为消息原文是线性的但任务状态是结构化的。模型每次读取工作记忆时先看任务状态字段能快速定位当前进度而不是从头读一遍全部对话。2.2 情景记忆记住“昨天聊过的那件事”情景记忆是跨会话的。举个例子用户上周跟你说“我在做一个智能简历筛选工具”这周又来问你“帮我看看这个招聘JD怎么解读”。如果 Agent 有情景记忆它应该联想到这位用户在做简历筛选工具是不是想招人或者想了解 JD 背后的筛选逻辑情景记忆的核心价值是连续性。用户不需要每次都重新交代背景Agent 能把散落在多次对话里的信息串联成一条线索。实现情景记忆主流方案是向量数据库。把每次对话中的关键事件转化为向量存进向量库里每次新对话到来时做相似度检索找到相关度高的历史事件注入到 Prompt 中。这个我在第三章会给出完整代码。有一个容易忽略的点情景记忆的“粒度”很关键。如果把整段对话作为一个向量存进去召回时往往不够精确。更好的做法是按“事件”切分比如一次对话里用户提了三件不相干的事那就拆成三条记忆而不是一条混合记忆。这是很多人第一次做情景记忆时踩的坑。2.3 语义记忆关于你的结构化知识语义记忆是“关于世界的知识尤其关于用户的知识”。它不依赖某个具体事件而是从多次交互中提炼出的稳定结论。例如{ user_id: u_123456, name: 陈晨, role: 后端工程师, tech_stack: [Go, Python, PostgreSQL], communication_style: prefers_direct_concrete_answers, projects: { resume_filter: { status: in_progress, goal: 基于大模型做简历初筛 } } }这类记忆适合用结构化数据库存储比如 PostgreSQL 或 SQLite每条记录有明确的字段。和向量检索式的情景记忆互补语义记忆负责“静态画像”情景记忆负责“动态经历”。我在公司内部给团队定的规范是凡是能从对话中稳定提取出的、对后续交互有长期影响的属性一律进结构化记忆凡是“某天某人说了某件事”这种事件型信息一律进向量记忆。两条线并行互不干扰但可以联合召回。2.4 程序记忆Agent 的“肌肉记忆”程序记忆对应的是技能。比如 Agent 学会了“当用户问天气时调用天气 API 并加工结果再回复”这个流程一旦固化就不需要每次重新推理一遍。在 AI Agent 领域程序记忆常以Skill 或 Tool 定义的形式存在。热搜词里有“AI Agent Skill”其实指的就是这个——把 Agent 执行某些任务的策略、步骤、模板固化下来变成可复用的能力。程序记忆的技术实现有两类显式定义写死函数或 Prompt 模板比如“查天气”“发邮件”“计算器”。隐式学习从历史成功案例中总结套路比如用户多次让 Agent 按某个风格写周报Agent 学会了自动套用这个风格。目前业界用得最多的是显式定义也就是给 Agent 配置工具清单和工具描述。隐式学习还比较早期但已经有团队在探索用“记忆”动态生成 Skill。给本小节画个重点四类记忆各有各的存储介质和生命周期别混为一谈。新人最容易犯的错误是把什么都往向量库里塞结果语义记忆被事件记忆冲淡召回质量越来越差。记忆类型生命周期存储介质召回方式工作记忆单次任务内存/字典直接读取情景记忆跨会话向量数据库相似度检索语义记忆长期稳定结构化数据库精确查询程序记忆长期代码/配置技能触发3. 从零搭建一套带记忆的 Agent完整实操3.1 技术选型为什么是“向量库 Embedding 结构化库”先回答一个很多人纠结的问题为什么不能让大模型“记住”用户直接把所有历史对话拼进上下文让模型读不就行了吗理论上可以但现实不行。第一上下文窗口再大也是有限的把半年对话全部塞进去Token 成本直接爆炸。第二信息之间相互干扰模型会被无关历史带偏。第三每次把全部历史重新过一遍响应延迟会明显增加。所以正确的做法是对话历史只做短期保留关键信息抽取出来进长期记忆长期记忆分两条线一条向量库管情景事件一条结构化库管用户画像。我这套方案用到的组件按照实际项目经验是这样选的组件用途推荐选项选型理由Embedding 模型text-embedding-3-small或bge-large-zh中文效果好维度适中成本低向量数据库Chroma开发/ Qdrant 或 Milvus生产Chroma 零配置起步快Qdrant 支持过滤和持久化更好结构化数据库SQLite开发/ PostgreSQL生产成熟、可靠、团队都会Agent 编排框架自研为主参考 LangChain 的 Memory 设计框架层依赖少一点排障更容易如果你是在 Java/Spring Boot 技术栈里做 Agent这套设计同样成立Embedding 调用可以封装成 HTTP 接口向量库可以选 pgvectorPostgreSQL 插件记忆逻辑放在 Service 层即可。热搜词里有“SpringBoot AI Agent 客户端”本质上也逃不开这套存储和召回逻辑。3.2 记忆的写入哪些对话内容值得存记忆系统设计不好往往不是因为“存得少”而是因为“存得太多”。我在早期版本里试过把用户的每一句话都做 Embedding 存进向量库结果召回时全是噪音。后来我总结了一套实用规则不是所有对话都值得进入长期记忆需要经过一道筛选。筛选策略可以简单分成三层预处理过滤把问候语、寒暄、纯语气词“嗯嗯”“好的”“谢谢”直接过滤掉不进记忆。关键信息提取用大模型对当前对话做结构化总结比如提取出“用户提到正在做 XX 项目”“用户偏好 XX 风格”。显著性判断判断信息是否具备长期价值。比如“用户今天吃了午饭”就没什么长期价值“用户下月要上线一个电商项目”就值得记住。典型的一段记忆写入代码大概是这样的from openai import OpenAI import chromadb client OpenAI() chroma_client chromadb.PersistentClient(path./agent_memory) collection chroma_client.get_or_create_collection(nameepisodic_memory) def extract_memory_from_dialogue(dialogue: str) - str: 让大模型从对话里提炼值得记住的信息 prompt f 你是信息提取器。从下面的对话中提取值得长期记住的事实。 只输出要点列表不要输出无关内容。 如果是寒暄或日常无信息量内容输出 NONE。 对话 {dialogue} response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0, ) result response.choices[0].message.content.strip() return None if result NONE else result def save_memory(user_id: str, memory_text: str, metadata: dict): 将记忆向量化并存入向量数据库 embedding client.embeddings.create( modeltext-embedding-3-small, inputmemory_text ).data[0].embedding collection.add( ids[f{user_id}_{uuid.uuid4().hex}], embeddings[embedding], documents[memory_text], metadatas[{**metadata, user_id: user_id}] )注意一个点写入时要给 metadata 打好标签。例如type: user_preference、type: project、type: event方便后续召回时做过滤。你不想在用户问“我的服务器架构什么样”时召回一堆与偏好相关的记忆吧。3.3 记忆的召回相关度检索与 Prompt 注入存进去不是目的能正确召回才是。召回的基本流程是把用户当前问题做 Embedding。在向量库中做相似度搜索取出 Top-K 相关记忆。加上 metadata 过滤比如只要当前 user_id 的记忆。重排把最相关的记忆放在 Prompt 末尾模型对末尾信息注意力更强。组装成“记忆块”注入系统提示词。召回代码可以这样写def recall_memory(user_id: str, query: str, top_k5): query_embedding client.embeddings.create( modeltext-embedding-3-small, inputquery ).data[0].embedding results collection.query( query_embeddings[query_embedding], n_resultstop_k, where{user_id: user_id} ) memories [] for doc, meta in zip(results[documents][0], results[metadatas][0]): timestamp meta.get(timestamp, unknown) memories.append(f[{timestamp}] {doc}) return \n.join(memories) MEMORY_PROMPT_TEMPLATE 以下是关于用户的一些历史记忆请在做回答时参考这些信息 {memory_content} 如果历史记忆与当前问题无关请忽略它们。 当前用户问题{user_query} def build_prompt(user_query: str, user_id: str) - str: memory_content recall_memory(user_id, user_query) if memory_content: return MEMORY_PROMPT_TEMPLATE.format( memory_contentmemory_content, user_queryuser_query ) return user_query这里有一个很容易忽略的细节召回时一定要先过滤 user_id再排序不要先全局排序再过滤。向量数据库支持先过滤再检索的模式比如 Qdrant 的 pre-filter这样既快又不会出现“别人的记忆串到你这里”的尴尬。我见过有团队用全局检索再在应用层过滤数据量一上来就出各种问题。召回阶段还有一个进阶技巧多路召回。向量库召回一路结构化数据库精确查询一路比如查用户画像两边结果合并去重后再注入。这样既能召回“似曾相识”的事件又能拿到“确定无疑”的用户属性效果远好于只用一路。3.4 记忆的更新新增、覆盖与冲突消解写入和召回解决了“存”和“取”但记忆不是一成不变的会过时、会被新信息覆盖。记忆更新的三种典型操作新增对话中出现了全新的关键信息向量库和结构化库都新增条目。覆盖用户明确纠正了之前的说法。比如用户以前说自己用的 PostgreSQL今天说“我们切到 MySQL 了”旧记录应该失效。降权旧记忆不代表错误但相关度降低。比如用户三年前做电商项目现在做 AI Agent 项目旧项目信息权重应下降。覆盖在结构化库里比较好实现直接 UPDATE 字段。但在向量库里比较麻烦因为向量库里存储的是“历史快照”你无法保证新信息向量和旧信息向量距离够近。我目前落地的方案是“软失效”给每条记忆加一个status字段用户明确纠正后把旧记忆的status置为inactive召回时过滤掉。这个方法比物理删除保险方便回溯问题也让用户看到“阿 AI 确实记得我之前说过什么”的连续性。metadata { user_id: user_id, status: inactive, # active / inactive superseded_by: new_memory_id }关于冲突消解还有一条经验当记忆之间出现矛盾时以用户最近的表述为准但要保留历史痕迹。我见过最粗暴的方案是直接删旧存新结果用户来一句“不对我说的不是 MySQL是 MariaDB”旧信息已经没了Agent 彻底懵了。保留软失效能让你在模糊场景下有回旋余地。4. 记忆系统落地时最常见的四个坑4.1 向量维度不一致导致召回静默失败这是我遇到的第一个“看上去没毛病实际就是不出结果”的问题。开发环境用的 Embedding 模型是 A后来上线换了 B两个模型的向量维度不一样。向量库允许写入不同维度的向量但检索时无法正确比较导致召回结果非常离奇——甚至空结果。排查链路一定要按这个顺序来打印向量库中存储的向量维度collection.get()里看 embedding 长度。打印当前查询向量维度。如果两者不一致不用继续查了问题就在这。解决办法有两个选一个就行固定 Embedding 模型版本线上不要随便换模型换之前要先建新的 collection 或做全量向量迁移。在写入和查询时统一做维度校验两边的维度不一致直接抛异常提醒而不是静默失败。我后来在系统里加了一行校验逻辑凡是维度不匹配就不允许写入虽然多了一步但避免了很多线上事故。4.2 冷启动期记忆系统最难受的第一个月带记忆的 Agent 刚上线时向量库里几乎没有关于用户的记忆召回必然为空。这个阶段用户的实际感受是“你告诉我你是有记忆的但感觉你和普通聊天机器人没区别。”冷启动问题的本质是记忆需要时间积累。作为开发者你不能等它自然积累得想办法“预填”接入历史数据如果之前有用例有历史对话记录直接批量做好清洗、抽取灌进记忆库。哪怕数据质量参差不齐也比空库强。引导用户补充画像在首次对话时温和地询问一些背景信息比如“您平时主要使用哪些编程语言”这样的问题让语义记忆库在第一次交互时就有基本内容。设置合理的记忆边界明确告诉用户“我是从今天开始记住你的”别让用户误以为 Agent 知道之前所有事情。这个在用户体验上反而更诚实、更可信。我在一个项目里做了“新用户引导 旧数据导入”双管齐下冷启动期从两周缩短到两天。不要觉得数据脏就懒得迁历史数据哪怕只有 70% 的质量也远比空库好。4.3 数据串台多个用户共用一套存储的灾难如果向量库的 metadata 里有user_id字段但你在写入和召回时忘了用它做过滤就会出现灵异事件A 用户问“我的项目进展如何”Agent 回答出 B 用户的项目内容。这种问题在大规模多用户场景下非常致命。一旦发生一次用户数据串台信任基本归零。排查时先看是否有过滤逻辑再看过滤条件是否生效# 错误写法没有按 user_id 过滤 results collection.query(query_embeddings[query_embedding], n_results5) # 正确写法强制按 user_id 过滤 results collection.query( query_embeddings[query_embedding], n_results5, where{user_id: user_id} )我的建议是不只在查询层面用代码过滤还要在存储设计上物理隔离。生产环境给每个用户建独立的 CollectionQdrant 支持多 Collection或者用 PostgreSQL 的行级安全策略。存储隔离比查询过滤更安全相当于你不知道对方的保险柜密码而不是知道密码但靠自觉不开。4.4 记忆注入过多导致模型注意力被稀释又一个反直觉的坑记忆召回得越多效果不一定越好反而可能更差。当 5 条记忆里只有 1 条真正相关另外 4 条只是“有点像”的时候大模型会被无关记忆带偏回答反而比“没有记忆”时更差。我调试过很多次发现问题出在“相似度阈值”设定得太低。向量召回的结果是“相似”但“相似”不等于“当前问题需要用到”。解决办法有几个设置召回阈值相似度分数低于某个值比如 0.55具体看 Embedding 模型的分布的记忆直接丢弃。加一轮重排用更强的模型或者简单规则对召回结果做二次打分只保留 Top-2 或 Top-3。控制注入量宁可少给不要多给。我给团队的默认参数是向量记忆 Top-3结构化画像最多 5 个字段总共记忆段不超过 300 Token。# 召回时动态调整阈值 results collection.query( query_embeddings[query_embedding], n_results10, # 先多召回一些 where{user_id: user_id} ) filtered [ (doc, meta) for doc, meta in zip(results[documents][0], results[metadatas][0]) if meta.get(score, 0) 0.55 ][:3] # 再过滤并截取这个“多召回、再过滤、严控数量”的三步法基本解决了我遇到的 90% 记忆效果不佳问题。5. 从“记住你”到“懂你”记忆系统的进阶演化5.1 时间衰减别让陈年旧事霸占记忆高地静态的记忆系统有个问题3 年前的记忆和昨天的记忆有同等的召回权重。但对大多数场景来说用户近期关注的内容远比历史内容重要。时间衰减的实现思路是存储时记录时间戳召回时在相似度分数上叠加一个时间折扣系数。import math from datetime import datetime def time_decay_score(base_score: float, timestamp: str, half_life_days: float 30.0) - float: days_elapsed (datetime.now() - datetime.fromisoformat(timestamp)).days decay 0.5 ** (days_elapsed / half_life_days) return base_score * decay半衰期 30 天意味着30 天前的记忆权重只剩一半60 天后只剩四分之一。这个参数需要根据业务调整做客服的可能只需要用户近一周的信息做长期知识管理的 Agent半衰期可以放宽到 90 天以上。加了时间衰减后一个明显的变化是旧的、低频的记忆不会彻底消失但也不会频繁干扰当前对话。这就是“记住但不打扰”的体验。5.2 记忆摘要与分层归档对话积累到一定程度向量库里的记忆越来越多。但我发现很多“记忆”其实是同一件事的不同变体。比如用户在前三周分别说了三个关于项目的细节单独都存了召回时可能返回三条碎片但合并成一条“项目全景摘要”才真正有用。这个需求催生了“记忆摘要层”原始记忆层保存具体的事件细节。摘要记忆层基于一段时间内的原始记忆由大模型生成结构化摘要。摘要不追求细节追求全局。核心画像层从摘要中再提炼稳定属性进结构化库。周期性任务流程大致是每天凌晨跑一次定时任务把当天新增的记忆按用户、按主题聚类。聚类后调用大模型生成“今日摘要”。将摘要合并进该主题已有的长期摘要中。这套分层的效果是Agent 在回答“整体上我是怎么做这个项目的”这类概括性问题时能直接调用摘要记忆而在回答“我上周三说的那个具体报错信息是什么”时走原始记忆检索。两条路径各司其职。5.3 从被动存到主动写Agent 自己决定记住什么前面的记忆写入流程里筛选规则是开发者写死的。这已经是“半主动”机制——模型参与了信息提取但触发与否由规则决定。L4 级别的主动记忆是Agent 在对话过程中自己判断“这段信息以后会有用”并主动执行存储动作。这类似于人的记忆行为——你不会刻意背下朋友的生日但你会记住他提到过最讨厌的食物。实现上可以采用“记忆意图识别”的方式在 Agent 的主流程里加一个旁路判断每一轮对话结束后用一个轻量模型判断“本轮是否出现值得长期记住的信息”如果有触发写入。没有的话不浪费 Token。我测试过的最小实现就是加一个分类调用def should_save_memory(dialogue: str) - bool: response client.chat.completions.create( modelgpt-4o-mini, messages[{ role: user, content: f这段对话中是否包含值得长期记住的关于用户的事实只回答 yes 或 no。\n{dialogue} }], temperature0, ) return response.choices[0].message.content.strip().lower() yes从效果上看主动记忆比规则筛选更灵敏。代价是会多几百毫秒延迟和少量 Token 消耗在非实时性要求极高的场景下完全可接受。5.4 记忆的隐私边界与删除机制“让 Agent 记住你”这件事天然伴随隐私风险。我在这部分比较谨慎因为做得不好会把整个产品带入信任危机。设计记忆系统时至少要回答这几个问题用户有权知道 Agent 记住了什么吗我建议提供“记忆管理页面”让用户能查看记忆库里的内容。用户可以说“忘了这些”吗必须提供一键清空记忆能力而且是物理删除不是软失效。记忆的保存期限是多久产品层面要定义清楚比如“会话记录保留 30 天”“用户画像可长期保留”。哪些敏感信息不应该进记忆身份证号、银行卡号、密码、健康记录等应在提取环节就拦截。不要心存侥幸等到出了事再补救。我在项目里专门做了一个敏感信息过滤器。大模型提取记忆后先过一次正则敏感词过滤命中风险词条的直接丢弃不进向量库。这一步不能省。这一节其实想说的是记忆能力强是把双刃剑。 你用记忆给用户带来了连续体验也要为用户承担记忆外泄的风险。安全边界设计得越早后续返工成本越低。写在实操之后记忆系统的迭代方向最后分享一点个人体会。我做记忆系统的第一版只想着“怎么把聊天记录存下来”结果效果一般用户感知不到。后来我把思路转过来记忆系统不是在“存储对话”而是在“构建用户模型”。存储只是手段让 Agent 在合适的时机说出“我知道你之前在搞 XX 项目那我们接着聊”才是目的。如果你准备从零开始做我建议按这样的顺序推进先把工作记忆做好——确保多轮对话不丢上下文。再上语义记忆——存用户画像让 Agent 知道“你是谁”。然后接情景记忆——用向量库存历史事件让 Agent 知道“我们之间发生过什么”。最后再考虑摘要、主动记忆、时间衰减这些进阶能力。还有一个很小的技巧我每次搭建记忆系统都会用上每周导出一份记忆库的快照备份。记忆是用户跟你积累的资产代码可以重新写但记忆丢了意味着用户对你的信任也丢了。定期备份低成本高保障。到这一步你的 Agent 已经能“记住你”了。下一篇我会聊聊当 Agent 记住了很多用户之后怎么从单用户的记忆扩展到多用户的个性化推荐和记忆共享。先写到这有落地过程中的问题欢迎在评论区交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价