☰
LLM长期记忆架构设计与向量检索实现指南
2026/10/20 23:24:01 网站建设 项目流程

最近在做 LLM 应用落地时,最常被问到的一个能力不是“能不能调用工具”,也不是“指令遵循得怎么样”,而是“它能不能记住我上次说过的话”。这个问题听起来简单,真正做起来却非常棘手。聊天记录一长,上下文窗口很快就撑不住了;超过窗口限制之后,前面的信息就像被剪断的录像带一样再也找不回来。这个问题的本质,是 LLM 缺少长期记忆。

近期在 Hacker News 上看到有人分享 Long Term Memory for LLM 的架构实验,结合自己的一些项目实践,我整理了一篇系统性的实操笔记。这篇文章会先讲清楚 LLM 长期记忆的核心概念和架构难点,再拆解常见的记忆设计模式,最后用 Python 实现一个可运行的“外部记忆层”,把“记忆”从对话框里搬出来,放到可持久化、可检索的存储中。

本文适合正在做 LLM 应用开发、Agent 搭建、知识库问答的开发者,也适合刚接触大模型应用、想理解“记忆机制”到底是怎么回事的初学者。读完你会掌握:

  • LLM 长期记忆的核心问题与边界
  • 短期记忆、长期记忆、外部记忆的区别
  • 四种主流记忆架构设计模式
  • 一个基于向量检索的长期记忆层完整实现
  • 生产环境中记忆模块的排错与最佳实践

1. 背景与核心概念

1.1 为什么 LLM 需要长期记忆

先来看一个最常见的场景。

你正在和 AI 助手讨论一个项目方案,前面已经确定了技术栈、模块划分、数据库选型,甚至约定了命名规范。聊了 20 轮之后,你随口问一句“那刚才说的用户表用软删除还是物理删除”,模型却回答得模棱两可,因为它已经记不清前面提到的数据库设计细节了。

这不是模型“变笨”了,而是模型的上下文窗口是有限的。目前主流的大模型上下文窗口虽然已经扩展到几十万 token,但依然存在两个制约:

  1. 成本问题:把整段历史对话全部塞进每次请求里,token 消耗会随对话轮数线性增长,长对话成本非常高。
  2. 注意力衰减问题:即便窗口放得下,模型对中间部分内容的注意力也会明显下降,历史信息容易被“稀释”。

所以,仅靠“把历史都拼进 prompt”不是一个可持续的解决方案。LLM 需要一种机制,把重要的历史信息沉淀下来,在需要时再快速、准确地取回,这就是长期记忆要解决的问题。

1.2 什么是长期记忆

这里先做几个概念区分,很多新手容易混在一起。

短期记忆(Short-term Memory):指当前会话中模型可以直接“看到”的信息,也就是上下文窗口内的内容。它的特点是读取快、无需额外处理,但受限于窗口长度,会话结束后往往就丢失了。

长期记忆(Long-term Memory):指跨会话、跨时间维度的信息留存。它通常不直接放进 prompt,而是存储在外部介质中,需要时通过检索机制取回相关片段,再注入当前上下文。

外部记忆(External Memory):从架构角度看,长期记忆往往以外部存储的形式存在,可以是向量数据库、关系型数据库、Redis、文件系统,甚至是一张结构化的 JSON 表。

用一个比喻来理解:短期记忆是办公桌上的便签,手边就能看到,但桌面空间有限;长期记忆是资料室的档案柜,容量大、可以持久保存,但需要一套编码和检索系统才能快速找到有用的文件。

1.3 长期记忆的难点在哪里

长期记忆看起来只是“多存一份数据再查回来”,实际上难点不少:

  • 记忆的取舍:不是所有对话都值得记,如何区分“重要信息”和“一次性寒暄”?
  • 记忆的结构化:对话是自然语言,但很多记忆其实是结构化的,例如用户偏好、项目约束、实体关系,怎么统一表达?
  • 记忆的时效性:一条记忆可能有时效,比如“本周三的会议待办”,过期了就不应该再被检索出来。
  • 检索的准确性:从大量历史记忆中找到当前问题真正需要的那几条,对检索策略要求很高。
  • 记忆的更新与遗忘:用户修正了之前说过的话,旧记忆如何失效?需要一套写覆盖或冲突消解机制。

