
LLM 长期记忆架构实验从 Context Window 困局到可持久化记忆系统这次我们看一个方向性很强的项目作者在 Show HN 上发布了一组关于 LLM 长期记忆架构的实验。标题很直白——Tried some experiments with architecture for Long term memory for LLM。先说结论如果你正在做 RAG、Agent、多轮对话、个人知识库这类应用迟早会撞到同一个问题——模型上下文窗口再大也装不下持续累积的对话历史和使用者知识。长期记忆架构就是用来解决这个问题的。本文会从架构角度拆解 LLM 长期记忆的几种主流方案重点对比它们的实现成本、适用场景和资源占用然后给出一套可以直接运行的实验代码基于向量库 摘要记忆 分层检索的最小长期记忆系统。你可以把它当成一个实验脚手架后续接到自己的 Agent 或知识库项目里。核心信息先给出来项目类型LLM 长期记忆架构实验主要解决的问题对话历史跨会话持久化、知识动态更新、上下文超长后的信息压缩典型技术栈向量数据库、嵌入模型、摘要模型、分层记忆管理硬件门槛视模型大小而定嵌入式模型可用 CPU 推理摘要与生成模型可走 API适合人群正在做 RAG、Agent、知识库、客服机器人、个人助手的开发者输出形式架构分析 可运行的 Python 实验代码这篇文章会带你把长期记忆的架构思路、代码实现、性能观察方法和常见坑完整过一遍。1. 核心能力速览长期记忆不是某一个模型而是一套架构组合。项目标题里的关键词是 architecture这非常重要——它说明重点不在单个模型而在于如何把多个组件组织起来。一个完整的 LLM 长期记忆系统通常包含以下能力能力项说明跨会话记忆对话结束后记忆仍然保留下次启动可以恢复信息检索根据当前问题召回相关的历史记录或知识片段记忆压缩把长对话/长文本压缩成摘要减少存储和输入开销记忆分层短期工作记忆和长期持久记忆分离管理知识更新新输入可以写入、旧记忆可以合并或淘汰可插拔存储后端可替换为向量库、SQLite、Redis 或文件系统批量处理支持批量导入历史对话/文档并建立索引API 集成记忆模块可作为独立服务被主应用调用实际操作中这套架构至少要包含四个角色嵌入模型把文本转成向量用于语义检索。存储后端向量库或普通数据库保存原始文本和向量。记忆管理器决定什么写入记忆、什么遗忘、什么先放短期记忆。生成模型真正回答问题的 LLM消费检索到的记忆。这四部分任何一个单独看都不复杂难点在“何时写、何时读、何时压缩、何时遗忘”的调度策略上。这也就是标题里说的experiments with architecture——架构实验的重点在于找到一套能稳定工作的记忆管理逻辑。2. 为什么 LLM 需要长期记忆先明确问题。现在的 LLM 普遍采用“无状态”设计每次请求把完整的上下文丢给模型模型推理完就结束服务端不保留任何状态。所有对话历史、知识背景、用户偏好都要靠调用方自己拼进 Prompt。这种设计带来三个直接问题。2.1 上下文窗口有物理上限大模型的 context window 从最早的 2K/4K发展到现在的 128K、200K 甚至 1M看起来很大但实际使用中有效长度要打折扣。超长上下文不仅显存/内存消耗大而且在长文本中段的信息召回率会明显下降。你不可能无限地把历史对话全部塞进 Prompt。2.2 Token 成本线性增长每次请求都把全部历史拼进去Token 消耗随时间线性增长。对一个长期使用的助手来说成本不可控。更合理的做法是把原始历史压缩成摘要只把当前问题相关的片段喂给模型。2.3 知识无法动态更新预训练模型的知识有截止时间。用户提供的新信息、项目文档、个人偏好模型本身记不住。只有通过 RAG 或记忆系统在推理时注入模型才能“知道”这些信息。长期记忆架构的核心目标就是在这三个约束下让模型看起来“记得住过去”。3. LLM 长期记忆的几种架构实验方案把社区的实验方案归类长期记忆大致有四条技术路线。它们不是互斥的很多项目是组合使用。3.1 全量拼接方案最简单。把历史对话全部拼进上下文。适合极短会话或测试不适合生产。优点实现成本为零信息不丢失。 缺点Token 爆炸、成本高、上下文超长后质量下降。这个方案只能作为基线对比不是真正的长期记忆。3.2 向量库 RAG 方案这是目前最主流、工程上最成熟的路线。每次对话结束后把关键内容切片、嵌入、存入向量库。新一轮对话开始时根据用户输入做向量检索召回 top-k 相关片段拼进 Prompt。优点存储成本低检索速度快可以处理海量历史。 缺点片段之间缺乏因果关联检索不到语义相近却表述不同的记忆单独使用容易丢失时间线信息。3.3 摘要记忆方案把对话历史交给 LLM 生成结构化摘要然后保存摘要而不是保存原始文本。下一轮对话用摘要作为背景上下文。优点Token 占用小能保留宏观脉络。 缺点摘要会丢失细节长期运行后摘要本身也会变长需要多层摘要或滚动压缩。3.4 三阶段记忆架构短期 长期 工作记忆这是刚才几种实验里更接近“认知架构”的方案也是本文代码部分要实现的思路工作记忆当前这轮对话的上下文直接放进 Prompt。短期记忆最近几轮对话的缓存放在内存或 Redis设置 TTL。长期记忆经过筛选的重要信息写入向量库或结构化存储长期保留。每次请求的流程是先查长期记忆再叠加短期记忆最后加上当前输入组装成完整 Prompt。这种分层架构的好处是职责清晰短期记忆管“最近聊了什么”长期记忆管“你必须记住的知识和偏好”。下面是三种主流路线对比架构方案存储介质Token 开销信息保真度实现复杂度适合场景全量拼接无直接用原始文本随历史线性增长最高最低临时测试向量库 RAG向量数据库 文本库只消耗召回的 top-k中依赖切片和召回中知识库问答、客服摘要记忆数据库保存摘要只消耗摘要片段中高摘要质量决定中长期多轮对话分层记忆向量库 缓存 DB摘要 召回片段 最近缓存高较高Agent、个人助手、复杂知识系统4. 环境准备与前置条件这个项目本质是“实验 架构验证”所以环境准备按通用 Python 项目处理。下面是一套可以跑通的最低配置具体版本以你本机为准。4.1 基础环境Python 3.10 或更高。一个可用的 LLM 调用方式OpenAI/DeepSeek 等兼容 API或者本地通过 Ollama / llama.cpp 启动的模型。一个嵌入模型本地可用 sentence-transformers 系列也可用 API 嵌入接口。向量库实验阶段用 Chroma 或 FAISS 最简单生产再考虑 Milvus / Qdrant。4.2 安装依赖pip install chromadb sentence-transformers openai numpy如果使用本地 Ollama不需要 openai 包可以直接通过 HTTP 调用pip install chromadb sentence-transformers requests4.3 模型选择建议用途推荐类型说明嵌入本地 CPUsentence-transformers 小型模型几 GB 内存即可运行中文场景选 multilingual 模型嵌入云端各家 embedding 接口简单稳定按量付费摘要/生成本地7B-14B 量化模型显存 8G-16G 可跑Ollama 直接拉取摘要/生成云端对话模型 API低成本适合实验阶段显存占用这里要提醒嵌入模型很小主要开销在摘要和生成模型上。如果你用本地 7B 量化模型实际显存占用通常在 6G-10G 之间具体要看量化等级、上下文长度和批次大小。如果用云端 API本地只有向量库和嵌入模型占用内存压力小很多。5. 长期记忆架构实验实现下面给出一个可以运行的分层记忆系统原型。它包含三个模块长期记忆存储用 Chroma 保存历史关键信息。摘要生成用 LLM 把对话历史压缩成摘要。记忆检索根据当前问题召回长期记忆叠加最近对话组装 Prompt。5.1 记忆存储模块# memory_store.py import chromadb from chromadb.config import Settings class MemoryStore: def __init__(self, persist_dir: str ./mem_db): self.client chromadb.PersistentClient( pathpersist_dir, settingsSettings(anonymized_telemetryFalse) ) self.collection self.client.get_or_create_collection( namelong_term_memory, metadata{hnsw:space: cosine} ) def write(self, memory_id: str, text: str, embedding: list, metadata: dict None): 写入一条长期记忆。 self.collection.upsert( ids[memory_id], documents[text], embeddings[embedding], metadatas[metadata or {}] ) def search(self, embedding: list, top_k: int 5): 根据输入向量召回最相关的记忆片段。 results self.collection.query( query_embeddings[embedding], n_resultstop_k ) return results[documents][0] if results[documents] else []这个模块做的事情很简单文本加向量一起写进 Chroma查询时用余弦相似度召回。注意upsert意味着同 id 写入会覆盖适合做记忆更新。5.2 嵌入与文本管理实际项目里原始文本和向量通常分开保存向量库只负责检索。这里为了演示直接使用 Chroma 的 document 存储功能。# embedder.py from sentence_transformers import SentenceTransformer class Embedder: def __init__(self, model_name: str sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2): self.model SentenceTransformer(model_name) def embed_text(self, text: str) - list: return self.model.encode(text).tolist() def embed_batch(self, texts: list) - list: return self.model.encode(texts).tolist()中文场景建议用多语言模型否则中文语义召回效果会很差。如果你用 API 嵌入把这里的实现替换成 HTTP 调用即可对外接口保持一致。5.3 摘要记忆模块摘要模块负责把一长段对话压缩成结构化的记忆条目。这个模块需要调用 LLM示例里用 OpenAI 兼容接口也可以通过 Ollama 切换成本地模型。# summarizer.py import json import requests class Summarizer: def __init__(self, api_url: str http://127.0.0.1:11434/api/generate, model_name: str qwen2.5:7b): self.api_url api_url self.model_name model_name def summarize_dialogue(self, dialogue_text: str) - dict: 把原始对话压成结构化记忆。 prompt f请把下面的对话压缩成结构化记忆只保留关键信息。 输出 JSON 格式包含三个字段 - topic: 主题 - facts: 事实性信息列表 - preferences: 用户偏好或明确指示 对话内容 {dialogue_text} 输出 JSON response requests.post( self.api_url, json{ model: self.model_name, prompt: prompt, stream: False, format: json }, timeout120 ) response.raise_for_status() content response.json()[response] return json.loads(content)这里的format: json是 Ollama 支持的结构化输出参数。如果你用的是标准 OpenAI 接口需要改成response_format{type: json_object}。实际生产环境中可以用更严格的 prompt 模板也可以在解析失败时做重试或降级处理。5.4 主流程记忆写入与检索把上面的模块组装起来就是完整的分层记忆主流程。# memory_manager.py from memory_store import MemoryStore from embedder import Embedder from summarizer import Summarizer class LongTermMemoryManager: def __init__(self): self.store MemoryStore() self.embedder Embedder() self.summarizer Summarizer() self.recency_buffer [] # 短期记忆最近 N 轮对话 def add_dialogue(self, dialogue_text: str, dialogue_id: str): 对话结束后调用写入长期记忆。 # 1. 生成结构化摘要 memory self.summarizer.summarize_dialogue(dialogue_text) # 2. 把摘要的每个事实拆成一条记忆 content | .join(memory[facts]) if memory.get(preferences): content | 偏好 | .join(memory[preferences]) embedding self.embedder.embed_text(content) # 3. 写入向量库 self.store.write( memory_iddialogue_id, textcontent, embeddingembedding, metadata{topic: memory.get(topic, )} ) # 4. 更新短期缓冲区保留最近 10 轮 self.recency_buffer.append(dialogue_text) if len(self.recency_buffer) 10: self.recency_buffer.pop(0) def build_context(self, user_question: str, max_recent: int 3) - str: 组装检索到的长期记忆和最近对话生成上下文。 # 1. 根据当前问题检索长期记忆 query_embedding self.embedder.embed_text(user_question) long_term self.store.search(query_embedding, top_k5) # 2. 取最近几轮短期记忆 recent \n.join(self.recency_buffer[-max_recent:]) # 3. 拼装上下文 context_parts [【长期记忆】] context_parts.extend(long_term if long_term else [暂无]) context_parts.append(\n【最近对话】) context_parts.append(recent if recent else 暂无) return \n.join(context_parts) # 使用示例 if __name__ __main__: manager LongTermMemoryManager() # 模拟第一段对话 session_1 用户我叫张三是深圳的前端工程师。\n助手好的张先生我记住了。 manager.add_dialogue(session_1, dialogue_idsession_001) # 模拟第二段对话问题依赖第一段的记忆 question 你还记得我叫什么名字吗我是做什么的 context manager.build_context(question) print(context)这个主流程把三个关键操作串起来了对话结束后调用add_dialogue把信息写入长期记忆。用户提出新问题时调用build_context检索记忆并拼装上下文。拼好的上下文直接送进 LLM作为系统提示或用户消息的一部分。这套架构已经把“跨会话记忆”的问题解决了。重启程序后Chroma 持久化目录还在记忆不会丢。5.5 工作记忆的组装实际对接 LLM 时最终 Prompt 可以这样组织[系统指令] 你是一个带有长期记忆的 AI 助手。请基于下面的记忆回答问题。 {context} [当前问题] {user_question}记忆管理器返回的context直接插入系统的占位符即可。注意要限制context的总长度避免召回过多导致 Prompt 膨胀。6. 功能测试与效果验证实验代码写完后要按以下顺序验证。不要一上来就测复杂场景先验证最基础的数据通路。6.1 测试 1记忆写入是否成功运行上面的memory_manager.py看控制台是否输出检索到的长期记忆。如果输出【长期记忆】暂无说明第一段对话可能没写入需要检查 Chroma 持久化目录。判断标准第二次运行时同样的问题能召回“张三、深圳、前端工程师”这些信息。6.2 测试 2跨会话恢复记忆关闭程序重新启动再次询问“我是谁”。如果build_context能召回昨天的记忆说明持久化生效。这是长期记忆最核心的验收标准。如果失败先检查persist_dir路径和 Chroma 版本兼容性。6.3 测试 3摘要准确度输入一段包含多个事实的对话打印 Summarizer 输出的 JSON检查topic 是否命中主题。facts 是否包含核心事实。preferences 是否能识别用户偏好。摘要质量差后面所有记忆都会失真。这是整个链路里最需要人工检查的环节。6.4 测试 4检索相关性准备一组不同主题的记忆然后用一个包含干扰项的问题去检索观察是否召回了真正相关的内容。是否存在语义相近但答非所问的片段。不同 embedding 模型对结果的差异。这一步可以调整top_k参数从 3 到 10 之间实验找出最合适的值。6.5 测试 5Batch 批量写入把历史对话从 JSON 文件批量导入import json with open(historical_dialogues.json, r, encodingutf-8) as f: dialogues json.load(f) manager LongTermMemoryManager() for item in dialogues: manager.add_dialogue(item[text], dialogue_iditem[id]) print(f已写入{item[id]})批量写入时注意控制速率避免调用大模型摘要接口时触发限流。6.6 测试 6长对话压缩构造一段超过 2000 字的对话看摘要输出占用的 Token 是否明显变小。这个测试直接关系到成本控制。合理的压缩率应该在 5 到 10 倍以上。7. 接口 API 与批量任务设计长期记忆模块不能只当脚本用。实际接入项目时最方便的方式是把它封装成独立 API 服务让主应用通过 HTTP 调用。7.1 用 FastAPI 封装记忆服务# api_server.py from fastapi import FastAPI from pydantic import BaseModel from memory_manager import LongTermMemoryManager app FastAPI() manager LongTermMemoryManager() class WriteRequest(BaseModel): dialogue_id: str dialogue_text: str class QueryRequest(BaseModel): question: str top_k: int 5 app.post(/memory/write) def write_memory(req: WriteRequest): 写入一段对话摘要到长期记忆。 manager.add_dialogue(req.dialogue_text, req.dialogue_id) return {status: ok, memory_id: req.dialogue_id} app.post(/memory/query) def query_memory(req: QueryRequest): 根据问题检索长期记忆。 context manager.build_context(req.question) return {context: context} if __name__ __main__: import uvicorn uvicorn.run(app, host127.0.0.1, port8000)启动命令pip install fastapi uvicorn python api_server.py启动后访问http://127.0.0.1:8000/docs可以看到 Swagger 接口文档可以直接在页面上测试。7.2 curl 调用示例# 写入记忆 curl -X POST http://127.0.0.1:8000/memory/write \ -H Content-Type: application/json \ -d {dialogue_id: session_002, dialogue_text: 用户我喜欢用 Python 写自动化脚本。\n助手已记录。} # 查询记忆 curl -X POST http://127.0.0.1:8000/memory/query \ -H Content-Type: application/json \ -d {question: 用户平时喜欢用什么语言, top_k: 3}7.3 Python 调用示例import requests BASE_URL http://127.0.0.1:8000 # 写入 requests.post(f{BASE_URL}/memory/write, json{ dialogue_id: session_003, dialogue_text: 用户下周我要去杭州出差三天。 }) # 查询 resp requests.post(f{BASE_URL}/memory/query, json{ question: 用户最近有什么行程安排 }) print(resp.json()[context])7.4 批量任务注意点批量导入历史数据时要考虑几个问题摘要接口并发限制。建议限速比如每秒请求数控制在接口配额内。失败重试。批量任务要记录失败的 dialogue_id结束后统一重试。进度日志。每写入 100 条打印一次进度方便中断恢复。幂等处理。重复导入同一 dialogue_id 时upsert会覆盖旧记录避免脏数据累积。8. 资源占用与性能观察长期记忆系统的资源开销分三块嵌入模型的 CPU/内存、向量库的磁盘和内存、摘要模型的生成耗时。8.1 嵌入阶段sentence-transformers的多语言小型模型CPU 上编码一次大概几十毫秒到几百毫秒。主要内存占用来自模型加载通常 1G-2G 内存。这是整个链路里最轻的一环。如果你想批量导入大量历史数据嵌入是主要瓶颈。可以先用 GPU 批量生成向量缓存到磁盘再批量写入向量库。8.2 摘要阶段摘要需要调用 LLM这是整个链路里最重的部分。观察重点本地 7B 量化模型生成 200 字摘要大约需要 5 到 15 秒取决于硬件。API 模型耗时在 1 到 5 秒之间但受网络和限流影响。显存占用取决于本地模型的参数量和量化等级。实际值要以本机nvidia-smi为准。建议摘要统一走异步任务队列不要阻塞对话主流程。需要在对话结束“延时几秒”后把摘要写入记忆而不是当场等待摘要完成。8.3 检索阶段向量检索本身很快几千条记忆的规模下检索耗时在毫秒级。真正的开销在向量库加载到内存的量。每次查询要编码一次用户问题。metadata 过滤是否使用索引。8.4 如何观察资源占用Linux/macOS 用top或htop观察内存和 CPUWindows 用任务管理器。GPU 显存用nvidia-smi持续观察。# 每 2 秒刷新一次显存占用 watch -n 2 nvidia-smi8.5 降本优化思路嵌入模型换更小的蒸馏版。摘要触发条件改成“对话超过 N 轮才压缩”。长期记忆只存事实和偏好不存寒暄和噪音。定期清理低相关度记忆避免向量库无限膨胀。9. 常见问题与排查方法下面按实战中最高频的问题整理排查表。问题现象可能原因排查方式解决方案重启后记忆丢失Chroma persist_dir 路径错误或未持久化检查目录下是否有 sqlite3 文件确认 PersistentClient 的 path 参数检索结果完全不相关嵌入模型语言不匹配在中英文混合数据上分别测试换多语言嵌入模型摘要输出 JSON 解析失败LLM 输出有噪音文本打印 summarizer 原始返回值增加解析容错和重试或改更严格的 prompt首次导入很慢嵌入和摘要串行执行看日志定位耗时阶段摘要任务异步化嵌入批量编码显存不足本地生成模型参数量过大运行nvidia-smi查看占用换更低量化等级或走 API接口超时摘要模型推理耗时长看接口日志和模型日志请求超时调大或改成异步任务记忆重复累积同一会话反复写入检查调用方是否重复触发写接口利用 upsert 幂等性或者先查后写向量库文件过大记忆无清理策略查看 collection count增加遗忘/合并策略定期清理低热度记忆9.1 摘要 JSON 解析失败的兜底方案def safe_parse(response_text: str): 尝试多种方式解析 LLM 输出的 JSON。 try: return json.loads(response_text) except json.JSONDecodeError: # 去掉 markdown 代码块标记 cleaned response_text.strip().removeprefix(json).removesuffix().strip() return json.loads(cleaned)9.2 向量库体积膨胀的清理策略长期运行后向量库会积累大量低价值记忆。一个简单的方案是按 metadata 里的时间戳定期清理。更高级的做法是用摘要记忆去“合并”旧的相似记忆但这需要额外的聚类和生成逻辑可以在后续实验里迭代。10. 最佳实践与使用建议基于这类架构实验的通用经验给出几条落地上比较实用的建议。10.1 先跑最小闭环不要一上来就设计复杂的记忆分层。先实现“写入 - 检索 - 拼接 Prompt”的最小闭环确认数据通路没问题再逐步加摘要、遗忘、合并等策略。10.2 把记忆类型分开管理长期记忆至少分两类用户静态画像姓名、职业、偏好和动态事件最近的行程、任务进度。前者变化慢适合长期保存后者时效性强需要设置过期时间。用一个memory_type字段区分检索时分别处理。10.3 控制每次注入的记忆量每次查询召回的记忆不要无脑全塞。给上下文设一个硬上限比如 2000 字。超出部分按相关度截断。Token 预算要提前算好避免 Prompt 超限。10.4 给记忆加来源和时间戳每条记忆记录写入时间和来源对话 id。这样不仅能追溯还能做时间过滤。比如“只查最近 30 天的记忆”对行程类问题很有用。10.5 异步写入避免影响主流程对话结束后的摘要写入不要阻塞用户的下一个问题。用队列或后台任务处理主流程只做检索和生成。10.6 涉及隐私和版权数据时务必确认授权长期记忆系统存储的是用户对话、个人偏好、甚至公司文档。这些数据可能包含敏感信息。做好几件事敏感数据本地处理尽量不送第三方 API。向量库加密存储。提供“删除记忆”接口用户可主动清除。批量导入外部文档时确认你拥有文档的使用和再加工权利。10.7 定期检查摘要质量摘要模型决定记忆系统的“地基”。建议每处理一批数据人工抽查 5% 到 10% 的摘要结果。如果摘要开始出现事实幻觉整个检索下来的答案都会失真。11. 总结与下一步这个实验项目最有价值的地方是把 LLM 长期记忆从“概念讨论”拉到了“可运行架构”层面。即使你不直接使用它的代码也可以借这套思路重新审视自己的 Agent 或知识库项目当前对话是否真的需要记住这么多历史哪些信息值得沉淀怎么在成本和召回质量之间找平衡最先应该验证的是摘要模块输出的结构化记忆是否准确。这一步决定后续所有检索和生成的上限。最容易踩的坑有两个一是嵌入模型和业务语言不匹配导致召回质量差二是没有遗忘策略向量库越跑越大检索噪音越来越多。后续可以扩展的方向包括记忆合并对相似记忆做聚类和合并减少冗余。分层摘要对长历史生成多层摘要适配不同粒度的检索。时间感知检索在查询中加入时间范围条件。反思机制定期让 LLM 审视已有记忆清理过期信息。与 Agent 框架集成把记忆模块作为 ReAct / Function Calling 流程中的 Tool 暴露。建议把这份代码当作实验脚手架保存好接 RAG、接知识库、接个人助理都能用。先跑通最小闭环再逐步加复杂度——这比一开始设计一个庞大的记忆系统要靠谱得多。