
最近在做企业级 AI Agent 落地的过程中经常听到一个吐槽“我们的 Agent 聊着聊着就忘了前面说过什么用户上午刚确认的需求下午再问一遍它就完全没印象了。”这其实不是某个厂商的 bug而是大部分 Agent 的默认状态——无状态。像 ChatGPT 网页版这类产品只是在窗口期内拼接了历史消息一旦超过上下文上限或者开启新会话之前的交互内容就全部清空了。在单轮问答场景下这种设计没有问题但一旦进入客服、销售、运营助手、企业内部问答这类需要“记住用户”的场景无状态的 Agent 基本不可用。本文就从“Agent 为什么会失忆”讲起梳理长期记忆系统的核心概念再分别基于 Redis、LLM 摘要、向量数据库给出三套可落地的实现方案。代码以 Python 为主覆盖从单机 Demo 到企业级架构的演进路径适合正在做 AI Agent 开发、想系统了解 Agent 记忆系统的后端开发者阅读。1. 为什么 AI Agent 总是“失忆”1.1 无状态是 LLM 的天生设定需要先明确一点大多数大语言模型LLM本身不保留任何历史状态。模型接收的是你本次请求传入的 prompt然后根据模型权重计算输出。上一次对话的内容不会残留在模型内部模型也没有“记住某个用户”的内建能力。所谓“多轮对话”本质上是一个输入组装过程用户当前输入 历史对话记录 新的完整 prompt也就是说模型看到的永远是“被拼好的文本”。如果业务代码没有把历史消息带进来模型天然就不知道之前聊过什么。这是理解 Agent 记忆系统的第一个关键点记忆不是模型的能力而是应用层要解决的问题。1.2 “失忆”的三种典型场景结合实际项目Agent 失忆通常分三种情况场景表现根本原因会话内失忆同一会话内聊到后面忘了前面的关键信息上下文窗口有限超出后最早的消息被截断跨会话失忆新开一个会话后不认识老用户没有持久化存储用户画像和历史偏好知识断层用户说过“我在上海工作”下次完全想不起来缺少结构化的事实抽取与存储检索机制第一种可以通过加大窗口或用摘要压缩解决第二种和第三种才是长期记忆系统要解决的核心问题。1.3 为什么企业级场景必须做长期记忆企业级 AI Agent 和通用聊天机器人最大的区别在于它有明确的服务对象和业务目标。无论是售前客服、售后诊断还是内部知识助手都需要持续积累用户信息、历史工单和业务上下文。公开资料显示有相当比例的企业知识工作者每周会花大量时间在信息检索和跨系统沟通上。如果 Agent 每次都要用户重新描述一遍背景那就完全失去了提效意义。换句话说长期记忆系统是企业级 Agent 从“Demo”走向“可用”的关键能力之一。2. 长期记忆系统核心概念与能力边界2.1 什么是 Agent 长期记忆系统简单来说长期记忆系统是一套“能存、能取、能更新、能遗忘”的机制。它让 Agent 在多次会话之间保留用户画像、业务事实、历史决策和知识片段并在合适的时机把它们重新注入模型上下文。先从几个维度理解记忆层次会更清楚短期记忆当前会话内的上下文通常用消息序列维护有窗口长度限制。长期记忆跨会话持久化存储的用户事实、偏好、历史约定通常存储在数据库或向量库中。工作记忆某个任务执行过程中临时组合的记忆片段任务结束即可释放。这三者不是互斥的而是配合使用。短期记忆负责“记住刚才”长期记忆负责“记住昨天”工作记忆负责“当前任务要用的临时信息”。2.2 长期记忆需要解决哪些问题一个真正能用的长期记忆系统至少要覆盖下面几条链路写入链路什么时候把用户信息写入记忆库是全部写入还是抽取关键信息后写入存储链路结构化信息放关系型数据库语义片段放向量数据库高频热数据放 Redis如何分层检索链路用户提问后如何找回相关的历史记忆靠关键词还是向量相似度更新链路用户修改了信息后如何保证旧记忆被覆盖而不是和新记忆冲突遗忘链路记忆无限增长后如何做清理归档哪些记忆优先级更高很多团队做记忆系统失败不是因为某一个环节不会写而是缺少全局设计。比如只做了 Redis 缓存没有事实抽取用户换个会话还是“失忆”或者只做了向量存储没有权限隔离导致 A 用户查到了 B 用户的隐私信息。2.3 几种常见记忆实现方式的对比实现方式特点适用场景不足纯上下文拼接实现最简单直接拼历史消息单轮或短会话问答窗口有限跨会话无效摘要压缩用 LLM 把长对话压缩为摘要长对话场景会丢失细节延迟较高结构化持久化抽取用户画像、事实写入数据库客服、CRM 场景需要设计抽取规则向量语义检索把记忆片段向量化按相似度召回开放式知识问答需要 Embedding 服务精度依赖模型实际企业级方案通常是多种方式组合使用而不是只选一种。3. 企业级 Agent 记忆系统整体架构设计3.1 系统分层思路下面给出一个比较通用的记忆系统分层设计不绑定具体云厂商可以直接按这个思路落地用户会话 ↓ Agent 流程编排层 ↓ 记忆读写服务Memory Service ↓ 路由与策略层写入策略 / 检索策略 / 更新策略 / 遗忘策略 ↓ 存储层 ├── Redis热记忆短期内高频访问 ├── MySQL / PostgreSQL结构化事实用户画像 ├── 向量数据库语义记忆开放域检索 └── 对象存储 / 数仓冷备归档记忆读写服务是整个系统的核心。它向上对 Agent 暴露统一接口向下屏蔽多种存储引擎的差异。这样后续替换向量库或加缓存组件时上层 Agent 不需要改动。3.2 记忆写入流程一条完整的信息写入链路大概是这样Agent 在对话中得到用户提供的信息。记忆服务判断信息是否值得持久化。如果是事实型信息走结构化抽取写入 MySQL 或 Redis。如果是语义型信息生成 Embedding 并写入向量库。对敏感字段做脱敏处理写入审计日志。这里要注意一个误区不是所有对话内容都要写入长期记忆。大量日常寒暄、临时性指令、无关闲聊如果全部入库既浪费存储成本又会在检索时引入大量噪声。所以要设计信息过滤器或重要性评分。3.3 记忆检索流程检索环节的关键是“按需注入”。用户每次提问时不可能把全部历史记忆塞进 prompt这样既浪费 token又会干扰模型判断。正确做法是解析用户意图判断是否需要历史记忆。从结构化库中拉取该用户的画像信息。从向量库中检索与当前问题语义相关的记忆片段。将召回结果按相关度和时间排序截取 top N。把最终记忆片段拼进系统 prompt。3.4 记忆更新与冲突处理用户的信息是会变化的。比如用户上周说“我在北京工作”这周说“我搬到上海了”。如果不做更新策略系统里就会同时存在两条矛盾记忆Agent 回答时可能随机命中其中一条。常见做法是给每条记忆增加时间戳和来源写入新记忆时如果检测到同实体、同属性的更旧记录则标记为过期或直接覆盖。这里的核心原则是新信息优先级高于旧信息但旧信息需要保留审计痕迹方便排查。4. 环境准备与项目结构4.1 环境版本说明本文代码以 Python 3.10 为基础。需要安装的组件如下版本以你当前项目的实际情况为准Python 3.10Redis 6.0MySQL 5.7 或 PostgreSQL 12ChromaDB本地开发用向量库LangChain可选用于工程化封装OpenAI SDK 或任意兼容 OpenAI 接口的大模型服务如果你使用的是国内大模型服务只要接口兼容 OpenAI 格式代码基本可以复用。如果使用本地模型做 Embedding也可以替换为 sentence-transformers 等方案本文会给出可替换的说明。4.2 安装依赖pip install redis pymysql chromadb openai langchain pydantic4.3 项目目录结构为了便于后续阅读建议按下面结构组织代码agent-memory/ ├── config.py # 全局配置 ├── models.py # 数据模型定义 ├── memory_service.py # 记忆服务统一接口 ├── storage/ │ ├── redis_store.py # Redis 存储 │ ├── mysql_store.py # MySQL 存储 │ └── vector_store.py # 向量库存储 ├── strategies/ │ ├── extraction.py # 信息抽取策略 │ ├── summarization.py # 摘要压缩策略 │ └── retrieval.py # 检索重排策略 ├── demo/ │ ├── demo_redis_memory.py # 实战一 │ ├── demo_summary_memory.py # 实战二 │ └── demo_vector_memory.py # 实战三 └── requirements.txt实际项目中不必完全照搬这个结构但建议保持“存储层与策略层分离”的设计思路这样后续扩展时改动最小。5. 实战一基于 Redis 的对话短期记忆5.1 场景说明第一个实战解决的是“会话内失忆”的问题。当用户在一次对话中连续提问时Agent 需要把最近几轮消息保存下来在下一次请求时带回给模型。这里用 Redis 做存储是因为它的读写速度快还能设置过期时间。对于只希望在几小时内有效的会话记忆Redis 非常合适。5.2 核心实现先定义配置类# config.py import os class Config: # Redis 配置 REDIS_URL os.getenv(REDIS_URL, redis://localhost:6379/0) # 短期记忆最多保存多少条消息 MAX_BUFFER_SIZE 20 # 会话过期间隔单位秒默认 24 小时 SESSION_TTL 24 * 60 * 60 # 系统提示词 SYSTEM_PROMPT 你是一个企业级智能助手请基于用户提供的信息回答问题。接着实现 Redis 存储类# storage/redis_store.py import json import redis class RedisMemoryStore: def __init__(self, redis_url: str, ttl: int 24 * 60 * 60): self.client redis.Redis.from_url(redis_url, decode_responsesTrue) self.ttl ttl def _key(self, session_id: str) - str: return fmemory:session:{session_id} def append_message(self, session_id: str, role: str, content: str): key self._key(session_id) message {role: role, content: content} # 将消息追加到 Redis 列表中 self.client.rpush(key, json.dumps(message, ensure_asciiFalse)) # 限制长度超出后从左侧删除最旧消息 length self.client.llen(key) while length 20: self.client.lpop(key) length - 1 # 刷新过期时间 self.client.expire(key, self.ttl) def get_messages(self, session_id: str) - list: key self._key(session_id) raw_list self.client.lrange(key, 0, -1) return [json.loads(item) for item in raw_list] def clear_session(self, session_id: str): key self._key(session_id) self.client.delete(key)这段代码做了三件事rpush把消息追加到 Redis 的 List 结构中。当消息数量超过 20 条时通过lpop删除最旧的消息。expire设置会话过期时间避免 Redis 中堆积无用数据。5.3 在 Agent 调用中注入记忆# demo/demo_redis_memory.py from config import Config from storage.redis_store import RedisMemoryStore from openai import OpenAI client OpenAI() memory_store RedisMemoryStore(Config.REDIS_URL, Config.SESSION_TTL) def chat_with_memory(session_id: str, user_input: str) - str: # 读取历史记忆 history memory_store.get_messages(session_id) # 组装消息列表 messages [{role: system, content: Config.SYSTEM_PROMPT}] messages.extend(history) messages.append({role: user, content: user_input}) # 调用模型 response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, temperature0.7, ) answer response.choices[0].message.content # 写入记忆 memory_store.append_message(session_id, user, user_input) memory_store.append_message(session_id, assistant, answer) return answer if __name__ __main__: session user_001 while True: user_input input(你: ) if user_input.lower() in [exit, quit]: break reply chat_with_memory(session, user_input) print(Agent:, reply)运行这个脚本后你会发现在同一 session 内Agent 能记住用户前面说过的内容。比如先输入“我叫张三是一名后端工程师”下一轮再问“我叫什么名字”Agent 能正确回答。5.4 不足与改进方向这个方案能解决短期记忆但有两个明显不足只保存原始消息不做信息抽取长期积累后 token 成本高。session 过期后记忆消失无法跨会话识别用户。所以实战一适合作为记忆系统的起点不能直接作为企业级长期记忆方案。接下来我们把它升级为“摘要 结构化存储”的方案。6. 实战二基于 LLM 摘要的长期事实记忆6.1 场景说明当用户和 Agent 的交互跨越多个会话时我们不能要求用户每次都重述自己的背景。此时需要把“用户说了什么”转化为“用户的持久化事实”存到数据库里。这个场景典型的例子是用户是某企业的采购负责人第一次会话提到“我们公司每年采购额大约 500 万主要关注供应链效率”第二次会话直接问“帮我推荐供应商管理系统”。Agent 如果能回忆起上一句回答质量会完全不同。6.2 方案设计这里采用“人工规则 LLM 摘要”结合的方式每轮对话结束后把新增的对话内容交给 LLM让它抽取事实信息。抽取结果为 JSON 结构包含实体、属性、值、时间。将结果写入 MySQL。查询时根据用户 ID 拉取该用户全部事实记录。6.3 数据库设计-- 用户事实表 CREATE TABLE user_facts ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(64) NOT NULL, entity VARCHAR(128) NOT NULL COMMENT 实体如用户、公司, attribute VARCHAR(128) NOT NULL COMMENT 属性如姓名、行业、偏好, fact_value TEXT NOT NULL COMMENT 事实内容, source_session_id VARCHAR(64) COMMENT 来源会话, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_active TINYINT DEFAULT 1, KEY idx_user_id (user_id), UNIQUE KEY uk_user_entity_attr (user_id, entity, attribute) ) COMMENT用户长期事实记忆表;is_active字段用于软删除和冲突标记。当用户信息变更时旧记录不物理删除而是标记为失效方便审计。6.4 事实抽取与存储# strategies/extraction.py import json from openai import OpenAI client OpenAI() EXTRACTION_PROMPT 请从以下对话中抽取关于用户的持久化事实。 只抽取明确表达的信息不要猜测。 如果某条信息用户已经更正请在结果中标记 is_activefalse。 输出格式为 JSON 数组每个元素包含 entity: 实体名称 attribute: 属性名称 fact_value: 事实值 is_active: 是否当前有效 对话内容 {conversation} def extract_facts(conversation: str) - list: response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个信息抽取助手只输出 JSON。}, {role: user, content: EXTRACTION_PROMPT.format(conversationconversation)}, ], temperature0, ) content response.choices[0].message.content # 清理可能的 markdown 代码块标记 content content.strip() if content.startswith(json): content content[7:] if content.endswith(): content content[:-3] return json.loads(content)这段代码的要点是使用temperature0确保抽取结果稳定。使用 JSON 格式输出方便程序化处理。要求模型输出is_active字段为后续冲突检测做准备。6.5 写入 MySQL 并读取# storage/mysql_store.py import pymysql class MySQLFactStore: def __init__(self, host, port, user, password, database): self.conn pymysql.connect( hosthost, portport, useruser, passwordpassword, databasedatabase, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, ) def save_fact(self, user_id: str, fact: dict, session_id: str): with self.conn.cursor() as cursor: sql INSERT INTO user_facts (user_id, entity, attribute, fact_value, source_session_id, is_active) VALUES (%s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE fact_value VALUES(fact_value), updated_at CURRENT_TIMESTAMP, is_active VALUES(is_active) cursor.execute(sql, ( user_id, fact[entity], fact[attribute], fact[fact_value], session_id, fact.get(is_active, 1), )) self.conn.commit() def get_user_facts(self, user_id: str) - list: with self.conn.cursor() as cursor: sql SELECT entity, attribute, fact_value, updated_at FROM user_facts WHERE user_id %s AND is_active 1 ORDER BY updated_at DESC cursor.execute(sql, (user_id,)) return cursor.fetchall()这里用了 MySQL 的ON DUPLICATE KEY UPDATE。当同一个用户、同一个实体、同一个属性重复写入时直接用新值覆盖旧值同时保留更新时间和来源会话。这是一种简单有效的冲突解决方式。6.6 在对话流程中使用用户画像# demo/demo_summary_memory.py from config import Config from storage.mysql_store import MySQLFactStore from strategies.extraction import extract_facts fact_store MySQLFactStore( hostlocalhost, port3306, userroot, passwordyour_password, databaseagent_memory, ) def build_user_context(user_id: str) - str: facts fact_store.get_user_facts(user_id) if not facts: return 暂无该用户的历史信息。 lines [f- {f[entity]}的{f[attribute]}是{f[fact_value]} for f in facts] return \n.join(lines) def chat_with_memory(user_id: str, user_input: str) - str: # 1. 构建用户画像上下文 context build_user_context(user_id) prompt f 已知用户历史信息 {context} 用户当前输入{user_input} 请结合历史信息回答用户问题。 response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], ) answer response.choices[0].message.content # 2. 抽取本轮新增事实并入库 conversation f用户: {user_input}\n助手: {answer} facts extract_facts(conversation) for fact in facts: fact_store.save_fact(user_id, fact, session_idcurrent_session) return answer运行后我们可以做两次独立会话验证。第一次输入“我是某某公司的采购负责人”第二次换个 session 再问“我之前提过我的职位吗”只要 user_id 不变Agent 就能正确回答。6.7 方案的优势与局限优势是信息结构化查询效率高能跨会话使用。局限也比较明显事实抽取依赖 LLM会有一定延迟和成本同时它只能捕获明确的实体关系型信息对于“用户曾经提到过某个模糊的概念”这类语义记忆无能为力。于是就有了第三个实战向量语义记忆。7. 实战三基于向量检索的语义记忆系统7.1 场景说明当用户说“我上次提到过一个很困扰我的数据库性能问题你还记得吗”这种表述没有明确的实体和属性很难用结构化字段表达。但它有清晰的语义指向适合用向量检索来解决。语义记忆的核心流程是把用户对话内容切分为记忆片段。用 Embedding 模型把片段转换为向量。存入向量数据库。用户提问时把问题转换为向量召回最相近的历史片段。7.2 搭建向量存储这里以 ChromaDB 为例适合本地开发和中小型项目。企业级场景可以替换为 Milvus 或云厂商向量数据库接口设计上保持统一即可。# storage/vector_store.py import chromadb from chromadb.utils import embedding_functions class VectorMemoryStore: def __init__(self, persist_directory: str ./chroma_data): self.client chromadb.PersistentClient(pathpersist_directory) # 使用 OpenAI Embedding也可替换为本地模型 self.embedding_func embedding_functions.OpenAIEmbeddingFunction( api_keyyour_api_key, model_nametext-embedding-3-small ) self.collection self.client.get_or_create_collection( nameagent_memory, embedding_functionself.embedding_func, metadata{hnsw:space: cosine}, ) def add_memory(self, memory_id: str, text: str, user_id: str, metadata: dict None): 写入一条记忆片段。 memory_id 建议使用 UUID避免重复。 meta {user_id: user_id} if metadata: meta.update(metadata) self.collection.add( ids[memory_id], documents[text], metadatas[meta], ) def search_memory(self, query: str, user_id: str, top_k: int 3): 按语义相似度召回记忆。 通过 user_id 过滤避免不同用户之间互相干扰。 results self.collection.query( query_texts[query], n_resultstop_k, where{user_id: user_id}, ) return results[documents][0] if results[documents] else []这里有两个关键点where{user_id: user_id}实现了用户维度隔离这是企业级应用绝不能省略的过滤条件。ChromaDB 使用的text-embedding-3-small是 OpenAI 的 Embedding 模型如果你在境内网络环境不方便调用可以替换为本地模型。后面会给出替代方案。7.3 本地或国产 Embedding 替代方案如果你的项目不能依赖 OpenAI Embedding 接口可以使用 HuggingFace 的sentence-transformers模型。下面是一个替换示例思路是自建 Embedding 函数# storage/local_embedding.py from chromadb.utils import embedding_functions class LocalEmbeddingFunction: def __init__(self, model_name: str BAAI/bge-small-zh-v1.5): from sentence_transformers import SentenceTransformer self.model SentenceTransformer(model_name) def __call__(self, input): if isinstance(input, str): input [input] embeddings self.model.encode(input, normalize_embeddingsTrue) return embeddings.tolist()使用方式embedding_func LocalEmbeddingFunction(BAAI/bge-small-zh-v1.5)bge-small-zh-v1.5是目前中文场景下效果较好且模型体积较小的 Embedding 模型适合作为本地方案。注意需要先在服务器上下载模型文件首次运行耗时较长。7.4 记忆切分策略向量检索的精度很大程度上取决于“记忆片段”的切分方式。切分太粗一个片段包含多个主题检索时噪声大切分太细语义不完整召回内容碎片化。比较推荐的做法是按对话轮次拆分每一轮作为一个候选片段。如果单轮内容过长再按段落或句子拆分。写入时给每个片段补充来源会话 ID、时间戳、主题标签。一个简单的切分实现# strategies/splitter.py import re def split_conversation_to_segments(conversation: str, max_length: int 200) - list: # 先按换行分割 paragraphs re.split(r\n, conversation) segments [] current for para in paragraphs: if len(current) len(para) max_length and current: segments.append(current.strip()) current para else: current \n para if current else para if current.strip(): segments.append(current.strip()) return segments7.5 完整运行示例# demo/demo_vector_memory.py import uuid from storage.vector_store import VectorMemoryStore from openai import OpenAI memory_store VectorMemoryStore() client OpenAI() def chat_with_vector_memory(user_id: str, user_input: str) - str: # 1. 检索相关历史记忆 memories memory_store.search_memory(user_input, user_id, top_k3) memory_text \n.join([f- {m} for m in memories]) if memories else 无相关历史记忆 prompt f 用户历史相关记忆 {memory_text} 用户当前问题{user_input} 请结合历史记忆回答。 response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], ) answer response.choices[0].message.content # 2. 将本轮对话写入记忆库 conversation f用户: {user_input}\n助手: {answer} segments split_conversation_to_segments(conversation) for seg in segments: memory_store.add_memory( memory_idstr(uuid.uuid4()), textseg, user_iduser_id, metadata{source: chat}, ) return answer运行这个 Demo你会发现在一个新的 session 中只要 user_id 不变 Agent 就能通过语义相关性找回历史记忆。即使提问方式不同比如用户之前说“数据库查询特别慢”之后问“我上次说的那个性能问题你还记得吗”也能命中。7.6 向量方案的三个注意点第一Embedding 模型需要和业务场景匹配。中文场景优先选择中文语料训练的模型比如 bge 系列。第二向量库不是万能的对精确匹配要求高的场景比如“用户手机号是多少”结构化数据库仍然是更合适的方案。第三向量检索结果需要做重排和过滤不能盲目相信相似度分数。8. 常见问题与排查思路8.1 存入 Redis 的消息读取顺序混乱问题现象Agent 回答时历史消息顺序错乱先看到新消息后看到旧消息。原因分析Redis 的LRANGE返回顺序是从左到右如果写入时多次混用LPUSH和RPUSH顺序就会不一致。解决思路统一使用RPUSH追加统一使用LRANGE key 0 -1读取。不要混用。8.2 摘要抽取时模型返回非法 JSON问题现象json.loads()报错模型返回的内容里夹杂了说明文字。原因分析部分模型在长文本场景下不严格遵守 JSON 输出格式可能包含 markdown 代码块标记或多于一个 JSON 对象。解决思路在 prompt 中强调“只输出 JSON”代码里做好清理和容错失败时跳过本轮抽取不要影响主流程。8.3 向量检索结果相关性差问题现象召回的“历史记忆”和当前问题在语义上完全不相关。原因分析可能是 Embedding 模型不适合当前语种也可能是记忆片段切分粒度过大或过小。解决思路先检查检索时是否传入了正确user_id再用几个标准问题做召回评测最后尝试更换 Embedding 模型或调整切分长度。8.4 记忆系统的常见问题汇总问题现象常见原因解决思路跨会话仍失忆没做持久化只用了内存或 Redis 短期缓存引入 MySQL 或向量库保存记忆回答中泄露其他用户信息检索时缺少用户维度过滤所有查询强制带user_id条件token 消耗过高历史消息全部塞入 prompt压缩摘要、只检索 top N 条记忆用户信息更新后仍用旧值冲突处理策略缺失使用时间戳 is_active字段处理记忆库膨胀过快所有对话无差别写入增加信息过滤器只保留高价值记忆多轮对话截断窗口超过模型上限将最早对话做摘要保留最新原文8.5 建议排查顺序如果你发现自己搭建的记忆系统表现不符合预期可以按以下顺序排查先确认存储层有没有写入数据。最简单的验证方式是直接查库。再确认检索层有没有取到数据。打印检索结果看召回内容是否合理。其次确认 prompt 组装是否正确。把最终发送给模型的消息打印出来。最后确认模型对记忆内容的理解是否正确。同一个 prompt 用不同模型测试排除模型能力差异。很多时候问题不是出在模型而是出在“存进去的内容检索不出来”或“检索出来的内容没拼进 prompt”。这两条链路查清楚大部分 Bug 就能定位。9. 工程化最佳实践9.1 记忆分层不要一个方案打天下从本文三个实战可以看出不同记忆类型适合不同的存储方案。企业级项目建议按下面方式分层短期会话记忆Redis设置 TTL。结构化用户画像MySQL / PostgreSQL字段设计明确。语义记忆片段向量数据库按用户隔离。归档冷数据对象存储或数据仓库定期从在线库同步。9.2 写入策略要克制记忆不是越多越好。每写入一条记忆都要考虑它对后续检索的增益。建议设定明确的写入规则只有用户主动陈述或明确回复的信息才值得写入。寒暄、临时性指令、情绪化表达不写入。信息抽取结果低于置信度阈值时不写入。写入操作可以做成异步队列避免阻塞主对话链路。9.3 安全与隐私是第一优先级长期记忆系统存储的是用户的隐私数据必须严格遵守以下几点用户 ID 维度隔离任何查询都必须带过滤条件。敏感字段写入前做脱敏或加密。记忆数据需要提供删除接口用户要求删除时必须能物理删除或标记删除。生产环境变更、批量清理、结构修改操作必须先备份再在测试环境验证。记忆服务自身需要审计日志记录谁在什么时间写入了什么记忆。9.4 可观测性与评估体系记忆系统是典型的“低频正确性”系统不能用“能跑起来”作为唯一标准。建议建立最小评估集准备一组“需要记忆”的测试对话。每次会话结束后验证记忆是否被正确提取。定义几个固定的召回测试问题每周跑一次跟踪检索命中率。记录 token 消耗和检索耗时评估成本与性能。如果检索命中率明显下降优先检查近期是否更换了 Embedding 模型或调整了切分策略。9.5 避免过度设计最后一条建议是不要为了“企业级”而堆组件。如果项目刚起步直接用一个 MySQL 表加一个 Redis 缓存就能覆盖需求。等用户量和数据量上来后再逐步引入向量库、消息队列和分布式存储。记忆系统的难点从来不在组件多而在于数据链路设计是否正确。10. 总结与下一步本文围绕“AI Agent 总是失忆”这个痛点完成了三件事。第一从概念上梳理了短期记忆、长期记忆和工作记忆的区别。第二从架构上给出了记忆写入、检索、更新、遗忘的完整链路设计。第三通过三个可运行的实战案例分别实现了基于 Redis 的对话记忆、基于 LLM 摘要的结构化事实记忆、基于向量检索的语义记忆。下一步如果你想继续深入可以重点关注这几个方向用 LangChain 的 Memory 模块简化开发但要注意它默认实现仍是窗口拼接生产环境需要自定义持久化。研究记忆评估与自动优化比如通过用户反馈来调整记忆写入策略。探索多智能体场景下的共享记忆也就是不同 Agent 之间如何安全地共享同一套记忆库。企业中落地 Agent 记忆系统优先保证两点数据安全和检索准确。只要这两点稳住Agent 的“失忆”问题就能解决大半。如果你也在做 AI Agent 开发建议从本文的实战一跑起来改造成本不高也能帮助你更快理解记忆系统的完整链路。如果这篇文章对你有帮助可以收藏备用后续实战中遇到问题也欢迎在评论区交流。