当前位置: 首页 > news >正文

AI Agent 双层记忆系统:从理论到落地

前言

做过 AI 对话应用的人都遇到过这个问题:用户昨天说"叫我小王",今天问"我是谁",模型一脸茫然。

这就是记忆系统要解决的核心问题。在 Deep Research 项目中,我设计了一套双层记忆架构——短期记忆负责当前对话上下文,长期记忆负责跨会话的用户画像与历史知识。本文完整记录设计思路与关键代码。

记忆模型

参考人脑记忆机制,将 AI 记忆分为三层:

层级类比职责生命周期后端
短期记忆工作记忆当前对话的上下文7天TTLRedis / PostgreSQL / 内存
长期-语义记忆百科知识用户画像、偏好、事实永久PostgreSQL / SQLite + Milvus
长期-情节记忆经历记忆历史任务、对话记录永久PostgreSQL / SQLite + Milvus

架构图

用户提问 │ ▼ MemoryManager.build_personalized_prompt_context() │ ├── 短期记忆(最近N条消息 + 对话摘要) ├── 长期语义记忆(Milvus向量检索 → 用户画像/偏好/事实) └── 长期情节记忆(Milvus向量检索 → 相似历史任务) │ ▼ 拼接为 [跨会话记忆] 注入 System Prompt │ ▼ Agent 处理后,persist_turn() 持久化本轮对话

短期记忆

存储结构

支持三种后端,根据环境自动选择:

def add_short_term_message(self, thread_id, message, user_id, tenant_id): payload = {"role": "human"|"ai", "content": "..."} if self.short_term_backend == "redis": key = f"ma:short:{tenant_id}:{user_id}:{thread_id}" self._redis_client.rpush(key, json.dumps(payload)) self._redis_client.expire(key, self.short_term_ttl) elif self.short_term_backend == "postgres": # INSERT INTO short_term_messages (id, tenant_id, user_id, thread_id, role, content, created_at) ... else: # 内存 dict self.short_term.add_message(thread_id, message)

摘要压缩

消息数量超阈值时自动触发 LLM 摘要压缩:

def _compress_pg_thread(self, tenant_id, user_id, thread_id): history = self._get_pg_short_term_messages(tenant_id, user_id, thread_id) if len(history) <= self.short_term_max_messages: return # 还没到阈值 ​ # 保留最近的消息,压缩旧消息 split_at = len(history) - self.short_term_summary_threshold to_summarize = history[:split_at] keep_messages = history[split_at:] ​ existing_summary = self._get_pg_short_term_summary(...) new_summary = self._summarize_text(existing_summary, to_summarize) ​ # 原子替换:删旧消息 + 保留新消息 + 更新摘要 DELETE ...; INSERT keep_messages; UPSERT summary

LLM 摘要 Prompt 设计:

def _summarize_text(self, existing_summary, history_slice): prompt = ( "你是对话压缩引擎。请在保留事实、偏好、结论、待办和约束的前提下进行递归摘要。\n" f"已有摘要:{existing_summary or '无'}\n" f"新增历史:\n{history_text}\n" "输出要求:100-300字,中文,结构紧凑。" ) response = self._summary_llm.invoke([HumanMessage(content=prompt)]) return str(response.content).strip()

关键点:摘要不是一次性生成,而是递归式——每次合并已有摘要 + 新增消息,类似 git rebase。

长期记忆

Milvus 向量检索

长期记忆的入口是向量检索。用户的提问 embedding 在 Milvus 中找最相似的历史记忆:

def _search_milvus(self, tenant_id, user_id, query, memory_type, limit=5): docs = self._milvus_store.similarity_search(query, k=max(limit * 4, 20)) entries = [] for doc in docs: metadata = doc.metadata or {} # 多租户过滤 if metadata.get("tenant_id") != tenant_id: continue if metadata.get("user_id") != user_id: continue # 类型过滤 if memory_type and metadata.get("memory_type") != memory_type: continue entries.append(MemoryEntry(...)) if len(entries) >= limit: break return entries

注意这里k = limit * 4——从 Milvus 多取一些,因为多租户和类型过滤会筛掉大量结果。如果只取 limit 个,过滤后可能一条不剩。

记忆保存

def save_fact(self, user_id, fact, category, tenant_id): memory_id = str(uuid4()) # 1. 写 PostgreSQL(结构化的元数据 + 全文搜索能力) self._insert_memory_pg(entry, summary=fact[:500]) ​ # 2. 写 Milvus(向量语义搜索) self._index_memory_milvus( text=fact, metadata={ "tenant_id": tenant_id, "user_id": user_id, "memory_id": memory_id, "memory_type": "semantic", "namespace": f"facts/{category}", }, ) return memory_id

PostgreSQL 和 Milvus双写:PG 支撑精确匹配和 ILIKE 模糊搜索,Milvus 支撑语义相似搜索。任一挂了不影响另一路。

自动记忆提取

persist_turn中检测用户是否触发了"记住我"意图:

def persist_turn(self, tenant_id, user_id, thread_id, query, answer): # 1. 保存短期记忆 self.add_short_term_messages(...) ​ # 2. 检测记忆触发词 remember_markers = ["记住", "我叫", "我的偏好", "remember", "my name is", ...] if any(marker in query.lower() for marker in remember_markers): extracted = extract_memory_from_messages([user_message]) # 提取 facts 和 preferences,分别存储 for fact in extracted["facts"]: self.save_fact(user_id, fact, category="user_fact") for pref in extracted["preferences"]: self.save_user_profile(user_id, {"preferences": [pref]}, merge=True)

