AI Agent 工程实践(10):Memory 不只是聊天记录——长期记忆架构设计

AI Agent 工程实践(10):Memory 不只是聊天记录——长期记忆架构设计
发布时间2026-07-12标签AI AgentLLMMemory向量数据库架构设计工程实现系列导航上一篇AI Agent 工程实践09AI Engineering OS v2——系统如何持续演化下一篇AI Agent 工程实践11Workflow——Agent 如何完成复杂任务本文是 [AI Agent 工程实践] 系列的第 10 篇第二季 · 工程实现。第 03 篇讲清楚了一件事记忆必须分层。Conversation → Working → Long → Archive单向流动定期回收。但讲完那篇后我开始动工实现立刻撞上一个问题长期记忆到底怎么存、怎么检索我把所有东西塞进一个 JSON 文件。10MB 以后查一次要 3 秒。换成向量数据库结果召回了半年前随口聊的天气却没召回上个月的关键架构决策。我终于意识到存对话 ≠ 有记忆检索 ≠ 理解。Memory 的问题从来不在存得下而在找得到对的东西。第 03 篇解决记什么这一篇解决怎么记、怎么找。从架构到落地。本文你将学到✓ 长期记忆的四种分类Working / Episodic / Semantic / Procedural✓ Memory Store 的双通道设计结构化过滤 语义检索✓ Memory Manager 的完整架构User → Conversation → Memory Manager → Vector DB✓ 三大核心流程写入怎么存→ 检索怎么找→ 蒸馏怎么压适合阅读✓ 读完了第 03 篇、想把 Memory 真正实现出来的开发者✓ 在选型向量数据库Chroma/Milvus/Pinecone做 Agent Memory 的人✓ 被检索噪声召回不准困扰的人问题背景第 03 篇 定义了四层记忆和生命周期。但那篇是设计层——它回答该记什么、记多久。到实现层问题变成了怎么存、怎么找。市面上的Agent Memory方案基本是两种极端一端是全存数据库。把所有对话存 PostgreSQL按时间倒序取最近 N 条。问题很明显关键记忆是语义相关不是时间相关——昨天关于代码规范的决定比今天早上的寒暄重要 100 倍。按时间检索等于在倒垃圾。另一端是纯向量检索。把所有内容 embed 成向量塞进 Chroma搜什么就返回相似的 top-K。问题是语义相似 ≠ 记忆相关。用户喜欢简洁风格和用户今天说要简洁语义高度相似但前者是长期偏好要记住后者是一次性对话该丢弃。纯向量检索分不出这两者。两种方案的共同缺陷没有对记忆本身做分类没有对检索做分层。于是长期记忆变成了一个 dump——什么东西都往里扔搜全靠运气。错误尝试第一次全存 Markdown 文件按日期索引最原始的做法——一个memory/目录按日期存 markdown要查什么就全文搜索。结果10MB 以后搜索变慢。更致命的是它只能做字面匹配。数据库设计搜不到Schema 定义代码风格搜不到命名规范。记忆不是日志字面搜索等于没有搜索。第二次纯向量检索靠相似度召回把每条记忆 embed 成向量搜的时候语义匹配返回 top-10。结果一时对话回来了长期偏好也回来了噪声也回来了。用户喜欢 Test-Driven Development和用户问 TDD 是什么向量距离极近但前者是长期偏好该记后者是一次性问答该丢。语义相似掩盖了记忆类型差异——向量检索看不出该不该记只看出字面像不像。两次尝试指向同一个教训Memory 的检索不能只有一个通道。它需要结构化过滤筛掉不该记的再叠加语义检索找到该找的。先分类型再谈检索。关键观察我把每次真正被用到的记忆和其存储特征做了对照记忆类型例子检索触发特征应该用什么检索Working当前任务报错信息当前会话不检索直接注入Episodic情节上周三修过一个类似 bug时间范围 关键词结构化过滤 语义Semantic语义本项目用 FastAPI概念匹配语义检索为主Procedural流程部署前先跑 pre-commit任务类型触发结构化过滤tag/scopepie title 纯向量检索的召回质量问题 真正相关 : 25 语义相似但类型不匹配 : 40 完全不相关噪声 : 35存对话 ≠ 有记忆检索 ≠ 理解。一段对话存进数据库叫日志一段对话被正确分类、能在正确时机被召回才叫记忆。差距不在存储在分类和检索策略。定位真正原因现有方案的共同缺陷把存储和检索当成一个平面操作——什么类型的记忆都存进同一个空间、用同一种方式搜。正确架构应该先分层第 03 篇已定义再给每层设计对应的存储策略和检索策略。于是 Memory 的实现问题拆成三个子问题怎么分类——每条记忆属于什么类型Episodic/Semantic/Procedural怎么存——不同类型走不同的存储路径结构化字段 向量双写怎么检索——先结构化过滤缩小范围再语义检索精准命中这正是 Memory Manager 要做的事。最终方案Memory 分类体系 Memory Manager 架构Memory 四种分类03 篇按生命周期分层10 篇按内容性质分类——两层互补不矛盾分类是什么例子存储策略Working Memory当前任务上下文正在改的文件、当前报错不落盘会话级注入Episodic Memory具体事件/经历上周三修过一个 async bug结构化时间/tag 向量Semantic Memory抽象知识/事实本项目用 FastAPI 和 asyncpg向量为主结构化 scopeProcedural Memory流程/方法部署前先跑 pre-commit结构化trigger/task_type关键不是所有记忆都该进向量库。Procedural 靠 trigger 触发就行不需要语义检索Episodic 需要时间范围 关键词 语义三管齐下Semantic 语义检索为主但要有 scope 过滤区分项目/用户。Memory Store双通道存储每条记忆入库时同时写两个通道# 一条记忆的存储结构 id: mem_20260712_001 type: semantic # 分类标签 → 用于结构化过滤 scope: project # user / project → 用于隔离 content: 本项目使用 FastAPI asyncpg 作为后端栈 embedding: [0.12, -0.34, ...] # 向量 → 用于语义检索 metadata: created_at: 2026-07-12T10:00:00 last_hit: 2026-07-12T14:00:00 hit_count: 5 # 命中次数 → 用于蒸馏/优先级 source: conversation # 来源 → conversation / user_explicit双通道意味着检索时先走结构化过滤typesemantic AND scopeproject缩小候选集再走向量语义匹配。过滤砍掉噪声语义找到精准。Memory Manager 架构这是你给的核心链路——User → Conversation → Memory Manager → Vector DBMemory Manager 的三大流程写入流程会话结束时Conversation 的内容被蒸馏——提取出可能值得长期记的信息分类器判断类型Episodic/Semantic/Procedural双写结构化字段进 PostgreSQLembedding 进向量库检索流程Agent 需要记忆时先构造检索条件typesemantic, scopeproject结构化过滤——缩小候选集语义匹配——在候选集里找最相关的 top-K蒸馏流程定期周复盘检查每条记忆的hit_count和last_hit长期未命中 → 降级到 Archive03 篇定义语义重复 → 合并保留更新版本实际收益指标全量向量检索Memory Manager过滤检索检索精度~25%噪声多显著提升先过滤召回速度慢全库扫快候选集小噪声来源类型不匹配过期基本消除过滤蒸馏为主观估算非严格 A/B Test但反映了双通道对检索质量的结构性改善。代码或配置示例Memory Store 表结构PostgreSQL 端-- 存储记忆元数据和结构化字段 CREATE TABLE memory_store ( id TEXT PRIMARY KEY, type TEXT NOT NULL, -- episodic / semantic / procedural scope TEXT NOT NULL, -- user / project content TEXT NOT NULL, tags TEXT[], -- 结构化标签 [fastapi, backend] hit_count INT DEFAULT 0, last_hit TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_type_scope ON memory_store(type, scope); CREATE INDEX idx_tags ON memory_store USING GIN(tags);Memory Manager 核心逻辑伪代码class MemoryManager: def __init__(self, pg_store, vector_store): self.pg pg_store # 结构化存储 self.vec vector_store # 向量存储 def write(self, memory: Memory) - None: 写入分类 → 双写 memory.type self.classifier.classify(memory.content) # 双通道写入 pg_id self.pg.insert(memory.to_metadata()) self.vec.insert(idpg_id, embeddingembed(memory.content)) def retrieve(self, query: str, filters: dict, top_k: int 5) - list: 检索先过滤 → 再语义 # 1. 结构化过滤缩小候选集 candidates self.pg.filter( typefilters.get(type), scopefilters.get(scope), tagsfilters.get(tags), time_rangefilters.get(time_range), ) # 2. 语义检索在候选集内匹配 query_vec embed(query) results self.vec.search(query_vec, idscandidates.ids, top_ktop_k) # 3. 更新命中统计 for r in results: self.pg.increment_hit(r.id) return results def distill(self, stale_days: int 30) - None: 蒸馏清理过期 合并重复 stale self.pg.get_stale(daysstale_days) for m in stale: self.archive.move(m) # 长期未命中 → 归档 self.pg.merge_duplicates() # 语义重复 → 合并三段代码拼起来就是 Memory Manager 的全部核心逻辑写的时候双通道分发查的时候先过滤后语义维护的时候按命中率蒸馏。设计权衡候选方案优点缺点为什么不选纯关系型数据库结构化查询强无语义检索只能字面匹配记忆是语义场景字面匹配遗漏率太高纯向量数据库语义检索强噪声多、无结构化过滤语义相似掩盖类型差异召回质量崩双通道PG Vector DB过滤精准语义灵活需维护两套存储选择理由先过滤再检索唯一兼顾准和全的方案双通道不是银弹。如果记忆量很小100 条一个 JSON 文件加全文搜索就够。双通道的价值在记忆量大、类型多时——过滤层缩减候选集语义层精准命中。和第 08 篇一样规模不到别过度设计。总结✅ 第 03 篇解决记什么本篇解决怎么存怎么检索——Memory 的实现依赖于正确分类和检索策略。✅ Memory 分四种Working不落盘/ Episodic事件/ Semantic知识/ Procedural流程各自有不同的存储和检索策略。✅ Memory Store 双通道PostgreSQL 管结构化过滤type/scope/tagVector DB 管语义检索先过滤再检索。✅ Memory Manager 三大流程写入分类→双写→ 检索过滤→语义→ 蒸馏命中率清理。✅ 架构链路User → Conversation → Memory Manager分类/检索/蒸馏→ Memory Store双通道→ Agent。参考资料第 03 篇Memory 设计——Agent 到底应该记住什么→ 记忆四层/生命周期本篇的记什么前置MemGPT: Towards LLMs as Operating Systems→ 分层记忆 分页调度本文分类体系的学术参照Chroma / Milvus 官方文档→ 向量数据库选型与检索策略参考Generative Agents (Park et al., 2023)→ Memory Stream 检索/反思机制蒸馏逻辑的来源系列导航上一篇AI Agent 工程实践09AI Engineering OS v2——系统如何持续演化下一篇AI Agent 工程实践11Workflow——Agent 如何完成复杂任务本文是 [AI Agent 工程实践] 系列的第 10 篇第二季 · 工程实现。