理解这些难点之后,再看各种架构实验,思路就会清晰很多:所有长期记忆方案,本质上都是在“存储”和“检索”两端做设计取舍。

2. LLM 记忆架构的常见设计模式

近一年来,社区里出现了很多长期记忆相关的实验方案,比如 MemGPT、Generative Agents、Letta、Mem0,以及各类知识库问答系统。从架构上归纳,主流设计模式大致有四类。

2.1 纯上下文扩展模式

这是最朴素的方式,也就是把所有历史记录拼进 prompt。

系统提示词 + 最近对话历史 + 当前用户问题

优点是不需要额外模块,实现最简单。缺点是:

  • 成本随对话长度线性上升
  • 超过窗口后必须截断,截断策略很难做到“刚好去掉无关内容”
  • 超长上下文会稀释模型对关键信息的注意力

适合:短会话工具、一次性问答、模型能力演示。

2.2 摘要压缩模式

每次对话达到一定长度后,用一个摘要模型把历史对话压缩成几句话,然后用压缩后的摘要替代原始历史。

[摘要] 用户正在开发一个Python项目,确定了FastAPI+React技术栈... [最近对话] ...

优点是可以保留较长时间跨度内的核心信息,比直接截断优雅。缺点是摘要过程会丢失细节,且重复摘要会产生“摘要的摘要”,信息逐层衰减。

适合:需要跨轮次延续信息、但细节要求不高的场景。

2.3 向量检索记忆模式

这是目前最主流的长期记忆方案。核心思路是:

  1. 把记忆文本通过 embedding 模型转为向量。
  2. 向量存入向量数据库。
  3. 用户提问时,把问题也转为向量,在向量库中做相似度检索。
  4. 将检索结果注入 prompt,作为“记忆上下文”。
向量检索 + 原始用户问题 → prompt

优点是信息密度高、可扩展性强、支持跨会话检索,是知识库问答和 Agent 长期记忆的主流基础方案。缺点是需要额外的 embedding 模型和向量存储组件,检索质量直接受 embedding 模型影响。

2.4 混合分层记忆模式

更复杂的架构会把上面几种模式组合起来,形成分层记忆。

  • 第一层:最近几轮对话,直接放窗口。
  • 第二层:稍早的对话,做摘要压缩。
  • 第三层:历史中需要精确留存的事实,写入记忆库,需要时检索。
  • 第四层:长期稳定的用户画像、偏好,单独存储并定期更新。

这种分层架构是目前社区实验的主要方向。Hacker News 上关于 LLM long term memory architecture 的讨论,大多数也是围绕“如何组织这四层信息”展开的。分层的好处是兼顾成本、精度和时效,代价是系统复杂度明显上升。

3. 记忆架构关键组件拆解

不管采用哪种模式,一个可落地的长期记忆层通常包含四个关键组件:输入处理、记忆存储、检索模块、写入策略。

3.1 输入处理组件

输入处理负责决定“哪些信息值得进入记忆系统”。原始对话往往是口语化的,直接存储会浪费存储空间,也会降低后续检索质量。

常见做法:

  • 先做对话清洗,去除无意义的口头语、重复表达。
  • 再过滤敏感信息和无效内容。
  • 对长对话做信息抽取,只保留关键实体、偏好、决策结论。
  • 可以调用一次 LLM 对记忆内容做规范化重写。

3.2 记忆存储组件

存储层决定了记忆的容量和查询方式。目前有以下几种选择:

存储方案优点缺点适用场景
关系型数据库事务可靠,结构清晰不适合语义检索用户画像、结构化配置
Redis读速快,支持过期无语义检索能力短期记忆、临时会话
向量数据库支持语义相似度检索需要 embedding 与索引维护长期记忆核心存储
本地 JSON/文件最简单,无额外依赖查询能力弱小规模实验

