尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

大模型长期记忆增强:原理、架构与最小实现

大模型长期记忆增强:原理、架构与最小实现 如果你正在开发基于大模型的应用大概率已经撞到过这样一堵墙用户明确说过“以后不要用专业术语回复我”下一次提问时模型照样抛出一堆术语用户上次提到自己是项目负责人这次继续追问时模型表现得像是第一次见面。原因不是模型不够聪明而是大模型本身没有真正意义上的长期记忆。于是“Augmenting Long-Term Memory”成了一个绕不开的命题。这里的 Augment 不是简单地把上下文窗口从 128K 扩到 1M而是用一套外部系统让模型能够跨会话、跨任务地记住并复用关键信息。这篇文章会从原理、架构、最小实现到工程排错完整拆解长期记忆增强是怎么做的、适合哪些场景、有哪些坑。如果你正在做聊天机器人、AI Agent、知识库问答或者想让模型记住用户偏好和项目上下文这篇文章值得你读完并收藏。我们会用一个可运行的 Python 示例演示如何用向量数据库和 Embedding 模型给大模型外接一套长期记忆。1. 大模型长期记忆为什么“窗口变长”没有解决根本问题很多人的第一反应是既然模型记不住那就把上下文一直塞给模型。上下文窗口越来越大OpenAI、Anthropic、Google 都在卷长文本好像只要窗口够大长期记忆就解决了。但从工程角度看这是一个明显的误区。第一成本按 Token 线性增长。用户每轮对话都要把历史全部提交给模型几千轮之后即使模型能处理账单也不答应。对于需要长期运行的 Agent 或客服系统这个成本是致命的。第二注意力会被稀释。大模型面对超长上下文时并不一定能够准确找到最关键的信息。信息越多模型越容易把注意力分散到无关内容上回答质量反而下降。这就是所谓的“迷失在中间”现象。第三模型没有对信息的筛选和遗忘能力。长期记忆不应该是什么都记而应该是记重要的、忘掉无关的。一个健康的记忆系统需要有能力对低价值记忆进行衰减和清理这一点单纯靠窗口无法实现。第四跨会话信息完全丢失。上下文窗口再大一次会话结束后就清零。用户换一个会话接着问模型依然什么都想不起来。这不是窗口长度问题而是记忆持久化问题。所以真正需要的是把“记忆”从模型内部剥离出来放到一个可管理的外部系统中。模型在回答问题时从外部系统检索出“此刻需要知道的记忆片段”组装进上下文或提示词里。这就是长期记忆增强的核心思路。2. 长期记忆增强的核心概念与原理要理解长期记忆增强先要分清几个概念。2.1 短期记忆、工作记忆与长期记忆在大模型语境中短期记忆当前请求内可见的上下文也就是 Prompt 里塞进去的内容。用户上一句话还在上下文中模型自然能引用。工作记忆模型处理当前任务时临时需要保持的信息类似于你心算时临时记的两个数字。对应到系统里可以理解为一轮任务中组合出来的完整上下文。长期记忆跨会话、跨任务存储的外部信息。它不在模型内部而是在数据库、索引或文件中需要时再取回。用人类来类比短期记忆是你的临场发挥长期记忆是你写在笔记本上的内容。问题在于模型没有“翻笔记本”的能力我们要做的就是帮它把这个动作做出来。2.2 RAG 与长期记忆的关系很多人已经接触过 RAGRetrieval-Augmented Generation检索增强生成。RAG 的核心是先从外部文档中检索相关内容再让模型基于检索结果生成回答。从机制看长期记忆增强和 RAG 非常像都是“检索 生成”。但两者侧重点不同。RAG 通常针对静态知识库文档内容稳定、更新不频繁。长期记忆更强调动态写入、高频更新、结构化管理。你的记忆系统不能像知识库那样一个月更新一次而是每一轮对话都可能产生新记忆也要删除过时记忆。可以把长期记忆系统理解为一个“活的 RAG”基础能力是向量检索额外增加了记忆写入、去重合并、优先级排序、衰减清理等机制。2.3 长期记忆系统的核心组件一个典型的长期记忆增强系统包含以下环节环节作用典型实现记忆获取从对话、任务状态、用户行为中提取值得记录的信息LLM 抽取、规则模板、日志分析记忆存储保存记忆内容支持快速检索向量数据库、关系数据库、键值存储记忆索引将自然语言转化为向量建立索引Embedding 模型、倒排索引、混合索引记忆检索根据当前输入找到相关记忆相似度检索、布尔过滤、重排记忆融合把检索结果合并进 Prompt 或上下文Prompt 模板、上下文组装器记忆更新合并重复记忆、移除过期记忆、调整权重写入时去重、定期清理、遗忘算法这些环节缺一不可。很多人只做了“存储 检索”忽略了“获取”和“更新”最后发现系统存了一堆垃圾或者满了之后性能越来越差。3. 长期记忆增强系统的整体架构设计在看代码之前先建立整体架构观。长期记忆系统不需要复杂到微服务但逻辑上可以分成几层。3.1 分层架构交互层接收用户输入调用大模型展示回答。理解层判断用户输入中哪些信息值得记住比如用户偏好、任务状态、关键约束。记忆管理层负责写入、更新、合并、清理记忆是长期记忆的“中枢”。检索层根据当前请求从记忆存储中检索 Top-K 条最相关内容。生成层将检索到的记忆和当前输入组装成 Prompt交给大模型生成回答。3.2 记忆类型实际项目中长期记忆通常不止一种记忆类型内容示例存储方式语义记忆用户偏好、领域术语、产品规则向量检索情景记忆某次会话的关键讨论、决定向量 时间戳任务状态当前进行到哪个步骤、下一步做什么结构化 KV实体关系用户与项目、成员之间的关系知识图谱在最小实现阶段我们重点处理语义记忆和情景记忆因为它们可以直接转化成文本片段存入向量数据库。3.3 数据流示例一次带长期记忆的对话流程如下用户输入“我是项目负责人后续回复请简洁”。理解层识别到“用户角色 项目负责人”和“回复风格偏好 简洁”。记忆管理层将这两条记忆写入向量数据库。用户再次提问“我要准备项目周报”。检索层在向量数据库中检索到“用户角色 项目负责人”“回复风格偏好 简洁”。生成层将记忆 当前问题一起发给模型 “用户是项目负责人偏好简洁回复请结合这个背景回答。”模型给出的回答更贴合用户身份和偏好。这个流程看起来简单难点在“理解层该提取什么”“检索不准怎么办”“记忆冲突怎么解决”。下面从最小实现开始逐步解决这些问题。4. 环境准备与基础依赖开始写代码前先准备环境。本文示例使用 Python因为生态最成熟。建议环境Python 3.9 或更高版本具体版本以你本机为准一个可用的 Embedding 模型例如sentence-transformers提供的本地模型一个轻量向量存储例如 FAISS 或 ChromaDB一个 LLM API 或本地模型接口用于最终生成回答安装核心依赖pip install sentence-transformers pip install faiss-cpu pip install openai如果希望用更完整的 RAG 框架可以额外安装 LangChain 或 LlamaIndex但我建议先自己实现一遍最小系统理解原理后再引入框架pip install langchain pip install langchain-openai pip install chromadb这里特别说明版本号在不同时期变化很快不要死记某个固定版本。安装时使用最新稳定版本即可。如果遇到依赖冲突优先在虚拟环境中解决。如果你使用 OpenAI 接口需要提前配置 API Key。不要把 Key 硬编码进代码建议放在环境变量里export OPENAI_API_KEYsk-xxxxxxxxxxxxxxxx如果是国内可用的合规模型服务也同理换成对应的 Base URL 和 Key。安全提醒API Key 只保存在服务端环境变量中不要提交到 Git不要放在前端页面。5. 最小示例用向量数据库为 LLM 增加长期记忆这一节我们不引入复杂框架直接用 FAISS sentence-transformers 实现一个最小可运行的长期记忆存储。核心流程把一条记忆文本转成向量存入 FAISS。当用户提问时把问题也转成向量。在 FAISS 中搜索最相关的记忆。把记忆片段拼入 Prompt供 LLM 使用。5.1 完整代码新建文件memory_demo.pyimport os import numpy as np import faiss from sentence_transformers import SentenceTransformer class MemoryStore: 一个极简的长期记忆存储类 使用 sentence-transformers 生成向量使用 FAISS 做相似度检索。 def __init__(self, model_nameall-MiniLM-L6-v2): # 加载本地 embedding 模型 self.model SentenceTransformer(model_name) self.index None self.memories [] # 原始文本列表和 index 的向量顺序对应 def _build_index(self, vectors): dim vectors.shape[1] index faiss.IndexFlatL2(dim) index.add(vectors) return index def add_memory(self, text): 写入一条记忆 vector self.model.encode([text], normalize_embeddingsTrue) vector np.array(vector, dtypefloat32) if self.index is None: self.index self._build_index(vector) else: self.index.add(vector) self.memories.append(text) def retrieve(self, query, top_k3): 根据当前问题检索相关记忆 if self.index is None: return [] query_vector self.model.encode([query], normalize_embeddingsTrue) query_vector np.array(query_vector, dtypefloat32) distances, indices self.index.search(query_vector, top_k) # FAISS 返回的是 numpy 索引转成 list 再取文本 return [self.memories[int(i)] for i in indices[0]] def build_prompt(user_input, memories): 把检索到的记忆拼接到 prompt 中 memory_block \n.join([f- {m} for m in memories]) prompt f 以下是从用户历史交互中检索到的长期记忆 {memory_block} 当前用户输入 {user_input} 请结合长期记忆回答用户的问题并且保持简洁。 return prompt if __name__ __main__: store MemoryStore() # 模拟历史交互中产生的记忆 store.add_memory(用户是某电商项目的后端负责人。) store.add_memory(用户偏好使用中文回答且回复要简洁。) store.add_memory(用户最近在推进订单模块的接口升级。) query 我现在应该优先汇报哪部分工作 memories store.retrieve(query) print(检索到的记忆) for m in memories: print( -, m) prompt build_prompt(query, memories) print(\n生成的 Prompt) print(prompt)5.2 代码解释MemoryStore类是这个示例的核心。add_memory方法把文本编码成向量存入 FAISS 索引retrieve方法把查询文本编码为向量然后在索引中查找最近的 Top-K 条记忆。这里有一个关键点normalize_embeddingsTrue。归一化可以让 L2 距离近似等于余弦相似度在统一向量空间中更稳定。实际项目中新增数据时不需要重建整个索引FAISS 的IndexFlatL2支持增量添加。build_prompt的作用是把检索到的记忆“注入”到模型输入中。长期记忆本身不改变模型权重它改变的是模型每次回答时的上下文这是最务实的增强方式。5.3 运行与验证在终端执行python memory_demo.py预期输出大致是检索到的记忆 - 用户最近在推进订单模块的接口升级。 - 用户是某电商项目的后端负责人。 - 用户偏好使用中文回答且回复要简洁。 生成的 Prompt 以下是从用户历史交互中检索到的长期记忆 - 用户最近在推进订单模块的接口升级。 ...如果检索结果和问题完全不相关先检查 memory 写入的文本是否粒度太大。一条记忆只保留一件事检索效果最好。6. 对话级记忆管理把历史对话变成可检索的长期记忆上面只是把已经写好的文本存入向量库。真实项目中记忆大多来自对话内容。我们需要在对话过程中自动抽取出值得记住的信息。一个可行的策略是每一轮对话结束后把“用户说了什么、回复了什么”传给一个抽取模块。抽取模块用规则或 LLM 提取出关键记忆。将记忆写入向量数据库。下一轮对话开始前根据当前问题检索并注入。为了避免把所有历史都塞进去我们可以为对话记录增加摘要层。下面是一个更完整但不过度复杂的对话级记忆管理器。6.1 对话级记忆管理代码创建一个chat_memory.py文件import json import time from typing import List, Dict from memory_demo import MemoryStore class ChatMemory: def __init__(self, memory_store: MemoryStore): self.store memory_store self.short_term: List[Dict[str, str]] [] self.last_memory_snapshot: List[str] [] def record_turn(self, user_input: str, assistant_reply: str) - None: 记录一轮对话。这里会调用一个记忆抽取函数你可以用 LLM 替换。 演示中我们使用简单规则把用户输入中带有“我是/我喜欢/我需要/他的/她的”等 关键信息的句子作为记忆近似提取。 self.short_term.append({user: user_input, assistant: assistant_reply}) # 实际场景更推荐调用 LLM 抽取记忆规则抽取只作为兜底 candidate self._extract_memory_by_rule(user_input) if candidate and candidate not in self.last_memory_snapshot: self.store.add_memory(f用户信息{candidate}) self.last_memory_snapshot.append(candidate) def _extract_memory_by_rule(self, text: str) - str: markers [我是, 我喜欢, 我不喜欢, 我负责, 我的角色是, 偏好, 必须, 不能用] for marker in markers: if marker in text: return text.strip() return def get_relevant_memories(self, query: str, top_k5) - List[str]: return self.store.retrieve(query, top_ktop_k) def build_context_with_memory(self, query: str) - str: memories self.get_relevant_memories(query) memory_block \n.join([f- {m} for m in memories]) return ( 以下是已知的用户长期记忆\n f{memory_block}\n\n 如果记忆和当前问题有关请优先参考如果无关忽略它们。 ) if __name__ __main__: store MemoryStore() chat ChatMemory(store) # 模拟两轮对话 chat.record_turn(我是电商项目的后端负责人回复尽量简洁。, 好的已记录。) chat.record_turn(我最近在推进订单接口升级。, 收到我会记住的。) context chat.build_context_with_memory(今天要写周报我该汇报什么) print(context)这段代码里record_turn在每轮对话后调用。_extract_memory_by_rule是一个非常粗糙的抽取器只处理固定关键词。实际上更好的做法是让 LLM 来做抽取。比如写一个抽取 Prompt请从下面的对话中抽取需要长期记忆的信息。 要求 - 只抽取用户明确表达的偏好、身份、项目状态、约束条件。 - 不抽取寒暄和无关内容。 - 输出为 JSON 列表每项一个 short 和 detail 字段。 对话 用户我是电商项目的后端负责人。 助手好的已记录。 输出然后把 LLM 返回的 JSON 写入记忆库。这样做效果更好但会增加一次 LLM 调用和成本。建议在记忆价值较高的场景下使用。6.2 为什么不能把全部历史存储在向量库向量库不是垃圾桶。如果你把每一轮对话都原封不动存进去很快会面临几个问题检索噪声变高大量历史闲聊会被检索出来干扰回答。内存和索引膨胀向量库越来越大检索性能下降。记忆冲突旧的偏好和新的偏好并存模型不知道该听哪个。所以记忆系统必须包含摘要、去重和更新机制。真正的长期记忆是“高质量的浓缩信息”而不是“历史全量日志”。7. Agent 场景下的长期记忆增强方案如果你开发的是 AI Agent长期记忆的需求会更强烈。Agent 需要记住用户的长期目标、当前任务的中间状态、已经做过的动作、用户的纠偏反馈。我们可以把 Agent 记忆分成三类语义记忆用户的领域、偏好、常量信息。情景记忆某个具体会话中发生了什么、推进到什么阶段。过程记忆解决某类任务时的步骤、工具使用习惯。在 Agent 中记忆的写入不能只在对话结束。Agent 的每一步动作都有可能产生新记忆。比如用户说“之后统称我的产品为 X”。Agent 执行工具后得到“数据库连接串是 test-db”。Agent 发现用户不喜欢冗长的回复。Agent 完成了某个子任务需要把结果写入记忆供后续步骤使用。下面用一个简化版 AgentMemory 类说明7.1 Agent 记忆管理器代码class AgentMemory: def __init__(self): self.semantic_memory {} # 使用字典保存用户偏好等确定性信息 self.episodic_memory [] # 保存关键事件列表 self.procedure_memory {} # 保存任务状态与步骤 def remember_semantic(self, key: str, value: str): 写入语义记忆。 例如 user.name张三user.lang中文assistant.style简洁 self.semantic_memory[key] value def remember_event(self, event: str, meta: dict None): 写入情景记忆。 例如“用户要求使用简洁回复风格”或“完成了订单模块接口设计”。 self.episodic_memory.append({ time: time.time(), content: event, meta: meta or {} }) def update_task_state(self, task: str, state: str, step: str): self.procedure_memory[task] { state: state, step: step } def build_agent_prompt(self, user_input: str) - str: semantic_block \n.join( [f{k}: {v} for k, v in self.semantic_memory.items()] ) event_block \n.join( [f- {e[content]} for e in self.episodic_memory[-5:]] ) task_block \n.join( [f- {k}: {v[state]}当前步骤: {v[step]} for k, v in self.procedure_memory.items()] ) prompt f 你是长期记忆型 Agent以下是你已经掌握的信息 【语义记忆】 {semantic_block} 【最近情景记忆】 {event_block} 【任务状态】 {task_block} 用户当前输入{user_input} 请结合记忆完成请求不确定的信息可以继续追问。 return prompt这个类把语义记忆、情景记忆、任务状态分开管理。语义记忆用字典适合键值型信息情景记忆用列表任务状态用结构化字典。在更复杂的系统中情景记忆还会同步写入向量库用于模糊检索。Agent 的记忆管理关键点在于“什么时候写”。我的建议是用户明确表达的偏好立即写。Agent 完成关键动作后更新任务状态。收到用户纠偏后覆盖旧偏好。对敏感信息写入前需确认。8. 运行结果与效果验证上一小节的代码可以直接运行输出类似你是长期记忆型 Agent以下是你已经掌握的信息 【语义记忆】 user.name: 张三 assistant.style: 简洁 【最近情景记忆】 - 用户要求使用简洁回复风格 - 完成了订单模块接口设计 【任务状态】 - 周报生成: in_progress当前步骤: 收集本周完成项 用户当前输入准备生成周报先给我一个大纲。如果模型收到这个 Prompt它就知道当前周报任务处于“收集本周完成项”阶段会给出更贴合上下文的大纲。8.1 如何评估长期记忆增强的效果长期记忆系统不像普通 API 一样只有“通/不通”需要一套可观测的评估指标。推荐从四个维度出发维度指标说明记忆写入率有效记忆占总写入片段比例太低说明抽取过度或规则太粗糙检索命中率目标记忆在 Top-K 中出现的比例衡量索引与检索质量回答引用率最终回答是否有效使用了记忆用人工或 LLM 打分遗忘率更新偏好后旧信息失效情况衡量记忆更新质量常见的评估方式是构造一组“记忆问答对”。比如写入记忆用户偏好简洁回复。提问用户喜欢的回复风格是什么期望答案简洁。然后批量运行系统统计命中率。这个测试集不需要很大可以先从 50 条业务场景开始。8.2 失败时先看哪里如果最终回答没有用上记忆按顺序排查检索阶段是否返回了正确记忆手动打印retrieve结果。Prompt 里记忆片段是否被模型看到有些模型系统提示中内容过长会被截断。记忆是否在写入前就被丢弃检查抽取模块的输出。是否有冲突记忆干扰打印近 N 条相似记忆人工判断。9. 常见问题与排查方法长期记忆增强系统常见的坑比较集中下面整理成一张排查表问题现象可能原因排查方式解决方案检索到无关记忆记忆分块过大一条记忆包含多件事打印检索结果检查记忆文本粒度每条记忆控制在 1-2 句话内模型回答时没使用记忆Prompt 中记忆位置太靠后或太长观察生成日志看最终发给模型的 Prompt把记忆放在用户输入之前并控制数量记忆写入过多抽取规则太宽每个句子都写入记录写入日志统计入库量让 LLM 抽取只保留高价值信息新偏好覆盖不了旧偏好写入时没有去重和冲突处理检查记忆库中是否存在两个相反片段写入前按用户和主题查重更新旧记忆向量库体积增长过快没有清理和衰减机制监控索引大小定期合并相似记忆删除低价值记录Embedding 效果差模型与业务领域不匹配抽样检查相似检索结果换更大的 Embedding 模型或微调重启后记忆丢失向量索引只存在内存中检查是否保存了索引文件使用 FAISS write_index 或改用持久化存储这里特别要提一个容易被忽略的问题Embedding 模型的一致性。如果你在写入记忆时用模型 A检索时用模型 B那么向量分布完全不同检索效果会非常差。生产环境中Embedding 模型版本一旦确定不要随意更换。如果需要升级必须重建全部索引。10. 长期记忆增强的最佳实践与工程建议这部分是长期记忆系统能否从 Demo 走向生产的关键。很多项目 Demo 跑得通一上生产就崩基本都是踩了下面这些点。10.1 记忆分块策略长期记忆的最小单位应当是“一条事实”而不是“一段对话”。例如“用户是电商项目后端负责人”是一条记忆“用户说了很多项目相关的话”不是一条记忆。分块的大致经验是语义记忆一句话包含主体、关系、值。情景记忆一段话包含事件、时间、影响。任务状态结构化 JSON而不是自然语言。分块过大会导致检索噪声高分块过小会导致信息碎片化难以组装。建议从“一条记忆 20-50 个 Token”起步再根据业务调优。10.2 记忆写入必须经过审核不是所有对话内容都值得写入长期记忆。我建议至少经过两个过滤规则过滤去除重复、过短、无意义内容。价值过滤只有涉及用户偏好、身份、项目状态、约束条件时才写入。如果是隐私敏感信息比如手机号、身份证、财务数据默认不应该写入记忆库。即使需要也要在用户明确授权后进行并且允许用户主动删除。10.3 记忆更新与清理长期记忆不是只增不减。一个健康的记忆系统需要生命周期管理新增从对话中抽取高价值信息。合并多轮提到的同一实体合并成最新状态。衰减长期未被引用的记忆降低权重。删除用户明确要求遗忘或确认过时的信息。如果刚开始不想做得太复杂可以先用一个“最后访问时间”字段每日批量清理超过 90 天未被引用的记忆。后续再引入更复杂的遗忘算法。10.4 可观测性生产环境必须能看到“这一轮模型回答用了哪些记忆”。建议在每次生成时输出一份记忆使用日志{ query: 用户当前输入内容, used_memories: [mem_id_1, mem_id_2], retrieved_count: 5, response_token_count: 320 }这份日志对排查问题、评估效果、调整检索参数都非常关键。没有日志的长期记忆系统等于在黑盒里做优化。10.5 权限与访问控制如果系统面向多个用户记忆必须按用户隔离。严禁出现 A 用户检索到 B 用户记忆的情况。最简单的方式是在记忆表或向量索引上打上user_id标签检索和写入时都绑定当前用户。在多租户场景下不建议把所有人的记忆放在同一个向量索引里再靠过滤字段隔离。向量检索是全局近似检索过滤条件可能影响结果。更稳妥的做法是每个用户维护独立索引或者使用支持 tenant partition 的向量数据库。10.6 成本控制长期记忆系统会产生两类额外成本Embedding 调用成本每次写入和检索都要生成向量。LLM 抽取成本如果让 LLM 抽取记忆每一轮对话都会多一次模型调用。建议优先使用规则抽取做首版对高频用户或高价值场景再引入 LLM 抽取。检索时统一做向量化缓存避免同一句话反复调用 Embedding。11. 总结与后续学习方向长期记忆增强不是某一个库或模型能解决的它是一套包含记忆获取、存储、检索、融合、更新的系统工程。把记忆从模型内部搬到外部系统用“检索 上下文组装”的方式让模型每次都能看到关键历史是目前最务实、成本最低、迭代最快的方案。本文从概念、架构、最小实现到生产建议覆盖了你从零搭建一套长期记忆增强系统的主要环节。建议下一步可以按这个顺序实践先跑通第 5 节的最小向量记忆示例感受写入与检索。再接入自己的业务设计出适合你场景的记忆抽取规则。最后引入索引持久化、记忆生命周期和用户隔离推进到生产环境。如果你对 Agent 场景感兴趣可以继续深入研究知识图谱记忆把实体关系也纳入长期记忆系统记忆融合方面可以学习 ReAct 中的 memory scratchpad、反思式 Agent 的 self-memory 机制。长期记忆的下一步必然从“存得下、找得到”走向“记得准、该忘就忘”。
返回列表