ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent记忆增强实战:从四层记忆模型到LangGraph落地

Agent记忆增强实战:从四层记忆模型到LangGraph落地 你是不是也遇到过这种场面跟一个Agent聊了快一个小时把项目背景、个人偏好、技术选型的原因全交代了一遍第二天打开新会话问它“昨天那个方案你还有印象吗”它礼貌地回你一句“我没有关于之前对话的记忆”。那一刻你大概会觉得这个Agent就是个“金鱼脑子”。“让 Agent 记住你”这件事看着只是给对话加个记忆真正做起来会牵扯到上下文管理、存储选型、消息架构、检索策略甚至还有遗忘机制和用户隐私。很多做AI Agent开发的人一上来就套一个大向量数据库以为把历史消息丢进去就万事大吉结果实测下来记忆是记住了该想起来的想不起来不该记的全塞进上下文Token费用先崩了。我这次想聊的就是我在实际项目里给Agent加记忆的完整做法。不绕理论直接讲我试过、踩过、又调回来的那些方案。1. 无记忆Agent的尴尬为什么“记住你”是道分水岭1.1 无记忆Agent的真实工作状态没做过Agent记忆的人可能觉得“无记忆”只是不方便而已。实际用起来问题比想象中大得多。我当时接的一个客服知识库项目里面有个Agent负责帮用户查订单、处理售后。用户第一次问“我这个耳机是上个月买的现在右耳不响了”Agent会贴心地给一套排查步骤。用户照着做完了回来说“已经重置过了还是不行”如果中间隔了几天、或者用户开了新会话Agent压根不知道“已重置”这回事又开始从第一步教用户重启配对。用户直接火了你是来来回回只会这几句吗无记忆Agent的工作模式本质上是每次对话都当成“人生若只如初见”。它的输入只有当前这条消息加上系统提示词至于用户是谁、以前说过什么、做过什么通通不在考虑范围内。1.2 记忆缺失带来的三类典型损失踩过这个坑之后我把记忆缺失的代价整理了三条几乎覆盖了我后来遇到的所有业务场景重复沟通成本用户每次都要重新交代背景上下文的“一次性”让对话效率极低。尤其在企业场景里员工跟内部Agent沟通工作进度第一天说了一遍第三天还得再说一遍。决策失准没有历史信息兜底Agent给的建议永远是泛泛的。比如用户偏好简洁回复还是详细报告、用户所在地时区、用户当前手上用的技术栈这些信息一旦“失忆”Agent的输出就从“定制化建议”降级成“通用科普”。无法积累真正有价值的Agent应该是越用越懂用户。没有记忆这个“越用越好”的增量逻辑就不存在每次都是从零开始等于把之前的所有交互资产全部丢掉。1.3 什么才叫“真正记住了你”后来我把“记住你”拆成了几个可验证的指标这样至少知道做出来之后怎么验收用户再次登录时Agent能直接说出用户上次处理到哪一步了用户明确给过的偏好比如“回复短一点”“报价用美元”后续对话不再需要重复用户纠正过Agent的错误之后同样的错误不会再犯第二次一段时间后再聊Agent还能提取出与当前话题相关的历史背景这些指标看起来朴素但每一条都指向“记忆不是存档而是影响后续行为”。记住本身不是目的记住了还能用上才是这套架构存在的意义。2. 先把记忆拆开四层记忆模型决定架构走向做记忆设计的第一个误区是以为“记忆聊天记录”。我见过不少项目把历史消息一股脑塞进向量库然后幻觉式地以为全解决了。结果就是要细节时没有细节要画像时没有画像。我后来参考了认知科学里对记忆的分类方式结合工程可实现性把Agent记忆拆成了四层。这个分层直接决定我后面的存储选型和检索策略所以我建议你在动手写代码之前也先给自己的Agent做这么一次分层。2.1 工作记忆承载当前对话的临时上下文工作记忆对应的是“当前这场对话正在进行中的上下文”包括最近几轮消息、用户刚上传的文件内容、当前任务相关的中间状态。工程上它通常就是大模型的上下文窗口。核心问题是这层记忆很“贵”——Token就是成本所以需要做取舍和压缩。我现在处理工作记忆的手段很朴素对超长输入先做摘要而不是把原文全塞进去对离当前意图太远的历史消息只保留摘要不保留原文用结构化字段跟踪任务中间状态比如“当前步骤3/5”而不是靠模型自行理解注意工作记忆的设计原则是“够用就行”。宁可少带一点也别让无关内容冲淡当前任务的注意力。2.2 情景记忆把历史会话变成可检索的事件情景记忆负责回答“用户之前做了什么”。它是老会话的压缩和重组织不是简单的聊天记录存档。比如用户上次查过某个产品、做出过某个决定、上次对话中遇到的报错信息都属于情景记忆的范畴。我第一次做这层记忆时走的捷径是直接把历史消息的原文向量化后存进向量库。后来发现效果很差——对话太口语化了“嗯嗯”“好的”“那你再试试”这种噪声直接向量化后召回出来的内容既占Token又没信息量。所以我现在处理情景记忆的方式是“二次加工后再入库”每次会话结束后单独跑一次轻量的摘要模型把这一轮对话里的关键事件抽出来格式尽量固定比如“时间、人物、问题、处理结果、遗留事项”然后再向量化入库。这样检索时召回的是一段结构化的有效信息而不是原始聊天流水账。2.3 语义记忆关于用户的长期事实与画像语义记忆是关于用户的基本事实和偏好比如“用户是前端工程师工作语言是JavaScript”“用户偏好每周五收到报告”“用户公司规模50人左右”。这类信息相对稳定不会每轮对话都变。这一层我强烈建议用结构化的键值存储或关系型数据库而不是塞向量库。原因很简单画像信息需要精确读取、精确更新。向量检索是“语义相近”可用户年龄、公司名、偏好种类这些字段根本不需要模糊匹配精确命中就好。而且画像信息的更新频率不高改动一个字段直接UPDATE就行完全没必要走检索引擎的流程。每次对话开始时把这部分压缩成简短的几行文字注入系统提示词就能让大模型“心里有数”。2.4 程序记忆记住“你怎么做事”程序记忆是很多人会忽略的一层。它记住的不是“用户是谁”而是“用户习惯怎么做”更偏行为和偏好模式。比如用户习惯先给结论再给细节、用户希望遇到未知问题时先告知再执行这些都是程序记忆的内容。我实际是把这层跟语义记忆放在一起管理的但从设计上分开来看有好处语义记忆是相对静态的“事实”程序记忆是随行为积累的“模式”。比如通过分析用户多次对话发现他每次都要求“先给代码再解释”那我就会把“先Code后Explain”写入程序记忆下一次自动按这个模式输出。3. 存储与检索选型记忆工程的地基工程分层完成之后最纠结的问题就是这四层记忆到底存哪、怎么查。这一章我说一下我最终采用的方案以及选型时反复权衡过的问题。3.1 多大的记忆该用什么存储我见过不少团队上来就上Milvus或者Qdrant其实没必要。存储选型先看数据规模、写入频率和查询模式。记忆层数据特点推荐存储理由工作记忆临时、读写频繁、体积受窗口限制内存态上下文对象 / Redis低延迟、会话结束即可释放情景记忆每次会话产生若干条增长快向量数据库 对象存储需要语义召回历史记录不宜丢语义记忆字段固定、量小、精确读改关系型 / KVPostgreSQL / MySQL / Redis精确匹配、事务更新、结构稳定程序记忆量小、更新慢KV或配置文件低频写入读取放在prompt组装时以我目前的项目规模其实就是一个PostgreSQL存用户画像和程序记忆一个向量库存情景记忆Redis存会话中的临时状态。这个组合便宜、运维轻、出问题好排查。3.2 向量检索的质量控制向量库能解决“语义相近”的召回问题但也是个容易翻车的环节。印象最深的一次用户说“这个Bug我昨天提过了”我把这句话向量化去库里匹配召回的却是别人的问题单记录因为“Bug”和“问题单”在向量空间上太近了。后来我调整了三件事入库前做实体抽取按“实体行为结果”的格式改写文本把关键词明确化检索时对召回结果做得分过滤通常会设置一个相似度得分下限太低的直接丢弃结合关键词命中做加权混合让向量召回和字面命中互相兜底这里有个很朴素的判断标准向量召回用的相似度得分在不同模型、不同文本风格下分布差异很大。不要一上来就定死一个全局阈值先跑一批真实数据看看得分分布再决定阈值放哪里。我见过拿0.85做硬阈值的项目结果召回率不到三成等于记忆库白建了。3.3 混合检索与记忆注入策略真正让记忆“用得上”的是把检索回来的一段段记忆在合适的位置注入给大模型。我现在采用的注入策略分三步对话一开始查询语义记忆和程序记忆生成一段用户画像摘要放进系统提示词让Agent知道“对面是谁、习惯什么风格”每轮收到用户消息后用当前消息和已有上下文拼一个检索query去情景记忆里召回最相关的历史片段召回的片段按“当前相关度时间新鲜度来源可信度”排序只取排名靠前的几条拼进上下文严格控制注入量分步注入听起来简单但实际调参的时候很磨人。最大的问题是“注入多少”。我一开始太想多给信息把Top10的历史片段全塞进去结果模型反而被无关记忆干扰回复质量严重下降。后来把Top10砍到Top3质量明显回升。少即是多在记忆注入这里特别成立。4. 在LangGraph里落地记忆改造一条可执行的实现路径前面讲了思路这一章说说实际代码落地。有不少人在热搜里问“langgraph开发ai agent实践”正好我目前的主力编排框架就是LangGraph拿它做记忆改造很顺手。4.1 用Checkpoint机制接管会话级记忆LangGraph自带一个Checkpoint机制可以理解成给图执行过程拍快照。每一次节点执行结束它会记录当前状态下次执行时可以从快照恢复。这天然就是工作记忆的持久化方案。我一般会把用户ID作为thread_id维度来区分各自的会话状态这样不同用户的记忆互不串线。示例代码大概长这样from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver # 定义Agent状态结构 class AgentState(TypedDict): messages: list user_profile: dict memory_hits: list # 用MemorySaver作为checkpoint存储 # 生产环境建议换成Redis/PostgreSQL等持久化实现 checkpointer MemorySaver() graph StateGraph(AgentState) # ... 添加节点和边 ... app graph.compile(checkpointercheckpointer) # 执行时把thread_id设为用户唯一ID config {configurable: {thread_id: user_12345}} result app.invoke({messages: [{role: user, content: 我上次问的部署问题还有后续吗}]}, configconfig)有了checkpointLangGraph在执行下一轮时会自动把之前的状态带出来Agent就“记得”上一轮聊到哪了。注意MemorySaver是内存态存储进程重启就丢生产环境建议换Redis或者PostgreSQL实现的持久化checkpointer这是我踩过坑之后才换的。4.2 自建Memory Manager节点处理长期记忆Checkpoint解决的是“这一场对话接上”但跨会话的长期记忆还需要单独处理。我的做法是在图里增加一个Memory Manager节点专门负责长期记忆的读写。整体流程是入口节点从用户ID读取画像注入“用户是谁”的信息记忆召回节点用当前问题去向量库召回相关情景记忆主对话节点把画像摘要、召回记忆、当前消息一起发给大模型生成回复记忆写入节点对话结束后抽取关键信息异步写回向量库和画像表这里我给一段简化版的自定义记忆节点示例def memory_read_node(state: AgentState) - AgentState: user_id state[user_id] # 读取结构化画像 profile profile_store.get(user_id) # 用当前消息召回相关历史记忆 hits memory_index.search(state[messages][-1].content, top_k3) memory_hits [h for h in hits if h.score SIMILARITY_THRESHOLD] return { user_profile: profile, memory_hits: memory_hits, } def memory_write_node(state: AgentState) - AgentState: user_id state[user_id] # 抽取关键信息实体、偏好、待办事项等 extracted extract_memory_candidates( state[messages] [state[response]], user_iduser_id, ) # 过滤后再写入避免垃圾信息入库 for candidate in extracted: if check_relevance(candidate): memory_index.upsert(user_id, candidate) return {}把这样的读写节点挂进StateGraphLong-Term记忆就跟主对话流程解耦了。写回操作我甚至会做成异步任务避免阻塞主链路。4.3 记忆改写与写回别把原始记录直接灌给大模型刚开始写这个模块时我最偷懒的做法是把旧对话原文倒给模型。后来很快发现几个问题一段真实客服对话可能有大量口语噪声喂给模型既费Token又干扰判断用户的一句话里可能混了多个事实直接丢进对话上下文和当前任务并无关多次对话后原始记录越来越大清洗成本越来越高所以我现在的写回逻辑是不存原文存“改写后的记忆条目”。比如用户说“我上次用Python写的爬虫一直被封IP”改写后入库的是“用户独立开发懂Python曾遇到爬虫IP封锁问题未透露是否解决”。这样一个条目既干净又包含了可复用的用户信息。改写这一步我单独串了一个小模型调用虽然多花了一点推理时间但换来的是后面每次检索时更准的召回和更低的上线成本划算。5. 遗忘比记住更难更新策略与用户掌控很多人做记忆功能只盯着“怎么记住”忽略了“怎么更新、怎么忘掉”。结果时间一长记忆库里堆了一堆过期甚至冲突的信息。这一章聊聊我在这上面吃的亏和最后的处理策略。5.1 记忆冲突用户改主意了怎么处理记忆冲突是我早期忽视的一个问题。用户上个月说“我比较喜欢邮箱沟通”这个月突然说“以后直接发飞书就行”。如果不处理Agent很可能在用户刚改完偏好之后仍然把邮箱当成首选联系方式。我现在的策略是给画像字段加“版本”概念每一次修改直接覆盖式更新同时保留最近更新时间戳。如果新信息与旧信息矛盾以时间戳更新的那条为准。对于情景记忆层面的冲突我会用“显式指令优先”原则用户明确说“以后不要这样”的指令最高优先级直接覆盖对应旧记忆。5.2 遗忘机制决定记忆何时失效遗忘听起来是坏事但记忆系统里没有遗忘系统迟早被自己压垮。我总结出三类需要遗忘的情况时间衰减情景记忆的价值随时间降低“上周的临时问题”大概率这周已不重要相关度评分里加时间衰减系数就好容量上限给每个用户的记忆条数设上限超过后按“重要性评分时间衰减”淘汰最不重要的条目负面反馈用户明确说“这个信息不需要了”时直接标记删除重要性评分我目前是用一套简单的加权公式做的用户显式强调的次数、信息是否涉及交易决策、是否影响Agent后续行为这几项给不同权重。本质就是让“影响后续行为的记忆”优先保留。5.3 用户掌控与隐私边界把记忆做成“用户不可控的黑盒”是很快会翻车的。用户有权知道他记住了什么、不想被记的内容能不能删这既是产品体验问题也是合规底线问题。我在产品里加了两个入口一个是“记忆管理”面板用户随时可以查看系统记住了哪些画像信息和历史事件另一个是“清除记忆”按钮一键删除该用户全部记忆数据。实测发现平台上线了这个入口之后用户对AI助手的信任感明显提升主动用的比例也很高。这里多说一句写入记忆前最好做一次敏感信息过滤。涉及身份信息、账号密钥、资金数据这些能不能入库、能不能用于召回要在架构层面就拦住别等出了投诉再补。6. 实测中反复踩过的坑与调优经验最后一个部分集中写我在落地过程中翻过车、但最后找到解法的问题。如果能让你少走几次弯路这篇就值了。6.1 记忆污染错误记忆如何被逐步固化记忆污染是我吃过最大的亏。现象是Agent某次从用户话里抽取了一个错误信息比如把“不是腾讯云”听成了“用的是腾讯云”写入了画像表。后面每次对话这个错误画像都会作为上下文注入Agent越来越坚定地认为用户在用腾讯云还会主动给出相关建议。用户每次纠正一次系统才勉强改一次下一轮又可能抽错。解法是两板斧。第一抽取记忆候选信息时要求大模型输出置信度低置信度的信息一律不自动写入转为待确认状态等用户明确确认后再入库。第二用户纠正Agent输出时所有涉及该话题的记忆条目全部进入复核流程而不是只改当前这一条。6.2 检索召回不准相似不等于相关向量检索的“语义相似”不等于“当前任务相关”这是绕不过去的坎。比如用户问“你们怎么计费的”召回出来的是三个月前同样问过计费的客服对话记录看似相关其实里面没有任何新增信息还占上下文。按照我现在的调法会从两个方向同时收口一是入库时做“信息增量过滤”凡是没有新增实体、没有决策事实、没有明确偏好的记忆候选直接丢从源头减少低质条目二是召回后做一次重排用一个轻量分类器或规则判断“这一条对当前问题是否构成信息增量”没增量的就不注入。重排逻辑很简单但把召回的噪声砍掉了一大半。6.3 上下文膨胀与成本失控记忆注入不加节制最直接的后果是上下文越来越长推理成本指数上涨响应速度也肉眼可见地变慢。我之前有一版上线后用户的长期记忆条目攒到几百条每次对话都要注入几十条历史单轮Token消耗比没记忆时翻了四五倍。现在的控制策略是单轮最多注入3条情景记忆、1段画像摘要、0到2条程序记忆如果一条记忆跟当前问题的相关性分数不达阈值宁可空着也不硬塞。省下来的成本拿来做一个“记忆预热”流程用户进入对话前先给出几个预设问题判断他大概要聊什么再针对性召回记忆效果比无差别全量召回好得多。6.4 一条简单的记忆能力评测方法记忆模块上线后最难的是怎么验证“效果真的变好了”。我后来整理了一套很轻量的评测脚本分享给你们参考准备一段统一的“用户设定”包括姓名、职业、几条偏好模拟3到5轮对话在对话里隐式或显式地透露这些信息结束对话启动新会话用10个问题测试Agent能否准确回忆出刚才的信息观察这10个问题的答案准确率以及平均每轮注入的Token量我自己的目标线是核心画像回忆准确率不低于90%关键情景事件回忆准确率不低于70%单轮记忆注入Token控制在总Token的30%以内。达不到这两条就回头调抽取逻辑和检索阈值。这个评测方法成本低、可重复也方便你在改一版之后快速对比效果。我在这些项目里反复折腾下来最大的体会是记忆不是“加一个向量库”那么简单的事它是一个需要跟Agent推理流程深度耦合的子系统。先分好层再定存储最后用评测去迭代调优这条路目前走下来最稳。如果你想给Agent做记忆增强我建议也按照这个顺序来别跳过前面的分层和选型直接写代码否则后面大概率要返工。
返回列表