在长期记忆实验中,向量数据库是检索记忆的主流存储方式,常见的有 Chroma、Milvus、Qdrant、Weaviate、pgvector 等。选型时要考虑数据量、部署难度、是否支持持久化、是否有成熟客户端。

3.3 检索模块

检索模块把当前问题映射到历史记忆中。最基础的检索方式是向量相似度检索,也就是把用户问题转为向量,在向量库中查询最相似的若干条记忆。

单纯向量检索的常见问题是“语义相近但实际无用”的干扰结果。一个更稳的做法是“向量召回 + 重排序”:

  1. 先用 embedding 相似度召回候选记忆,数量可以多一些,比如 20 条。
  2. 再用一个重排模型(reranker)或基于规则的过滤逻辑,从候选中筛选出最相关的 3-5 条。
  3. 最后按时间权重调整排序,兼顾“语义相关”和“信息时效”。

3.4 写入策略

写入策略决定了记忆系统如何新增、更新、遗忘。最简单的是“无条件写入”,每条对话都存进去,但这会让记忆库迅速膨胀,检索噪声越来越大。

推荐策略:

  • 阈值写入:对话轮次达到 N 轮后,批量提取这次对话中的关键信息写入记忆库。
  • 去重更新:写入前先检索是否有相似旧记忆,如果有则更新原记录,而不是新增一条。
  • 遗忘机制:为记忆增加时间戳和重要度评分,定期清理超过有效期的数据,或者将低重要度数据归档。

4. 环境准备与实验设计

下面进入实操环节。我们来实现一个最简但完整的长期记忆层,采用“向量检索 + 记忆库”模式,不依赖重型组件,方便你理解全链路。

4.1 实验环境

  • 操作系统:Windows 10 / macOS / Linux 均可
  • Python:3.10 或以上
  • 安装包:openai、chromadb、pandas
pip install openai chromadb pandas

如果你的环境没有配置 openai 库,请先安装。本文示例使用 OpenAI 兼容的 embedding 与 chat 接口,如果你的模型是本地部署,也支持 OpenAI 兼容协议,思路是一样的。

需要说明的是,各家依赖版本迭代较快,本文代码以常见版本为参考,重点演示设计思路。如果某个 API 在你的版本中不存在,请以官方文档为准调整。

4.2 项目结构

llm-memory-lab/ ├── memory/ │ ├── __init__.py │ ├── vector_memory.py │ ├── summarizer.py │ └── agent.py ├── data/ │ └── mem.db # Chroma 持久化目录 ├── demo.py └── requirements.txt

4.3 设计要点

我们要实现的记忆系统有三个模块:

  1. vector_memory.py:基于 Chroma 的记忆存取与检索。
  2. summarizer.py:使用 LLM 将对话压缩成结构化记忆。
  3. agent.py:把记忆检索结果与用户问题拼成 prompt,交给 LLM 生成回复。

实际运行流程:

用户问题 → 向量化 → 在记忆库中检索 top-k 条历史记忆 → 记忆 + 最近对话 + 用户问题 → LLM

5. 核心代码实现

5.1 向量记忆模块

创建memory/vector_memory.py,负责初始化向量数据库,并提供增、查两个核心方法。

# 文件路径:memory/vector_memory.py import chromadb from chromadb.config import Settings class VectorMemory: """ 基于 Chroma 的长期记忆存储与检索。 """ def __init__(self, persist_directory: str = "./data/mem_db"): self.client = chromadb.PersistentClient( path=persist_directory, settings=Settings(anonymized_telemetry=False), ) # collection 可以理解为一张记忆表 self.collection = self.client.get_or_create_collection( name="llm_long_term_memory", metadata={"hnsw:space": "cosine"}, ) def add_memory( self, memory_id: str, text: str, embedding: list, metadata: dict = None, ): """ 写入一条记忆。 """ self.collection.add( ids=[memory_id], documents=[text], embeddings=[embedding], metadatas=[metadata] if metadata else None, ) def search_memory( self, query_embedding: list, top_k: int = 5, where: dict = None, ): """ 根据 query 向量检索最相似的记忆。 """ result = self.collection.query( query_embeddings=[query_embedding], n_results=top_k, where=where, ) return result

