AI Agent 记忆系统设计:面试必考!三层记忆架构从原理到 LangGraph 实战
AI Agent 记忆系统设计
面试必考!三层记忆架构
从原理到 LangGraph 实战
Buffer Memory · Entity Memory · 向量长期记忆 · LangGraph 状态图 · 生产级接入方案全解析
🎯 高频面试题精选(答案在后文)
📌 为什么 Agent 记忆系统是核心基础设施
大语言模型本身是无状态的——每次调用都是全新的,它不知道你昨天告诉它什么,也不记得三轮前的决策。一个没有记忆系统的 Agent 就像每隔几分钟失忆一次的助手,无法完成任何复杂的长程任务。
即使是 128K 的超长上下文,在实际工程中也面临三大问题:① Token 成本爆炸(每轮传入全量历史);② 注意力漂移(模型在极长上下文中容易遗漏中间信息,”Lost in the Middle”问题);③ 跨会话无法延续(重启后历史消失)。因此,设计结构化的记忆系统是构建生产级 Agent 的必修课。
🏗️ 三层记忆架构全景图
短期会话记忆
管理当前对话轮次,直接拼入 prompt。Buffer(原文)、Window(滑窗)、Summary(压缩)三种策略按需选择。
实体记忆
从对话中抽取结构化实体(人名、偏好、任务状态)并持久化。跨会话后仍能”记住你是谁”。
语义长期记忆
历史对话/经验向量化存入向量库,查询时语义检索最相关片段注入上下文,支持百万级记忆规模。
⚡ Layer 1:短期会话记忆深度解析
三种策略对比
| 策略 | 原理 | Token消耗 | 信息完整性 | 适用场景 |
|---|---|---|---|---|
| BufferMemory | 完整保留全部历史 | O(n)线性增长 | 100% | 短会话(<20轮) |
| WindowMemory | 只保留最近 K 轮 | O(1)固定 | 近期信息完整 | 客服、实时问答 |
| SummaryMemory | LLM 压缩旧历史为摘要 | O(log n) | 摘要有损 | 长会话、调研助手 |
| SummaryBufferMemory | 近K轮原文 + 旧历史摘要 | 接近O(log n) | 近期完整 | 生产推荐 ✓ |
SummaryBufferMemory 代码实战
from langchain.memory import ConversationSummaryBufferMemory from langchain_openai import ChatOpenAI from langchain.chains import ConversationChain # 初始化 LLM llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # SummaryBufferMemory:近 2000 Token 保留原文,超出则压缩为摘要 memory = ConversationSummaryBufferMemory( llm=llm, max_token_limit=2000, # 超过此阈值触发摘要压缩 memory_key="chat_history", return_messages=True, human_prefix="User", ai_prefix="Assistant" ) # 构建对话链 chain = ConversationChain( llm=llm, memory=memory, verbose=True ) # 模拟多轮对话 response1 = chain.predict(input="我是小明,我在开发一个电商平台") response2 = chain.predict(input="我主要负责后端 Java Spring Boot 模块") response3 = chain.predict(input="现在帮我设计用户订单状态机") # 查看当前记忆内容 print("== 当前记忆摘要 ==") print(memory.moving_summary_buffer) # 已压缩的历史摘要 print("== 近期原文记忆 ==") for msg in memory.chat_memory.messages: print(f"{msg.type}: {msg.content[:100]}")
🧩 Layer 2:实体记忆(Entity Memory)
实体记忆的核心思想是从对话中抽取结构化信息并持久化,而不是保留原始对话文本。它能跨会话记住”用户叫什么名字、有什么偏好、当前任务状态”等关键实体信息。
实体记忆工作原理
- 每轮对话后,调用 LLM 对当前轮内容进行实体抽取(人名、组织、偏好、状态等)
- 将抽取的实体以 KV 格式更新到实体存储(内存字典 / Redis / 数据库)
- 下轮对话时,将相关实体描述注入 System Prompt,让模型”知道”用户背景
- 随着对话积累,实体信息持续增量更新,无需传入全量历史
Entity Memory + Redis 持久化实战
import json import redis from langchain_openai import ChatOpenAI from langchain.memory import ConversationEntityMemory from langchain.memory.entity import RedisEntityStore # Redis 实体存储后端(生产推荐,支持 TTL 和持久化) redis_client = redis.Redis(host="localhost", port=6379, db=0) entity_store = RedisEntityStore( redis_client=redis_client, session_id="user_alice_session", # 按用户隔离 key_prefix="entity", ttl=86400 * 30 # 实体信息保留30天 ) llm = ChatOpenAI(model="gpt-4o-mini") memory = ConversationEntityMemory( llm=llm, entity_store=entity_store, memory_key="chat_history", input_key="input", k=5, # 注入最近5条相关实体 return_messages=True ) # 第一次会话:建立用户画像 memory.save_context( {"input": "我叫 Alice,是字节跳动的 Java 工程师,负责推荐系统"}, {"output": "好的 Alice,很高兴认识你!推荐系统有什么我可以帮你的?"} ) # 查看抽取到的实体 entities = entity_store.mget(["Alice", "字节跳动"]) for k, v in zip(["Alice", "字节跳动"], entities): print(f"{k}: {v}") # 输出示例: # Alice: Java工程师,就职于字节跳动,负责推荐系统开发 # 字节跳动: Alice的雇主,互联网公司 # === 三天后,新会话 === # 重新加载同一 session_id 的 entity_store,实体信息仍然存在 new_memory = ConversationEntityMemory( llm=llm, entity_store=RedisEntityStore( redis_client=redis_client, session_id="user_alice_session" # 相同 session_id ) ) # Agent 仍然"记得"Alice是Java工程师,负责推荐系统 result = new_memory.load_memory_variables({"input": "帮我优化一下推荐算法"})
🌊 Layer 3:长期语义记忆(Vector Memory)
语义记忆是 Agent 记忆系统的”硬盘”——它能存储大量历史经验,并在需要时通过语义相似度检索召回最相关的片段注入上下文。这让 Agent 能真正”积累经验”而非每次都从零开始。
语义记忆读写流程
基于 Qdrant 的向量记忆系统实战
import time from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct, Filter, FieldCondition, Range from openai import OpenAI import uuid openai_client = OpenAI() qdrant = QdrantClient(url="http://localhost:6333") # 创建记忆集合 COLLECTION = "agent_memory" qdrant.recreate_collection( collection_name=COLLECTION, vectors_config=VectorParams(size=1536, distance=Distance.COSINE) ) def embed(text: str) -> list[float]: """调用 OpenAI text-embedding-3-small""" resp = openai_client.embeddings.create( model="text-embedding-3-small", input=text ) return resp.data[0].embedding def save_memory(user_id: str, text: str, importance: float = 1.0): """将一条记忆写入向量库""" vector = embed(text) qdrant.upsert( collection_name=COLLECTION, points=[PointStruct( id=str(uuid.uuid4()), vector=vector, payload={ "user_id": user_id, "text": text, "timestamp": time.time(), # Unix 时间戳,用于时间衰减 "importance": importance # 0~1,重要性评分 } )] ) def retrieve_memory(user_id: str, query: str, top_k: int = 5) -> list[dict]: """检索最相关记忆,结合语义相似度与时间衰减""" vector = embed(query) results = qdrant.search( collection_name=COLLECTION, query_vector=vector, query_filter=Filter( must=[FieldCondition( key="user_id", match={"value": user_id} # 用户级隔离 )] ), limit=top_k * 3, # 多取一些,后续重排序 with_payload=True ) now = time.time() scored = [] for r in results: age_days = (now - r.payload["timestamp"]) / 86400 # 时间衰减:7天后相关性减半,利用指数衰减 time_decay = 0.5 ** (age_days / 7) final_score = r.score * 0.7 + time_decay * 0.2 + r.payload["importance"] * 0.1 scored.append({"text": r.payload["text"], "score": final_score}) scored.sort(key=lambda x: x["score"], reverse=True) return scored[:top_k] # 写入几条历史记忆 save_memory("alice", "Alice偏好使用 Java 开发后端,擅长 Spring Boot 和 Kafka", importance=0.9) save_memory("alice", "Alice在2026年3月完成了推荐系统 A/B 测试,CTR 提升了12%", importance=0.8) save_memory("alice", "Alice的团队正在迁移到 K8s,遇到了 Ingress 配置问题", importance=0.7) # 检索相关记忆 memories = retrieve_memory("alice", "帮我设计一个高并发订单服务") context = "\n".join([m["text"] for m in memories]) print("注入的记忆上下文:\n", context)
🕸️ LangGraph 三层记忆完整集成实战
LangGraph 通过共享 State Schema 天然支持记忆的读写分离和跨节点访问。以下是一个集成三层记忆的 Agent 完整代码骨架:
from typing import Annotated, TypedDict, Optional from langgraph.graph import StateGraph, END from langgraph.checkpoint.sqlite import SqliteSaver from langchain_core.messages import BaseMessage, HumanMessage, AIMessage import operator # ── State Schema:三层记忆统一管理 ── class AgentState(TypedDict): # Layer 1: 当前会话消息列表(LangGraph 自动追加) messages: Annotated[list[BaseMessage], operator.add] # Layer 2: 实体记忆(结构化 KV) entities: dict[str, str] # {"Alice": "Java工程师...", "项目X": "..."} # Layer 3: 语义记忆检索结果(注入当前轮) retrieved_memories: list[str] # 用户标识(用于记忆隔离) user_id: str # ── 节点1:从向量库检索相关记忆 ── def retrieve_memories_node(state: AgentState) -> dict: latest_msg = state["messages"][-1].content memories = retrieve_memory(state["user_id"], latest_msg, top_k=3) return {"retrieved_memories": [m["text"] for m in memories]} # ── 节点2:LLM 推理(注入三层记忆)── def agent_node(state: AgentState) -> dict: # 构建 System Prompt(融合三层记忆) entity_ctx = "\n".join([f"{k}: {v}" for k, v in state["entities"].items()]) semantic_ctx = "\n".join(state["retrieved_memories"]) system_prompt = f"""你是一个专业的 AI 助手。 【实体记忆(用户背景)】 {entity_ctx} 【相关历史经验】 {semantic_ctx} 请结合以上背景信息回答用户问题。""" # 调用 LLM(此处省略具体实现) response = llm.invoke([{"role": "system", "content": system_prompt}] + state["messages"]) return {"messages": [AIMessage(content=response.content)]} # ── 节点3:更新记忆(对话后写入)── def update_memory_node(state: AgentState) -> dict: # 异步写入语义记忆(最近一轮对话) last_human = next(m for m in reversed(state["messages"]) if isinstance(m, HumanMessage)) last_ai = next(m for m in reversed(state["messages"]) if isinstance(m, AIMessage)) memory_text = f"用户问:{last_human.content}\n助手答:{last_ai.content[:200]}" save_memory(state["user_id"], memory_text) return {} # ── 构建图 ── graph = StateGraph(AgentState) graph.add_node("retrieve", retrieve_memories_node) graph.add_node("agent", agent_node) graph.add_node("update_memory", update_memory_node) graph.set_entry_point("retrieve") graph.add_edge("retrieve", "agent") graph.add_edge("agent", "update_memory") graph.add_edge("update_memory", END) # 持久化 Checkpointing(支持会话恢复) checkpointer = SqliteSaver.from_conn_string("agent_memory.db") app = graph.compile(checkpointer=checkpointer) # 调用 Agent(thread_id 用于识别会话) result = app.invoke( { "messages": [HumanMessage(content="帮我设计一个高并发订单服务")], "entities": {"Alice": "Java工程师,字节跳动,推荐系统"}, "retrieved_memories": [], "user_id": "alice" }, config={"configurable": {"thread_id": "alice_thread_001"}} )
🗑️ 记忆遗忘策略:有遗忘才有智慧
无限积累记忆会导致检索噪声增加、存储成本爆炸、隐私风险。生产系统必须设计主动遗忘机制:
| 遗忘策略 | 触发条件 | 实现方式 | 适用层 |
|---|---|---|---|
| TTL 过期 | 超过保留时限 | Redis EXPIRE / Qdrant payload filter | Layer 1 / 2 |
| 重要性阈值 | importance < 0.3 | 定期扫描删除低分记忆 | Layer 3 |
| 容量上限 LRU | 记忆数量超出配额 | 维护访问时间戳,淘汰最久未用 | Layer 3 |
| 语义去重 | 余弦相似度 > 0.95 | 写入时检测近重复,合并或跳过 | Layer 3 |
| 用户主动删除 | 隐私/GDPR 请求 | 按 user_id 批量删除全部向量 | 所有层 |
🛠️ 生产工程落地建议
- 记忆分层隔离:短期记忆放进程内存/Redis(TTL 2小时),实体记忆放 Redis(TTL 30天),语义记忆放 Qdrant(永久+定期清理)。不同生命周期的数据不要混存。
- 写入异步化:每轮对话后的记忆写入(Embedding + Upsert)放入消息队列异步处理,不能阻塞主对话链路。P99 延迟要求 <500ms 时必须异步。
- 检索注入量控制:每轮注入的历史记忆建议 Top-3~5 条,总字符数不超过 1000 字符,防止 Token 超限。超出时优先保留 importance 高的。
- 用户记忆隔离:所有向量必须携带 user_id 字段,检索时强制 Filter,防止记忆越权访问(这是数据安全的底线)。
- 记忆摘要定期整合:超过 100 条的用户记忆,定期调用 LLM 做记忆整合(Memory Consolidation)——将相似记忆合并为一条高质量摘要,保持向量库的精简和高质量。
- 冷启动处理:新用户首次会话时,从注册信息/行为数据预填充实体记忆,避免”记忆冷启动”影响首次体验。
- 可观测性:记录每轮的记忆检索命中率、注入内容、最终回答质量,接入 LangFuse 进行全链路追踪,持续迭代记忆策略。
2. 向量记忆无用户隔离:多用户共用一个集合但查询时忘了加 user_id Filter,导致用户 A 看到用户 B 的历史。
3. 记忆无限增长:忘记设置 TTL 和容量上限,6个月后向量库膨胀到 100GB 运维告警。
每天一篇技术干货,助你在面试和工作中脱颖而出
关注公众号 CODER8023,获取最新技术文章
夜雨聆风