拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI Agent记忆分层设计与落地实践:从短期上下文到长期向量召回

做Agent开发的人应该都遇到过这么个场景上午刚跟助手聊完一个项目的技术选型下午再打开对话它已经完全不记得我们讨论到哪儿了又得从头补一遍背景。更让人崩溃的是你上周明确告诉过它“我平时用Python多一些不太熟Java”这周它照样用Java风格给你写示例代码。这种“每次见面都像第一次认识”的体验放在普通的聊天机器人身上还能忍但如果你做的AI Agent是要持续处理任务、长期扮演某个角色的产品那没有记忆能力基本等于废了一半。“走进AI Agent”这个系列写到第三篇我觉得最值得展开的就是Agent的记忆能力。通俗点说就是让Agent记住你、记住你说过的话、记得你的偏好和习惯。这篇我打算从记忆的分层设计、底层存储与召回机制、实际落地代码再到踩坑经验完整捋一遍适合正在做Agent开发的工程师也适合产品经理用来判断自己的Agent到底该往哪个方向打磨。1. 为什么Agent需要“记住你”1.1 无记忆的Agent永远在“第一次见面”我见过不少团队做了一个看起来很聪明的Agent能写代码、能查资料、能调用工具但用户用了几次就再也不碰了。原因很简单每次对话都是全新开始用户得反复解释自己的身份、背景、需求和偏好。这种感觉就像你去一家常去的餐厅服务员每次都问“您想吃点什么”完全不记得你上次说过的忌口。从产品体验的角度看记忆是Agent从“工具”升级为“助手”的分水岭。一个没有记忆的Agent它的回答质量完全取决于当前这轮对话输入了多少上下文一旦上下文被截断或对话结束所有信息就都丢了。而一个带记忆的Agent能够跨会话、跨任务地持续累积对用户的理解回答会越来越贴合用户的实际情况。拿实际场景来说我做过一个面向内容创作者的写作助手Agent。最初版本完全没有记忆用户每次都要重新告诉它“我的公众号读者是互联网从业者我偏好口语化表达最近在写AI工具测评”。后来加了记忆层它自己会记住这些信息。实测下来用户第二次开始对话时基本不需要重复背景直接说“继续写昨天那个选题的第二段”就能接着干。活跃度提升了将近一倍因为用户明显感觉到“它还记得我”。从技术角度看记忆缺失的根源在于大语言模型本身是“无状态”的。模型的权重在训练后是固定的它不会自动记住你和它之间的对话。每次调用API你都要把全部相关信息塞进上下文窗口模型只看到当前这一次请求的内容。所以“让Agent记住你”本质上就是在模型外面额外搭一套记忆系统把需要的信息在合适的时机自动取出来喂给模型。1.2 记忆分层的整体设计思路既然模型本身不记事我们就得在外面给它造一个“大脑”。我在实际项目里习惯把Agent的记忆设计成三层对应不同的数据特性和访问频率。第一层是短期记忆就是当前会话内的上下文信息。比如你正在写一篇文章前面几轮讨论的修改意见需要保留住。短期记忆通常直接利用大模型的上下文窗口但窗口大小有限现在主流是128K到200K token所以需要做截断或摘要确保塞得下又不丢关键信息。第二层是长期记忆解决的是“跨会话”的召回问题。用户昨天聊过的技术选型、上个月定过的方案下次对话时还能被主动想起来。这一层一般用向量数据库存储通过Embedding把文本转换成向量再做相似度检索。它解决的是“语义召回”的需求你说“我上次提过的那个性能优化方案”它能根据语义找到对应内容。第三层是用户画像和结构化信息比如用户的姓名、职业、语言偏好、作品风格、常用工具链。这些信息相对稳定变化频率低适合用结构化方式存储。调用时直接注入系统提示词让Agent在一轮对话开始时就“知道”用户是谁。这三层不是互相替代的关系而是配合使用的。长期记忆负责“想起来”用户画像负责“懂你”短期记忆负责“接着聊”。分层设计还有一个好处每一层的技术选型和维护策略可以独立优化。比如用户画像适合用SQL或JSON存储长期记忆适合用向量库短期记忆则要跟着模型窗口能力走。下面我把每一层的核心机制展开讲清楚。2. 记忆机制的三种核心形态2.1 短期记忆上下文窗口到底怎么管短期记忆是所有Agent都无法绕开的一层因为你只要调用大模型API就必须把对话历史作为上下文传进去。问题在于上下文窗口不是无限的对话一长就会溢出。处理上下文溢出业界的主流方案有三种。第一种是“滑动窗口截断”只保留最近N轮消息更早的直接丢弃。这种做法实现最简单但问题是如果用户在一小时前提过一个关键需求之后聊了很多无关内容那个关键需求就被挤掉了。适合需求简单、对话轮次少的场景。第二种是“摘要压缩”这也是我比较推荐的做法。对话到达一定长度后调用一次大模型把之前的对话内容提炼成一段摘要再连同最近几轮的原始消息一起放进上下文。LangChain里有个ConversationSummaryBufferMemory思路就是窗口内保留原文窗口外做摘要。这样做的好处是既保留近期细节又不丢失早期的重要信息代价是多了一次摘要调用的延迟和费用。第三种是“关键信息提取”本质上就是让大模型在每轮对话结束后判断有没有值得长期记住的信息比如用户透露了偏好、确认了决策、提到了重要日期。如果有就写入长期记忆。这种方式能把短期记忆里的重要内容“沉淀”下来就算上下文被清空也不怕。我自己的经验是短期记忆不能只依赖一种策略。通常在Token总量小于窗口的60%时直接全量保留超过60%就触发摘要压缩同时每轮都做关键信息提取写入长期记忆。窗口占用比例需要根据具体模型调GPT系列和开源模型的表现不完全一样建议实测后再定阈值。2.2 长期记忆向量检索与语义召回长期记忆的目标是实现“说一句话就能关联到很久以前的信息”。比如用户问“我之前让你调研过的那个向量数据库后来我选了哪个来着”Agent如果能检索到上个月对话里的相关内容体验就会很好。实现长期记忆的技术栈并不复杂核心流程是三段式内容切片、向量化、相似度检索。先看内容切片。对话历史也是需要切分的但和文档切片不同对话切片最好是按“语义单元”来切。我的做法是把连续几轮围绕同一个主题的对话合并成一个记忆片段而不是机械地每500字切一块。比如用户在一轮对话里详细讲了项目的背景、目标和时间节点那这一整段就可以作为一个记忆单元存进去。切得太碎召回时会缺少上下文切得太粗召回时又不够精准需要反复调。然后是向量化。这一步会把文本片段转换成高维向量核心是选择一个合适的Embedding模型。中文场景我最早用的是开源的BGE系列后来也用过OpenAI的text-embedding-3-small实测下来BGE在中文对话上的语义召回效果更稳定而且可以本地部署成本也低。Embedding模型选得不好后面检索质量再折腾也白费。最后是相似度检索。用户当前的输入先转成向量在向量数据库里查最相近的记忆片段。这里有两个关键参数返回数量top_k和相似度阈值。我在项目里一般把top_k设为3到5阈值则要看具体Embedding模型的分数分布需要跑一批真实数据看分布再定不要想当然写死一个值。实际的长期记忆存储我推荐几种方案数据量小且简单时用ChromaDB或LanceDB部署省心数据量大了或者需要和现有基础设施打通用Milvus、Qdrant或pgvector更合适。pgvector是个很有意思的选项因为很多团队本来就有PostgreSQL加个扩展就能当向量库用运维成本低。2.3 用户画像结构化偏好存储长期记忆解决的是“发生过什么”用户画像解决的则是“用户是一个什么样的人”。这一层的信息相对稳定比如用户的职业、常用语言、写作风格偏好、习惯的工具链、对回复长度的要求。这类信息用自然语言存在向量库里当然也行但用结构化方式更可靠、更高效。我在项目里通常用一个JSON文档或SQL表来维护用户画像大致长这样{ user_id: u_12345, name: 张小白, profile: { profession: 前端工程师, languages: [JavaScript, TypeScript], preference: { answer_style: 简洁直接, code_style: 有注释偏向函数式, depth: 中等深度避免过长理论展开 }, facts: [ 正在开发一个React组件库, 团队规模5人偏重快速迭代 ] }, updated_at: 2026-01-18T10:00:00Z }用户画像是怎么来的两种方式。一种是用户主动告诉Agent“叫我阿杰”“以后回复短一点”——这是显式信息直接抽取入库。另一种是Agent在对话中自己推断出来的比如用户连续三次问了Vue相关问题就可以把“Vue使用者”记到画像里——这是隐式信息需要Agent自己判断和确认。写入时机是画像模块的关键。我的建议是不要在用户还没有明确透露信息时强行猜测宁可少记一点、记录确定的内容。给画像是给Agent提供稳定的“人设锚点”而不是给它额外的不确定信息源。调用时机上用户画像的内容每次对话开始时都要注入到系统提示词里让Agent在生成第一句话的时候就带着“对用户的了解”。这一步对体验的提升非常明显。用户会觉得这个Agent真的“懂我”而不是每次都要自我介绍一遍。3. 实操落地方案给Agent加一个记忆层3.1 技术选型现成框架还是自研记忆层聊完了机制接下来谈落地。很多第一次做Agent记忆的同学第一反应是找个现成框架解决。确实LangChain、LlamaIndex、Dify这些框架都提供了记忆相关的模块开箱即用。但用了几次之后我的感觉是框架的记忆组件适合快速验证原型生产环境里还是得自研或者深度定制一层。为什么因为框架给的记忆模块都比较“通用”而实际项目里的记忆行为非常业务化。比如有的场景需要按用户ID隔离记忆有的场景需要按对话主题分组召回有的场景要求某些信息绝对不能存入长期记忆。这些规则在通用框架里写起来很别扭不如自己在外面单独搭一个记忆服务通过API供Agent调用。记忆服务大致包含这些组件对话管理模块负责短期上下文的存储与摘要Embedding服务负责文本向量化向量数据库负责长期记忆的存取画像存储负责用户结构化信息的管理检索编排层负责把当前输入转化为检索请求把召回结果注入Prompt。听起来复杂但用几个成熟组件拼起来并不难。另外要考虑读写路径。写入路径上不是每一句对话都值得存。我在系统里给记忆加了个重要性判断在对话结束前或每几轮结束时让Agent回顾一下刚才的内容挑出“值得记住”的事实存下来。读取路径上要把召回的记忆按当前对话的相关性排序别一股脑全塞进Prompt——那既浪费token又可能干扰模型生成。3.2 核心代码记忆的写入、召回与注入下面分享一个简化但完整的记忆层实现示例。我用的技术组合是Python ChromaDB OpenAI兼容接口。ChromaDB适合起步生产环境按同样思路换Milvus或pgvector即可。先定义一个记忆服务的核心类包含写入和召回两个主方法import time import uuid import chromadb from openai import OpenAI client OpenAI() chroma_client chromadb.PersistentClient(path./agent_memory) collection chroma_client.get_or_create_collection( namememory, metadata{hnsw:space: cosine} ) def save_memory(user_id: str, content: str, memory_type: str fact): 将一段值得记住的内容写入长期记忆库。 memory_id f{user_id}_{uuid.uuid4().hex[:8]} collection.add( documents[content], metadatas[{ user_id: user_id, type: memory_type, timestamp: time.time(), importance: 0.8 # 由上层LLM判断后传入 }], ids[memory_id] ) def recall_memory(user_id: str, query: str, top_k: int 3, min_score: float 0.25): 根据用户当前输入召回最相关的长期记忆。 results collection.query( query_texts[query], n_resultstop_k, where{user_id: user_id} ) docs results.get(documents, [[]])[0] distances results.get(distances, [[]])[0] # ChromaDB的distance是越小越相似转换成相似度分 scored [ (doc, 1 - dist) for doc, dist in zip(docs, distances) if (1 - dist) min_score ] scored.sort(keylambda x: x[1], reverseTrue) return scored def build_prompt_with_memory(system_prompt: str, user_input: str, user_id: str, chat_history: list): 在构造Prompt时注入用户画像、召回记忆和短期聊天历史。 profile load_user_profile(user_id) # 从SQL/JSON读取用户画像 memory_items recall_memory(user_id, user_input) memory_text \n.join( f[长期记忆] {doc} for doc, score in memory_items ) enhanced_system ( system_prompt \n\n# 用户画像\n (profile if profile else 暂无额外信息) \n\n# 与该用户相关的历史记忆\n (memory_text if memory_text else 暂无相关历史记忆) ) messages [{role: system, content: enhanced_system}] messages.extend(chat_history) messages.append({role: user, content: user_input}) return messages这段代码里save_memory负责把值得记住的信息和用户ID绑定后存进向量库recall_memory在当前用户的数据范围内做相似度检索build_prompt_with_memory则把用户画像、长期记忆和短期聊天历史一起拼装成最终的Prompt。这里有个细节要特别注意所有检索都必须带上user_id作为过滤条件。如果漏了这个用户A在服务端留下的记忆可能会被用户B检索到这在多用户场景下是最严重的事故没有之一。向量数据库的where条件就是用来干这个的。3.3 关键参数调整与调优经验记忆系统的效果好坏往往不是模型强弱决定的而是几个关键参数调没调对。我把实战中最重要的参数和经验整理一下。top_k召回数量是我最先调的参数。太小了召回的上下文不够太大了噪声多而且烧token。在我做的内容创作场景top_k3表现最好在技术问答场景top_k5更好因为技术细节需要更多上下文。建议默认从3开始根据用户反馈和实际应用场景上下调整。相似度阈值经常被忽略但它直接影响记忆质量。阈值设得太低召回一堆不相干的内容设得太高该想起来的东西又漏了。我的经验是先跑一批真实对话数据把所有记忆片段跟用户问题的相似度分数拉出来画个分布看看“真正相关”和“明显不相关”的分界在哪里再把阈值定在分界附近。写入策略和记忆数量直接决定长期记忆的“纯度”。不能啥都往里存否则检索时会反复命中那些低价值的琐碎内容高价值的真正重点反而被淹没。我在Agent的流程里加了一个过滤逻辑对话结束后由LLM判断这段对话里有没有值得长期保存的信息只有重要性评分高的内容才写入记忆库。垃圾进垃圾出第一批入库存的就是以后检索到的。最后是时效性问题。时间久远的记忆相关性自然下降在检索时应该对记忆做时间衰减。比如公式可以设计为 final_score similarity_score * alpha recency_score * beta其中recency_score按天数负指数衰减。我一般用 alpha0.7、beta0.3这样既保证语义相关占主导又给近期记忆额外加分。4. 记忆维护、遗忘机制与安全底线4.1 记忆写入的去重与冲突处理长期记忆用得久了容易出现重复和冲突。比如用户先告诉你“我主要写Java”一个月后又跟你说“最近转Go了”如果两条记忆都被存进去了Agent就会很困惑不知道到底该按哪个来。我的做法是写入时先做一次相似度检索。如果新记忆和已有记忆的相似度较高往往说明是同一条信息的更新版本那就应该用新内容覆盖旧内容而不是再插一条。这个“先查重再写入”的步骤虽然多了次向量查询但能显著降低记忆库的膨胀速度也避免了冲突。对于用户画像类信息冲突处理要更严格。画像字段尽量结构化固定字段有更新时直接覆盖旧值。对于画像里无法枚举的自由文本我建议在写入时让LLM判断一下新旧两条信息的关系是替代、补充还是矛盾矛盾时以最近的显式表达为准。4.2 主动遗忘与记忆衰减策略记忆不是越多越好。人脑有遗忘机制Agent的记忆库也该有。长期不访问的记忆、已经过时的信息、明显没用的闲聊片段留在库里除了浪费空间、拉低检索精度没有任何好处。遗忘策略我按优先级排序做了三档。最低频的是“过期清理”对带时间戳的记忆片段定期归档比如超过180天未被检索命中的记忆可以移到冷存储或直接删除。中间档是“冲突合并”定期让LLM把同主题的多条记忆合并成一条精炼的概括比如同一项目的多条讨论可以汇总成一个完整记忆块。最高频的是“即时衰减”在检索打分时直接降低旧记忆的得分权重让新记忆更容易被命中。有一件事需要特别注意遗忘策略执行前一定要确认是否影响用户核心数据。比如用户明确要求长期保存的偏好设置不应该因为“长时间没被访问”就删掉。归档和删除都要留审计日志线上出问题的时候还能回溯。4.3 隐私与数据安全红线记忆功能天然涉及用户数据所以隐私安全必须从设计第一天就纳入考虑而不是等功能上线后再补。我在这块坚持几条硬性原则。最小化收集原则。能不入库的信息就不入库尤其是身份证号、地址、健康信息这类敏感数据。在写入记忆之前做一道敏感信息过滤通过正则或LLM判断命中规则就直接丢弃或脱敏。别为了“记忆功能完整”把不该留的全留下。用户控制权是底线。产品里至少提供三个按钮查看记忆、删除单条记忆、清空全部记忆。很多用户对“AI记住了我的一切”有天然的不安全感给用户明确的控制入口反而会提升信任度。我做的写作助手在产品设置里加了“记忆管理”页用户能看到Agent记住了自己哪些信息还能一键删除上线后几乎没收到过隐私投诉。多租户隔离同样是硬要求。任何检索和写入都必须带上用户维度并且要经过统一鉴权层。之前某些产品出过A用户读到B用户记忆的事故基本都是因为查询时漏了用户ID过滤。代码review时必须重点看这一块。5. 常见问题与排查技巧实录5.1 上下文窗口溢出的处理方案对话稍长就报“context length exceeded”这是接入记忆后最先遇到的问题。很多人以为是模型窗口不够大其实往往是代码没做好上下文管理。排查思路先看历史消息是不是一直在无限制地累积有没有触发摘要压缩。我建议在对话服务里加一个统计模块实时计算当前消息数组的token总量超过告警阈值就自动触发摘要流程。token估算不一定要调和模型完全一致用tiktoken估算即可误差能控制在5%以内。如果摘要也压不住就得考虑裁剪历史消息里的“大块内容”。比如长文本工具返回结果、整页抓取的网页内容这些尽量不进聊天历史用完即弃只把结论性内容放回记忆库。把工具返回和对话历史分开管理上下文压力会小很多。5.2 检索结果不相关Embedding模型与chunk优化召回了一大堆记忆但真正相关的没几个这是记忆Agent最常见的翻车现场。问题往往不在向量数据库而在“源头”和“中间步骤”。先检查Embedding模型。换一个模型效果差距可能很大。如果发现相似度普遍虚高或者同类话题的相关分数反而不高多半是该换Embedding模型了。中文场景我推荐先试开源的BGE-large-zh再对比text-embedding-3-small用自己的一批标注数据跑对比选效果好、成本能接受的。再检查chunk切分方式。对话记忆的切分粒度直接影响检索精度。切得太碎召回片段缺少上下文模型拿到手也不知道在说什么切得太粗片段里掺了大量无关信息语义被稀释。我的经验是以“一段完整的话题讨论”为切分单位配合一定的首尾重叠效果比纯按固定字数硬切好很多。最后检查查询的构造方式。用户问题有时很口语化“那个上次说的方案”这种指代性说法直接检索效果很差。我的方案是在检索前先做一次查询改写让LLM把模糊说法扩写成可检索的完整语句再用改写后的文本去查向量库。这一步成本很低但召回效果提升非常明显。5.3 记忆“串味”与污染问题记忆污染是我认为最隐蔽也最危险的问题。表现是Agent忽然开始胡说前言不搭后语甚至角色错乱。排查下来十有八九是检索到了和当前场景无关、甚至错误的内容把它当成了上下文送进了Prompt。最常见的污染源是“记忆内容本身是错的”。比如用户随口说了一句“我下个月可能去上海”系统把它当成了确定事实存进去下次Agent就会说“你已经决定去上海了”。想尽量避免这种情况写入前的判断逻辑要更严谨只把明确表达的事实或决策存入记忆猜测性、未经确认的信息不写。另一个污染源是召回时对记忆缺少来源可信度的区分。我的做法是在元数据里增加一个confidence字段用户主动说的置信度最高系统推断的置信度较低。召回后可以按置信度过滤或降权。如果Agent开始胡言乱语第一件事就是检查系统提示词里注入的记忆文本看看是不是混入了意义不明的内容。5.4 性能与成本优化记忆功能本身是有成本的。每轮对话多一次向量检索写入时要多一次Embedding摘要压缩时还要多一次大模型调用。用户量大了之后这些成本会被放大得很明显。向量检索这块比较好优化。第一是加缓存同一用户检索过的高频问题在短时间内直接复用结果不用每次都查库。第二是控制collection的规模定期清理低价值记忆也能让检索速度更快。成本大头其实在“写入判断”和“摘要压缩”这两处LLM调用上。优化思路是让它批量处理不是每一轮对话都调用LLM判断是否该写入记忆而是攒几轮对话后一次性分析。对话摘要也不是每次都整段总结而是增量式地只总结新增部分再合并进已有摘要。这样优化下来LLM调用次数能降一半以上对记忆质量的影响很有限。写到最后我自己做Agent记忆层的这段时间踩过不少坑最大体会是记忆功能不是建好就完事的静态模块它更像一块需要持续打理的地。写入策略、检索参数、遗忘机制每一项都需要根据真实数据反复调优。刚开始搭的时候宁可设计得保守一点——少存、精存、可控也比一股脑全记住然后不可收拾要强得多。最后再分享一个实用技巧在开发环境专门做一个“记忆体检”脚本批量跑一批典型对话场景输出Agent实际注入的记忆内容肉眼检查一下有没有存错、存偏、存重复的情况。记忆系统不像模型推理结果那么直观它的问题往往要过很久才在对话里暴露出来。定期体检虽然土但能拦住大部分潜在的翻车现场。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门