这里有几个关键点:

  • PersistentClient会把数据持久化到本地目录,重启后记忆还在。
  • hnsw: space选择 cosine 余弦距离,更适合语义相似度检索。
  • documents保存记忆原文,embeddings保存向量,metadatas可以附加时间戳、来源、重要度等字段。

5.2 Embedding 与对话压缩模块

创建memory/summarizer.py,这个模块负责两件事:生成文本向量、把对话压缩成结构化记忆。请注意:不同模型的向量维度可能不同,使用前先确认你用的 embedding 模型维度与向量库索引维度一致。

# 文件路径:memory/summarizer.py from openai import OpenAI client = OpenAI() # 读取环境变量中的 API key def get_embedding(text: str, model: str = "text-embedding-3-small") -> list: """ 将文本转换为向量。 """ resp = client.embeddings.create(model=model, input=text) return resp.data[0].embedding def summarize_dialogue(dialogue: str, model: str = "gpt-4o-mini") -> str: """ 将一段原始对话压缩为结构化记忆文本。 """ prompt = f""" 你是一个记忆抽取助手。请从下面的对话中提取值得长期记住的信息, 包括:用户偏好、项目决策、重要事实、待办事项。 不要输出无关内容,不要编造,用简洁的中文条目输出。 对话: {dialogue} """ resp = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": "你是一个专业的记忆抽取助手。"}, {"role": "user", "content": prompt}, ], temperature=0.2, ) return resp.choices[0].message.content def build_memory_text(meta_type: str, content: str) -> str: """ 将抽取结果与记忆类型组合成规范化文本,便于检索。 """ return f"[{meta_type}] {content}"

build_memory_text的作用是给记忆加一个标签前缀,比如[偏好]、[项目决策]、[待办]。这个前缀在向量检索时能提供额外的语义锚点,提高命中率。

5.3 Agent 主逻辑

创建memory/agent.py,这是整个记忆层与 LLM 交互的入口。

# 文件路径:memory/agent.py from .vector_memory import VectorMemory from .summarizer import get_embedding SYSTEM_PROMPT = "你是一个有长期记忆能力的 AI 助手。" class MemoryAgent: """ 带长期记忆的 AI Agent。 """ def __init__(self, top_k: int = 5): self.memory = VectorMemory() self.top_k = top_k def run(self, user_input: str, dialogue_id: str = "default"): """ 接收用户输入,检索相关记忆,生成回复。 """ # 1. 将用户输入转成向量 query_vec = get_embedding(user_input) # 2. 检索历史记忆 result = self.memory.search_memory( query_embedding=query_vec, top_k=self.top_k, where={"dialogue_id": dialogue_id}, ) # 3. 组织记忆上下文 memory_context = "" if result["documents"]: docs = result["documents"][0] memory_context = "\n".join(docs) # 4. 拼装 prompt prompt = f""" 以下是与你对话用户的长期记忆,请结合记忆回答问题。 如果记忆与问题无关,请忽略记忆,直接根据你的知识回答。 长期记忆: {memory_context} 用户问题:{user_input} """ from .summarizer import client resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt}, ], temperature=0.7, ) return resp.choices[0].message.content def save_dialogue(self, dialogue_text: str, dialogue_id: str = "default"): """ 将一段对话抽取成记忆并写入向量库。 """ from .summarizer import summarize_dialogue, build_memory_text summary = summarize_dialogue(dialogue_text) memory_text = build_memory_text("对话记忆", summary) vec = get_embedding(memory_text) memory_id = f"{dialogue_id}_{int(time.time())}" self.memory.add_memory( memory_id=memory_id, text=memory_text, embedding=vec, metadata={"dialogue_id": dialogue_id, "timestamp": time.time()}, )

注意:上面用到了time模块,记得在文件头部import time。