上下文注入

每次用户提问前,build_personalized_prompt_context组装记忆上下文:

def build_personalized_prompt_context(self, user_id, thread_id, query, max_memories=8): context = self.get_context_for_agent(user_id, thread_id, query, max_memories) sections = [] if profile_text := context.get("user_profile"): sections.append(f"## 用户画像\n{profile_text}") if recent_text := context.get("recent_messages"): sections.append(f"## 最近对话\n{recent_text}") if summary_text := context.get("conversation_summary"): sections.append(f"## 对话摘要\n{summary_text}") if memory_text := context.get("memory_text"): sections.append(memory_text) return "\n\n".join(sections)

注入到每个节点的 prompt 中:

def with_memory_context(state, user_prompt): memory_context = state.get("memory_context", "").strip() if not memory_context: return user_prompt return f"{user_prompt}\n\n[跨会话记忆]\n{memory_context}"

追踪与调试

每次记忆注入后记录完整 trace,方便排查"为什么给我注入了这段记忆":

self._last_trace = { "query": query, "memory_count": len(memory_entries), "source_count": {"milvus": 3, "postgres": 1}, # 来自哪个检索源 "items": [ {"id": "xxx", "type": "semantic", "source": "milvus", "snippet": "..."}, ], "milvus_raw_hits": [...], # Milvus 原始命中(含被过滤的) "injected_chars": len(injected), }

用户输入/memory-trace即可查看最近一次的注入详情。

总结

  • 短期记忆:消息累积 + 递归 LLM 摘要压缩,多后端自动降级

  • 长期记忆:PG + Milvus 双写,向量检索为主、全文搜索兜底

  • 自动提取:触发词检测 + LLM 信息抽取,零手动标注

  • 可观测性:每次注入记录完整 trace,排错不靠猜

这个架构的可扩展性不错——如果想接入对话推荐、情感分析、知识图谱,只需要在新的 Agent 节点中读取memory_context即可。

http://www.gsyq.cn/news/1521178.html

相关文章:

  • 从广播到对讲机:拆解生活中FM与PM调制的真实应用场景与硬件选型
  • 从微信聊天到RabbitMQ:聊聊异步通信如何让我们的系统更“抗压”
  • 别再傻傻分不清了!华为iStack与H3C IRF堆叠实战配置对比(附避坑指南)
  • 2026年靠谱的贵州水泥管/贵州排污水泥管定制加工厂家推荐 - 品牌宣传支持者
  • IEEE论文投稿前必查:作者单位排版里的5个隐形坑(LaTeX避坑指南)
  • 从全表扫描到覆盖索引:我是怎么干掉慢查询的
  • 2026年热门的自动化配电箱/定制配电箱公司对比推荐 - 品牌宣传支持者
  • 2026年口碑好的贵州泡沫混凝土工程/屋面泡沫混凝土优质厂家推荐榜 - 行业平台推荐
  • 2026q2河北折叠门厂家评测:唐山,邢台,廊坊,河北抗爆窗/河北抗爆门/河北泄压门/资质性能与服务对比 - 优质品牌商家
  • 别再只盯着FOC了!聊聊永磁电机那些‘基本功’:V/F、DTC和矢量控制到底怎么选?
  • 从SGD到Adam:图解十大优化算法原理,5分钟搞懂各自适用场景
  • 2026年靠谱的市政排水管/贵州顶管可靠供应商推荐 - 品牌宣传支持者
  • 5个常见问题解决指南:Windows版Mesa3D图形驱动安装与故障排除
  • 别再只盯着MEMS了!聊聊谐振式加速度计:从石英晶体到高精度导航,它到底强在哪?
  • 别再傻傻分不清了!5分钟搞懂TOPS、FLOPS和FLOPs,选AI芯片和评估模型算力不求人
  • 2026年知名的贵州地下排水管/贵州检查井/道路排水管可靠供应商推荐 - 行业平台推荐
  • 从社交网络到电路分析:邻接矩阵和关联矩阵到底该怎么选?
  • TongWeb8实战:Spring Boot应用如何选择企业版、容器版还是嵌入版?
  • 积分逻辑:概率论与逻辑学的交叉应用
  • 3ds Max 2024减面实战:从‘优化’到‘多分辨率’,哪个修改器更适合你的游戏模型?
  • 2026年展览制作行业观察:谁在定义高品质展会搭建的新标准? - 优质品牌商家
  • XELFViewer终极指南:3步掌握跨平台ELF文件分析神器
  • 从手机芯片到超算:一文搞懂算力单位TOPS、TFLOPS背后的量级与实战意义
  • 别再乱选MQTT的QoS了!手把手教你根据业务场景选对等级(附性能对比)
  • Tanh还是Sigmoid?BP神经网络激活函数选择避坑指南与实战对比
  • 游戏显卡真香!实测RTX 2070在CST 2023中的GPU加速效率与成本分析
  • 从PyTorch转Rust?tch-rs、Candle、Burn、DFDX四大框架实战对比与选型指南
  • DC-DC电源PCB布局的‘静’与‘动’:深入解读MPQ8633B芯片的功率地与信号地设计奥秘
  • 2026年铁路国际货运公司深度评测:天津海纳、北京新嘉光、宝利泰等品牌实力剖析与真实案例分享 - 优质品牌商家
  • DBeaver数据库驱动全集:一站式离线解决方案的专业指南