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

发布时间:2026-07-12
标签:AI Agent|LLM|Memory|向量数据库|架构设计|工程实现


系列导航

上一篇:AI Agent 工程实践(09):AI Engineering OS v2——系统如何持续演化?
下一篇:AI Agent 工程实践(11):Workflow——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/scope)

pie title 纯向量检索的召回质量问题
    "真正相关" : 25
    "语义相似但类型不匹配" : 40
    "完全不相关噪声" : 35

存对话 ≠ 有记忆,检索 ≠ 理解。

一段对话存进数据库叫"日志",一段对话被正确分类、能在正确时机被召回,才叫"记忆"。差距不在存储,在分类和检索策略

定位真正原因

现有方案的共同缺陷:把"存储"和"检索"当成一个平面操作——什么类型的记忆都存进同一个空间、用同一种方式搜。

正确架构应该先分层(第 03 篇已定义),再给每层设计对应的存储策略和检索策略。于是 Memory 的实现问题拆成三个子问题:

  1. 怎么分类——每条记忆属于什么类型(Episodic/Semantic/Procedural)
  2. 怎么存——不同类型走不同的存储路径(结构化字段 + 向量双写)
  3. 怎么检索——先结构化过滤缩小范围,再语义检索精准命中

这正是 Memory Manager 要做的事。


最终方案:Memory 分类体系 + Memory Manager 架构

Memory 四种分类

03 篇按"生命周期"分层,10 篇按"内容性质"分类——两层互补,不矛盾:

分类是什么例子存储策略
Working Memory当前任务上下文正在改的文件、当前报错不落盘,会话级注入
Episodic Memory具体事件/经历"上周三修过一个 async bug"结构化(时间/tag)+ 向量
Semantic Memory抽象知识/事实"本项目用 FastAPI 和 asyncpg"向量为主,结构化 scope
Procedural 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

双通道意味着:检索时先走结构化过滤(type=semantic AND scope=project),缩小候选集,再走向量语义匹配。过滤砍掉噪声,语义找到精准。

Memory Manager 架构

这是你给的核心链路——User → Conversation → Memory Manager → Vector DB:

Memory Manager 的三大流程:

写入流程

  1. 会话结束时,Conversation 的内容被蒸馏——提取出"可能值得长期记"的信息
  2. 分类器判断类型(Episodic/Semantic/Procedural)
  3. 双写:结构化字段进 PostgreSQL,embedding 进向量库

检索流程

  1. Agent 需要记忆时,先构造检索条件(type=semantic, scope=project
  2. 结构化过滤——缩小候选集
  3. 语义匹配——在候选集里找最相关的 top-K

蒸馏流程

  1. 定期(周复盘):检查每条记忆的 hit_countlast_hit
  2. 长期未命中 → 降级到 Archive(03 篇定义)
  3. 语义重复 → 合并,保留更新版本

实际收益

指标全量向量检索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(id=pg_id, embedding=embed(memory.content))

    def retrieve(self, query: str, filters: dict, top_k: int = 5) -> list:
        """检索:先过滤 → 再语义"""
        # 1. 结构化过滤:缩小候选集
        candidates = self.pg.filter(
            type=filters.get("type"),
            scope=filters.get("scope"),
            tags=filters.get("tags"),
            time_range=filters.get("time_range"),
        )
        # 2. 语义检索:在候选集内匹配
        query_vec = embed(query)
        results = self.vec.search(query_vec, ids=candidates.ids, top_k=top_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(days=stale_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/tag),Vector 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 工程实践(09):AI Engineering OS v2——系统如何持续演化?
下一篇:AI Agent 工程实践(11):Workflow——Agent 如何完成复杂任务?
本文是 [AI Agent 工程实践] 系列的第 10 篇(第二季 · 工程实现)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值