这个 Agent 的核心思想是:每次回答前先“回忆”,再把回忆结果作为上下文的一部分交给 LLM。这就是外部记忆层的雏形。

5.4 完整演示脚本

创建根目录下的demo.py,模拟一次完整的“写入记忆 → 检索记忆 → 生成回复”流程。

# 文件路径:demo.py from memory.agent import MemoryAgent agent = MemoryAgent() # 第一步:模拟一次对话,并保存记忆 dialogue = """ 用户:我们项目准备用 Python 开发后端 API。 助手:好的,建议使用 FastAPI 框架,性能好,生态也成熟。 用户:可以,数据库就用 PostgreSQL。 助手:明白,PostgreSQL 加 FastAPI 是很常见的组合。 """ print("==> 正在保存对话记忆...") agent.save_dialogue(dialogue, dialogue_id="project_a") print("保存完成。") # 第二步:新会话,用户提问,检索相关记忆 question = "我们项目后端用什么框架?" print(f"用户问题:{question}") answer = agent.run(question, dialogue_id="project_a") print(f"AI 回答:{answer}")

运行:

python demo.py

预期效果是:即使这是一次全新的会话,AI 也能根据历史记忆回答出“项目使用 FastAPI 和 PostgreSQL”。这就是长期记忆最基本的价值。

6. 进阶架构思路与工具选型

6.1 引入记忆总结与分层

上面示例只做了最简单的“整段对话压缩后存入记忆”。实际项目中,对话可能很长,直接压缩整段对话会丢失细节。更好的做法是:

  1. 将对话按轮次切分,每 3-5 轮抽取一次。
  2. 抽取结果先写入“候选记忆区”。
  3. 当候选记忆区积累到一定规模后,再做一次合并压缩,写进“长期记忆区”。
  4. 检索时优先从长期记忆区检索,其次从候选区补充。

这种分层方式把信息按生命周期切分,避免把重要细节和临时内容混在一起。

6.2 记忆更新与遗忘策略

长期记忆最大的风险不是“没有记忆”,而是“记住了错误信息后一直生效”。比如用户先说“数据库用 MySQL”,后来改口“还是用 PostgreSQL 吧”,如果记忆系统没有覆盖机制,AI 会一直按旧信息回答。

一个有效的做法是更新写入。

# 伪代码:更新写入逻辑 def upsert_memory(query_text: str, new_memory: str): # 先检索相似旧记忆 old_memories = memory.search_memory(get_embedding(query_text), top_k=3) for doc in old_memories: if similarity(doc, new_memory) > 0.85: # 删除旧记忆,用新记忆覆盖 memory.collection.delete(ids=[doc["id"]]) # 再写新记忆 memory.add_memory(...)

同时,还需要引入时间衰减:检索结果不仅看相似度,还要乘以时间因子,让两周前的记忆权重自然降低。

6.3 向量数据库选型参考

不同规模项目的向量数据库选型策略:

项目规模推荐方案理由
个人实验 / 小项目Chroma安装简单,支持本地持久化
中型项目Qdrant / Milvus检索性能好,支持过滤,部署可控
已用 PostgreSQL 的团队pgvector避免额外引入组件,复用现有数据库
大规模生产Milvus / Weaviate分布式能力强,运维生态成熟

选型时不要只看宣传的性能数字,还要关注团队的运维能力。嵌入在业务代码里的向量库往往比单独维护一套分布式系统更省心。

7. 常见问题与排查思路

在实现长期记忆的过程中,我遇到和看到过很多问题,下面整理成一张排查表。

问题现象常见原因解决思路
检索结果与问题完全无关embedding 模型选型不当或领域差异大换更强的 embedding 模型;增加记忆标签,如[偏好][决策]
查询返回空结果collection 为空,或 where 过滤条件写错检查记忆是否写入;先去掉 where 条件测试
记忆写入但重启后丢失Chroma 持久化路径未设置使用 PersistentClient 并指定 path
检索到的记忆太旧缺乏时间衰减或过时清理机制在 metadata 中记录时间戳,检索后按时间重排
向量维度不一致导致报错embedding 模型换了,向量维度变了统一 embedding 模型,或者重建 collection
对话太长导致压缩丢失关键信息一次性压缩整段对话按轮次切分,分批压缩;对关键实体单独抽取
用户修改了旧信息但 AI 仍按旧信息回答没有更新覆盖机制写入前先查找相似记忆,用新信息覆盖旧记忆
Token 费用过高每次检索都塞入过多记忆条数降低 top_k;增加过滤条件;对记忆做重要度评分

