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

Agent记忆系统实战:从失忆到个性化记忆管线的构建

讲Agent开发这两年我踩过最深的坑不是模型选型也不是工具调用而是“每次打开对话Agent都不认识我”。明明上一轮才告诉它“我在做跨境电商独立站预算不高”下一轮它又一本正经问“你的业务背景是什么”。这种失忆让再强的模型也像个高智商金鱼。所以这个系列写到第三篇我特意挑了“记忆”这个话题——它看似基础实际上是最能拉开体验差距、也最容易被直接套Prompt模板的人忽略的部分。在这篇文章里我会把Agent记忆这件事拆开讲清楚它到底是什么、分哪几类、为什么没有记忆的Agent做不好个性化然后给出一套可以直接落地的记忆管线设计附带代码和踩坑记录。内容偏工程实践适合正在搭Agent、或者想系统化学习Agent开发的人看完你至少能回答这几个问题记忆放在哪里什么时候写入怎么检索怎么防止Agent记下一堆没用的垃圾1. 先搞清楚Agent 的“记忆”到底指什么1.1 没有记忆的 Agent 像什么先做个简单对比。一个不带记忆的Agent本质上就是“大模型 工具调用 单轮指令”。用户说一句它答一句回答完就清空下一轮从零开始。这在只回答“今天天气怎么样”这类一次性问题时没问题但一碰到需要连续协作的场景就露馅了。举几个我实际遇到过的场景用户说“帮我整理一下下周的出差行程”Agent问了出发地、目的地、时间偏好到了第二步“顺便把机票和酒店都定了”它已经把前面信息全忘了又开始问“请问您要去哪里出差”。用户在公司内部知识库场景里反复追问某个项目的背景Agent每次都把同样的背景资料重复推一遍完全不知道用户已经看过。用户明确提出“我不喜欢太长的回复尽量说结论”Agent下一轮照样输出两千字。这些问题不是模型能力不够而是Agent缺少一个跨轮次、跨会话保存信息的机制。没有记忆Agent永远无法形成真正的个性化。很多人觉得加记忆很简单把对话历史全塞进Prompt不就行了真做起来就会发现这种方式成本高、效果差、越到后面越失控。所以记忆不是“要不要做”的问题而是“怎么做得既省又不傻”的问题。1.2 拆开“记忆”三张表搞定短期、长期与工作记忆做记忆系统之前我建议先建立一套分类框架。别一上来就想着“把所有对话都存进数据库”那种做法后面基本都会变成垃圾场。参考认知科学里对记忆分层的方式我会把Agent记忆分成三类记忆类型对应人类概念典型内容存储方式生命周期工作记忆当前正在处理的任务状态正在执行的流程进度、临时变量、已选参数会话状态/上下文单次任务结束即清空短期记忆连接当前对话的信息最近几轮对话原文、用户刚提供的偏好对话历史缓存滑窗内保留超出后摘要或丢弃长期记忆跨会话稳定知识用户画像、历史事件摘要、领域事实数据库/向量库按策略持久化可更新可过期用这个框架去设计最大的好处是知道每种信息该放哪、该留多久。工作记忆如果塞进长期存储会出现记忆污染长期记忆如果只放在对话上下文里跨会话就全丢了。我见过不少项目把三者混在一起结果短期记忆越攒越长每次调用都在烧Token。实际项目里工作记忆一般由Agent框架自己管比如LangGraph里的State或者Coze这类平台的状态变量短期记忆对应对话历史轮次长期记忆才是我们要花心思设计的部分。这篇文章的重点也是围绕长期记忆展开的。2. 记忆系统整体设计与方案选型2.1 记忆放在哪两种主流架构的取舍动手之前先定架构。目前主流做法有两种我分别叫它们“全量注入型”和“检索增强型”。全量注入型的思路比较简单把用户所有的历史交互记录打包全部塞进Prompt让模型自己根据上下文提取需要的信息。优点是实现快、不需要额外存储缺点是Token成本随轮次线性增长而且大模型对超长上下文的关注度不均匀几十轮之后关键信息会被淹没。检索增强型的思路是历史信息默认存到外部存储每次对话前根据当前用户输入检索出一小部分“最相关”的记忆再注入Prompt。这是目前生产环境采用最多的方案既控制Token成本又能精准把有用记忆送到模型面前。缺点是需要额外开发一套“写记忆、读记忆、管记忆”的管线。我的建议很明确做Demo可以用全量注入五分钟跑通但要上真实业务直接选检索增强型否则后期重构成本远高于前期搭建成本。这个判断基于一个很简单的数据一个重度用户每天跟Agent交互50条消息一周就是350条按平均每条综合开销计算全量注入的Token消耗量会涨到让人肉疼的程度。2.2 存储选型关系库、KV 库还是向量库确定检索增强架构之后下一个问题是记忆存哪。很多人的第一反应是向量数据库因为跟“AI记忆”“语义检索”绑定得深。但我不建议所有记忆一上来就扔向量库。先分类看对话历史原文适合放Redis这类KV存储Key按会话ID组织Value存消息列表过期时间可以灵活设置。因为它只用得到最近N轮不需要做全局语义检索。用户画像、偏好、事实型记忆适合放关系型数据库或文档数据库。这类记忆字段清晰、更新频繁比如用户所在城市、行业、偏好风格直接用SQL或文档更新比向量库简单得多也方便人工排查。事件记忆、知识条目适合放向量库。这类记忆是描述性的用户之前说过做过什么没法用结构化字段穷举只能靠Embedding做语义检索。比如用户提到“我最近在准备注册会计师考试”这一条信息没法拆成三五个标签最合理的做法是整句嵌入之后用户问“帮我找个学习计划”时它能被语义关联起来。向量库选型上小项目我推荐Chroma或者LanceDB本地跑零运维成本稍大一点的线上项目直接用pgvector挂在PostgreSQL上省得同时维护两套数据库数据量到千万级以上再考虑Milvus这类专门系统。别为了追求“主流”一上来就上重型分布式向量库大概率杀鸡用牛刀。2.3 记忆长什么样从原文到摘要、画像、事件存储结构确定后接下来要想一条记忆到底以什么形态保存直接保存用户原话还是加工之后再存这是很多人容易踩坑的地方因为原话信息密度低检索噪声很大。我自己常用的是三类记忆表示配合使用原始片段保存高价值对话原文比如用户主动表达的偏好、提交的具体事实。我一般只保存经过判定模块筛选过的片段不会所有消息都存。事件摘要一轮或几轮对话结束后生成一段简洁摘要描述“这段时间发生了什么”。比如“用户在过去半小时内确认了项目上线时间并提到团队缺少前端资源”。这种摘要在后续做回忆时比原文检索更高效。结构化画像将长期稳定的属性抽出来形成用户画像字段。比如姓名、城市、行业、沟通风格、关注领域。画像字段需要设计好更新策略不能用户说“我是做电商的”就写死后来用户说“我转行做咨询了”也要能改。一条完整的长效记忆我一般用五元组表示主体用户/项目 谓语偏好/事实/事件 对象 发生时间 可信度。可信度是很多系统忽略的字段实际上很有用因为Agent从对话中抽取的信息不一定为真加一个置信度用于后续更新和遗忘会从容很多。3. 落地实现让 Agent 装上长期记忆3.1 最小记忆管线写、读、更新架构定好了具体到实现我习惯把记忆系统抽象成三个阶段写入Memory Write、读取Memory Read、更新Memory Update。只要能跑通这个闭环记忆就算真正“装上”了。写入阶段要回答的问题哪些信息值得记什么时候记我采用“事后抽取 定期摘要”的组合策略。所谓事后抽取就是Agent每完成一次用户请求后用一个轻量判定把用户明确给出的偏好、事实、要求等信息提取出来写入存储。所谓定期摘要就是当单轮对话达到一定规模时对整个会话过程生成一份摘要再入库防止重要信息被滑窗冲掉。读取阶段要回答的问题当前对话需要哪些历史记忆我采用“用户输入驱动检索 固定高频信息拼接”的方式。用户每次输入后先把输入丢给检索模块从长期记忆库里拿到TopK条相关记忆同时把用户画像里的核心字段固定放进系统提示词里保证高确定性信息永远在线。更新阶段要回答的问题记忆怎么保持新鲜新记忆与旧记忆冲突怎么办我的策略是带权覆盖如果新事实与旧画像字段冲突且新事实出现次数或明确程度更高则更新旧字段如果只是泛泛提及则保留旧字段把新信息降权记录。3.2 用 LangGraph 实现带记忆的 Agent代码实操理论讲完直接上代码。我用LangGraph来演示因为它把状态、节点、边的概念表达得很清楚适合做记忆这种需要多步编排的逻辑。下面是一个简化但完整的记忆增强Agent框架省去了复杂工具调用逻辑把核心记忆流程展示出来import os from langgraph.graph import StateGraph, START, END from typing import TypedDict, Annotated from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_chroma import Chroma from langchain_core.prompts import ChatPromptTemplate # 1. 定义全局状态 class AgentState(TypedDict): user_input: str user_id: str conversation_history: list retrieved_memories: list response: str # 2. 初始化存储向量库存事件记忆KV存对话历史 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) memory_store Chroma( collection_nameagent_long_term_memory, embedding_functionembeddings, persist_directory./memory_db ) # 3. 检索记忆根据当前输入取最相关的TopK条 def retrieve_memory(state: AgentState) - AgentState: query state[user_input] # 只从该用户的子空间里检索避免跨用户记忆串扰 docs memory_store.similarity_search_with_score( query, k3, filter{user_id: state[user_id]} ) state[retrieved_memories] [ {content: doc.page_content, score: score} for doc, score in docs if score 0.35 # 距离越小越相关0.35是调过的阈值 ] return state # 4. 写入记忆从本轮交互中抽取值得记的内容 def write_memory(state: AgentState) - AgentState: # 简化处理这里直接调用一个抽取函数 # 实际项目里可以设计结构化输出让模型判断是否值得记录 extract_prompt 从用户本轮输入中抽取值得长期记住的信息比如偏好、身份、计划、重要事实。 没有值得记的信息就返回空列表。 用户输入{input} .format(inputstate[user_input]) # 实际开发中建议用结构化输出避免自由文本解析 extracted llm.invoke(extract_prompt) # 示例如果抽取结果非空则写入向量库并关联user_id的元数据 if extracted and extracted.strip() and extracted ! []: memory_store.add_texts( texts[extracted], metadatas[{ user_id: state[user_id], timestamp: 2025-01-15T10:30:00Z, source: conversation }] ) return state # 5. 生成带记忆的回复 def generate_response(state: AgentState) - AgentState: history state[conversation_history][-6:] # 短期记忆只保留最近几轮 memory_block if state[retrieved_memories]: memory_block 以下是与你相关的历史记忆请在回答时参考\n \n.join( m[content] for m in state[retrieved_memories] if m[score] 0.35 ) prompt ChatPromptTemplate.from_messages([ (system, 你是用户身边的智能助手。请结合给定的历史记忆和对话历史个性化地回应用户。\n{memory_block}), (human, 用户输入{user_input}) ]) chain prompt | llm state[response] chain.invoke({ memory_block: memory_block, user_input: state[user_input] }).content return state # 6. 组装图 llm ChatOpenAI(modelgpt-4o-mini) graph StateGraph(AgentState) graph.add_node(retrieve_memory, retrieve_memory) graph.add_node(write_memory, write_memory) graph.add_node(generate_response, generate_response) graph.add_edge(START, retrieve_memory) graph.add_edge(retrieve_memory, generate_response) # 写记忆可以在回复生成前做也可以并行这里放在回复后更合理但为演示放在前 graph.add_edge(retrieve_memory, write_memory) graph.add_edge(write_memory, generate_response) graph.add_edge(generate_response, END) app graph.compile() # 模拟一次带记忆的对话 result app.invoke({ user_input: 我最近在准备注册会计师考试每天只有两小时学习时间, user_id: user_001, conversation_history: [] })代码里的几个关键点我实际调试的时候反复确认过filter{user_id: state[user_id]}非常关键。如果不加这个过滤一旦用户量上来会检索到其他用户的记忆这在生产环境是严重数据事故。相似度阈值不是拍脑袋定的。我用验证集测过不同阈值下的精确率0.35左右在这个Embedding模型下既能召回相关记忆又不会带进太多噪声。换模型必须重新调。写入和读取谁先谁后其实有讲究。上面这段为了演示把写入放在回复前真实业务我建议在回复完成后异步执行写入避免阻塞主链路。LangGraph这类框架真正的价值在于它可以把“记忆写入—记忆检索—回复生成”这些环节定义成图中节点后续加记忆清理、人工审核节点都很方便不用推翻重写。3.3 Prompt 里记忆区怎么放注入技巧与模板记忆检索回来了怎么放进Prompt也有讲究。不是随便拼接就行位置和格式直接影响模型对记忆的利用效果。我在生产中常用三段式Prompt组织方式第一段系统角色 固定画像信息比如用户的称呼、常驻城市、沟通风格。这些是“无论如何都要遵守”的底噪信息。第二段动态记忆区放检索到的历史事件摘要、偏好描述。这段用固定的分隔标记包住让模型能区分“记忆”和“当前对话”的边界。第三段短期对话历史 当前用户输入。有个比较典型的模板你是一个深度陪伴式助手以下是关于用户的稳定画像回答时需顺应这些特征 - 称呼小林 - 沟通风格偏简洁希望拿到结论 - 当前关注注册会计师备考 - 常用设备MacBook常用工具帮做学习计划 --- 长期记忆区 --- 2025-01-10用户提到每天可学习时间约2小时主要在晚上。 2025-01-12用户反馈不喜欢碎片化的学习安排。 --- 对话历史 --- 用户帮我做一个本周学习计划 助手... --- 当前输入 --- 用户周六要多学一会儿实测下来动态记忆区要放在“对话历史”之前因为模型对系统提示词后部和靠近用户输入的中间区域关注度更高。如果放在一大段对话之后模型容易忽略它们。另外记忆区里的每条记录我会带上时间戳方便模型判断哪些记忆是旧的、可能已过期。这个小细节帮我解决了不少记忆过时引发的逻辑矛盾。4. 记忆工程的核心细节摘要、重要性与遗忘4.1 对话压缩滑窗加摘要控制上下文成本短期记忆不能无限膨胀长期记忆也不能只增不减。先说说对话历史这块的处理。我实践下来比较稳的方案是“滑窗 摘要”两层策略。滑窗负责保留最近最完整的上下文摘要负责承接更早但仍有价值的信息。具体实现上我设置一个窗口大小比如最近8轮对话完整保留。超过8轮后触发一次会话摘要任务把窗口之外的内容压缩成结构化摘要下次用到时以摘要形式注入而不是把历史原文全带着。为什么是8轮因为按平均每轮300到500个Token算8轮大概三四千Token这个规模对很多常用模型的上下文压力不大同时足够处理大部分连续任务。摘要触发条件也可以按Token数来比如对话累计超过4000 Token时就做一次压缩。压缩任务建议用稍微强一点的模型单独执行别在主对话链路里占用响应时间。摘要生成后可以归档到事件记忆库如果以后聊到相关话题检索系统能把它捞回来。4.2 记忆不是越多越好重要性评分机制第二个细节是“记忆筛选”。很多人在做记忆系统时有个误区觉得把用户说过的话都记住才安全。实际跑一段时间就会发现问题记忆库越堆越满检索时经常捞回一堆平庸内容真正关键的反而被淹没。我给每条进入长期记忆的信息打一个重要性分这个分由三个维度加权计算明确性用户是否用了确定性很强的表达比如“我确定”“一定”“再也不要”比“可能”“也许”的分高。重复度用户在多轮对话中提到同一主题的次数越高说明越重要。行为暗示是否会显著影响后续决策比如“我下周到北京出差”比“我喜欢蓝色”在商务助手场景下权重更高。低于阈值的记忆我只保留摘要不进入细粒度检索高于阈值的记入核心画像或高频事件库。这套机制跑起来之后最明显的变化是检索TopK的记忆命中率提升了一大截因为库里的每条记录都是“值得留”的而不是“顺手留”的。4.3 用户画像的持续演化长期记忆不是写一次就完事用户是会变的。如何让画像跟随用户实际行为演化是体现记忆系统成熟度的关键。我的更新策略可以总结成“显式覆盖 隐式修正”两条线。显式覆盖指用户明确说出跟旧画像冲突的新信息比如用户说“我不再准备注会了改了方向”此时直接更新画像字段并保留一条变更日志避免下次回复还在聊注会。隐式修正指用户没有直接说但行为连续多轮暗示旧画像不再适用比如用户连续一周每天问的都是另一件事这时系统会对旧画像字段做降权等待更明确的信号再更新。这里要特别提醒画像字段的更新必须谨慎宁可晚一步更新也不要因为用户随口一句话就大改画像。我早期犯过这个错用户说“我还没决定考研”系统直接把他标记为“考研用户”后面推荐了一堆考研资料被嫌弃了很久。判断是否更新至少要结合上下文确认不能逮着一句话就当真。5. 常见问题与排查技巧实录5.1 检索到“相关记忆”却是噪声这是我在向量检索方案里遇到最多的坑。语义相似度高不代表对当前任务有用。比如用户曾经聊过“我经常失眠”后来问“有什么提高效率的建议”向量检索很可能把“失眠”捞出来因为“经常”和“建议”在语义空间上离得近。但这条记忆对当前效率问题没什么帮助。解决思路是混合检索。在纯向量检索之外加一层关键词匹配或者用重排模型对召回结果打分。轻量做法是向量召回Top20然后用一个文本相关性规则或小模型做重排只保留真正对当前任务有帮助的记忆。另一种思路是给记忆打业务标签检索时先用标签粗筛再在粗筛结果里做向量相似度排序。5.2 模型把推理过程当成事实记忆第二个高频问题是记忆写入环节把模型自己的推理内容当成用户事实存了下来。比如用户问“我适合转行做数据分析吗”模型在思考过程中可能输出一段分析如果直接把这段分析当作用户特征写入记忆后续对话就会默认“用户已在转行数据分析”完全跑偏。解决办法是严格区分“用户陈述”和“模型推理”。写入模块只允许从用户消息中抽取信息模型生成的推测性内容一律打上置信度标签低于阈值不进长期库或者只作为候选记忆等待用户后续确认。我在抽取模块里加了一个前置判断“这条信息是用户明确表达的事实还是模型自己的推论”这一步看起来简单实际能过滤掉八成以上的记忆污染。5.3 记忆膨胀导致性能劣化记忆系统的另一个常见问题是“只进不出”时间长了检索性能明显下降存储成本也在涨。我见过一个项目跑了一个季度记忆库里有上百万条片段每次检索耗时超过800毫秒直接把对话延迟拖垮。应对方案有三板斧。第一斧是设单用户记忆上限比如每个用户最多保留2000条长期记忆超出后触发清理策略。第二斧是定期压缩把一段时期内同主题的记忆合并几条并成一条摘要。第三斧是遗忘机制超过90天未被检索命中的低优先级记忆自动进入冷归档再久就删除。给Agent设计遗忘能力听起来反直觉实际反而提升了记忆系统的整体质量。5.4 用户隐私与记忆边界最后必须聊一条底线问题用户记忆涉及个人数据不是想存什么就存什么。我在做记忆库设计时有一个总原则能少存就少存能派生的不存原文敏感信息脱敏。比如用户提到身份证号、银行卡号这类明文写入前先做字段识别和掩码绝不以明文形式进向量库。同时给用户提供“查看我存储的记忆”和“删除我的记忆”入口这在产品层面既是合规要求也是建立信任的必要条件。记忆的时间边界也要注意。用户说“这是我以前的情况现在已经变了”系统应该有能力按照时间戳或者用户显式指令做部分遗忘而不是永远拿旧记忆回答新问题。这一点在工程上很容易被忽略但在真实使用中对体验影响极大。跟着这个系列一路看到这里你会发现Agent的记忆根本不是“加一个数据库”这么简单它更像是一个需要精细设计的小型系统牵扯到信息抽取、存储选型、语义检索、更新策略和遗忘策略。我个人这几轮迭代下来最深的体会是记忆系统的目标不是“记得多”而是“记得准、用得对”。那些看似聪明的“全记住”方案最后基本都会被噪声和成本拖垮反而是做了筛选、压缩、遗忘的系统能稳定跑很久。最后再分享一个小技巧上线记忆功能之后我建议花点时间做一个“记忆可视化后台”把每个用户的核心画像、最近写入的事件记忆、检索命中情况列出来。你不需要看很多次但每隔几天抽查几条就能发现很多自动评估发现不了的问题。记忆系统是会被用户行为“养”起来的越用越准前提是你在早期愿意不断手动去校验它。
分享:

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

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