排查这类问题有一个通用思路:把“写入”和“检索”两步分开验证。

先在记忆库里直接查询某条内容,确认已经写入;再用同一个 embedding 模型对测试问题做相似度对比,确认检索链路没问题;最后才去检查 prompt 组装是否正确。

8. 最佳实践与工程建议

8.1 记忆内容要“结构化 + 标签化”

不要把原始对话直接扔进向量库,至少要抽取、格式化、加标签。通过[偏好]、[决策]、[待办]、[实体关系]这样的前缀,能让检索命中率提升一个台阶。这种设计在检索时天然形成“类别过滤”的能力,可以配合 metadatas 里的 type 字段做二次过滤。

8.2 严格控制写入“记忆”的门槛

记忆不是越多越好。建议设置写入门槛:

  • 寒暄、问候、表情类内容不写入。
  • 单轮无信息量的内容不写入。
  • 明确包含用户偏好、项目决策、重要事实的内容才写入。
  • 写入前先让记忆抽取模型判断“这条对话是否有长期价值”。

门槛越高,记忆库越干净,检索质量越高。

8.3 上下文组装顺序有讲究

在构建 prompt 时,记忆内容不宜放在用户问题之后。推荐顺序:

系统提示词 注入记忆内容(长期记忆) 最近对话(短期记忆) 用户当前问题

因为模型对输入内容的注意力分布并不均匀,放在中间的内容容易被忽略,放在开头和结尾的信息被利用得更充分。记忆内容最好放在 system 或对话开头的位置。

8.4 单独管理记忆的版本与回滚

长期记忆在生产环境中相当于“用户状态数据”,和代码一样需要版本管理。可以给记忆增加类似memory_version的字段,当算法或模型升级时,可以按版本切换。涉及记忆批量修改的场景,务必先在测试环境验证,再对生产环境操作,并保留数据备份。

8.5 安全与隐私边界

长期记忆一旦落地,就会涉及用户数据的保密、删除、导出等合规问题。至少要考虑:

  • 敏感信息(密码、身份证号、密钥等)不得写入记忆库。
  • 提供“清除指定用户记忆”的运维接口。
  • 记忆检索和写入需要做权限隔离,不同用户不能互相看到对方的记忆数据。
  • 如果需要删除用户数据,应同时清除向量库、关系库和备份中的数据。

9. 总结与下一步

这篇文章从 LLM 长期记忆的核心痛点出发,梳理了长期记忆与短期记忆的区别、四种主流架构模式,并实现了一个基于向量检索的完整记忆层示例。

你现在应该掌握了:

  • LLM 长期记忆要解决的是“跨会话信息留存与准确恢复”问题
  • 向量检索记忆是最主流的长期记忆实现方案
  • 一个可运行的内存层至少包含:存储、写入、检索、注入四部分
  • 记忆不是越多越好,必须有抽取、过滤、更新、遗忘机制

下一步,可以沿着这几个方向继续深入:

  • 给记忆层接入真实对话流,接入 LangChain 或自研 Agent 框架
  • 尝试 Milvus 或 pgvector 等生产级向量数据库
  • 尝试引入 reranker 模型,提升检索精度
  • 研究 MemGPT、Generative Agents 等论文中的分层记忆思想

长期记忆是 LLM 走向 Agent 化、个性化的重要基础设施,也是这个阶段最值得花时间研究的工程方向之一。建议你先跑通本文的示例,再根据你的业务场景设计属于自己的记忆模型。代码写多了,踩坑踩多了,自然就有了自己的经验体系。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询