前言
大多数 Agent Memory 设计的误区,是过早堆砌数据库、消息队列、向量引擎等中间件,从而混淆核心业务逻辑与工程优化组件。Agent Memory 的核心不是简单的一读一写接口,而是 Memory Service 的 Recall/Write 两大主业务入口;真正的工程复杂度在 Write 侧内部的记忆提取、验证、去重、冲突解决与生命周期治理。所有中间件只为让这两个入口更快、更准、更可靠、更易扩展。本文围绕核心本质、数据模型、生命周期、召回架构与落地路径展开。
一、Agent Memory 核心本质
1.1 核心运行流程
一次标准 Agent 请求,记忆模块只参与三个核心步骤:
text
用户请求 → ① Recall 读取记忆 → LLM/工具执行任务 → ② 完成任务 → ③ Write 写入/更新记忆 → 响应用户
1.2 两大主业务入口,不是全部能力
- Recall(读取记忆):检索相关历史记忆,为当前任务提供上下文依据。
- Write(写入记忆):筛选本次对话有效信息,并完成候选记忆的生成与写入。
Write 不是单次 POST /memory,它内部是一条完整闭环:
text
Memory
├── Recall ← 主业务入口
└── Write ← 主业务入口
├── Extract
├── Validate
├── Resolve
├── Dedup
├── Conflict
└── Store
因此不能把 Memory 理解成 GET /memory + POST /memory 就完成了。PostgreSQL、Redis、Milvus、OpenSearch、Kafka、Embedding、Reranker 等组件,最终都服务于这两个入口及其内部能力。
1.3 Memory Service 四大能力
- Recall(检索):精准匹配、召回相关历史记忆,优先级最高。
- Write(写入):生成候选记忆,并完成提取、验证、去重、冲突解决与持久化。
- Manage(管理):更新、删除、冲突修复、过期清理。
- 运维管控:版本管理、权限控制、操作审计。
二、各中间件核心职责
各组件各司其职,但不应以单一检索能力机械划分组件边界:PostgreSQL 作为唯一事实源负责记忆持久化与版本治理;Milvus 提供 Dense、Sparse 及 Hybrid 检索能力,承担主要语义与混合召回;Redis 负责结构化热点数据缓存;OpenSearch 作为可选的专业全文检索组件,在复杂关键词、全文分析及高级搜索需求出现时补强;Kafka 负责写入后的异步任务与索引构建解耦。
| 组件 | 职责 | 核心价值 | 落地阶段 |
|---|---|---|---|
| PostgreSQL | 唯一事实源;持久化 Memory、版本、关系、权限、审计;返回 Active Version Data | 保证记忆数据一致性与可治理性 | V1 必备 |
| Milvus | Dense Vector / Sparse Vector 索引;语义、关键词及 Hybrid Search;保存 Memory ID 与检索字段 | 统一承担主要语义/混合召回,避免重复引入搜索组件 | V2 |
| Redis | 结构化热点 Memory、Preference、最近/高频访问数据缓存 | 降低 PG 访问压力,提升高频精确查询速度 | V2/V5 |
| OpenSearch | 可选的专业全文检索;复杂关键词、短语、模糊、多字段、过滤、聚合等 | 当 Milvus 的 Sparse/Hybrid Search 无法满足复杂搜索需求时补强 | 按需 / V3+ |
| Reranker | 对 Dense/Sparse/Keyword 等召回候选进行二次相关性判断与排序 | 提高最终 Recall Precision | V3 |
| Kafka | Write 后台任务、索引构建、Embedding、异步治理等事件投递 | 解耦主链路,削峰与异步处理 | V5 |
| LangGraph | 管理当前 Agent 任务 State、流程和临时上下文;不负责长期 Memory 持久化 | 区分任务状态与长期记忆 | 始终 |
2.8 Memory 与 RAG 的边界
Memory 与 RAG 都向 Agent 提供上下文,但解决的问题不同:
Memory 解决"过去发生过什么,以及 Agent 应该长期记住什么";RAG 解决"外部知识源中有什么,以及当前任务需要查什么"。
二者的核心区别不是"一个是用户数据、一个是知识数据",而是信息的来源、生命周期和更新机制不同。
| 维度 | Agent Memory | RAG |
|---|---|---|
| 核心问题 | Agent 应该记住什么? | 外部知识库中有什么? |
| 信息来源 | 用户交互、历史任务、Agent 行为、验证经验、显式偏好 | 文档、代码、手册、规范、数据库、网页等 |
| 信息性质 | 个性化、历史性、经验性、关系性 | 外部知识、事实性、参考性 |
| 典型内容 | 用户偏好、项目环境、历史故障、解决经验、操作规则 | SDK 手册、API 文档、技术规范、项目文档 |
| 生命周期 | 动态演化、版本化、冲突治理、过期/归档 | 随知识源更新、重新索引、删除或版本更新 |
| 作用域 | User / Agent / Project 等 | Knowledge Base / Tenant / Document 等 |
| 主要召回方式 | 结构化 + 语义 + 关键词 + Scope/状态过滤 | 关键词 + 向量 + Metadata Filter + Reranker |
| 最终作用 | 让 Agent 知道过去、了解协作对象、延续经验 | 让 Agent 获取当前任务所需的外部知识 |
例如:
Memory:
text
用户偏好:代码示例默认使用 C++20
Project Fact:当前项目部署在 RK3568
Episodic:上次排查 CUDA 首次 Run 约 788ms,最终通过 warmup 解决
Procedural:以后遇到 CUDA 首次 Run 慢,先检查是否包含 CUDA Context / cuDNN 初始化,并进行 warmup 验证
这些信息来自 Agent 与用户/项目过去的交互,并且需要长期治理,因此属于 Memory。
RAG:
text
RK3568 官方技术手册
ONNX Runtime CUDA EP 文档
OpenHarmony API 文档
C++ 标准文档
项目 SDK 手册
企业技术规范
这些信息来自外部知识源,Agent 在当前任务中按需检索,因此属于 RAG。
2.8.1 Memory 与 RAG 可以同时参与一次请求
二者不是互斥关系,而是可以共同构建 Agent Context:
text
User Query
│
┌──────────┴──────────┐
↓ ↓
Memory Recall RAG Recall
│ │
│ │
用户/项目/历史/经验 文档/代码/规范/知识
│ │
└──────────┬──────────┘
↓
Context Builder
↓
LLM
例如用户问:> "RK3568 上 ONNX Runtime CUDA 首次 Run 为什么慢?"
Memory 可以召回:
text
Episodic:之前曾遇到首次 Run 约 788ms 的问题
Procedural:先进行 warmup,再比较后续 Run latency
RAG 可以召回:
text
ONNX Runtime CUDA EP 文档
CUDA Context 初始化相关资料
cuDNN 初始化相关资料
最终 Agent 将:
text
Memory = "我以前怎么处理过"
RAG = "外部资料怎么说明"
两者结合后才能形成更完整的回答。
2.8.2 一个重要边界:Memory 不应该替代 RAG
不能因为某次对话中 Agent 看过一份官方文档,就把整个文档内容复制成 Memory。更合理的是:
text
RAG:官方文档原始知识
↓
Agent 使用
↓
如果产生稳定、可复用的个人/项目经验
↓
Memory:"以后遇到类似问题应该怎么处理"
因此:
RAG 保存和检索外部知识,Memory 保存 Agent 在长期交互过程中形成的个性化事实、历史经验、偏好与可复用方法。
两者可以互相产生信息,但数据所有权和生命周期必须保持独立。
三、长期记忆四类模型(当前 V1 核心模型)
当前平台第一版采用四类长期记忆模型。这四类不是随意分类,也不是为了凑数,而是认知科学理论在 AI 工程落地中的必然映射,直接回答 Agent 长期记忆中最核心的四个问题:
Semantic:世界/实体/项目/用户是什么?(事实与关系)
Preference:用户希望 Agent 怎么行为?(偏好与约束)
Episodic:过去具体发生过什么?(历史上下文与事件)
Procedural:以后遇到类似问题该怎么做?(方法论与规则)
四类的核心价值在于:数据结构、更新方式、检索方式、生命周期都不同。
3.0 为什么采用四类长期 Memory:理论参考与工程划分
当前平台采用 Semantic、Preference、Episodic、Procedural 四类长期 Memory。其中,Semantic 与 Episodic 属于 Tulving 多重记忆系统理论中的陈述性(外显)记忆;Procedural 属于该框架下的非陈述性(内隐)记忆。 而 Preference 并非传统认知心理学中与前三者并列的标准记忆类型,而是为表达用户/项目持续行为偏好而抽象出的工程模型。
因此,本系统的四类划分可以理解为:
认知科学提供理论参考,Agent 工程需求决定最终的数据模型。
四类 Memory 分别解决 Agent 长期协作中的四个核心问题:
text
Semantic "知道什么?是什么?有什么关系?"
Preference "应该按照什么方式服务这个用户/项目?"
Episodic "过去发生过什么?"
Procedural "以后遇到类似问题应该怎么做?"
四类并不是互斥的数据分类。同一段交互可以同时产生多个 Memory Candidate,并通过 memory_relations 建立关联。
3.0.1 四类 Memory 的工程基因
四类 Memory 真正需要区分的,不是名字,而是:
数据结构、更新语义、召回条件、生命周期和治理方式不同。
| 记忆类型 | 核心内容 | 数据结构倾向 | 更新策略 | 核心召回方式 | 典型实现 |
|---|---|---|---|---|---|
| Semantic | 事实、实体属性、关系 | 属性表 / 主谓宾 / 关系表 | Update / Versioning / Append | Entity / Attribute / Relation + Semantic | PostgreSQL / Milvus |
| Preference | 用户/Agent/Project 的持续偏好与约束 | Key-Value + Scope | Override / Versioning | Scope + Key 精确匹配 | PostgreSQL / Redis Cache |
| Episodic | 具体历史事件、任务经历、结果 | 结构化 Episode + Summary | Append / Versioning / Archive | Semantic + Keyword + Metadata Filter | PostgreSQL / Milvus |
| Procedural | 方法、规则、SOP、经验 | Rule / Procedure / Step / Condition | Refine / Versioning | Structured + Semantic + Keyword / Rule Match | PostgreSQL / Milvus / Rule Engine |
这里的"典型实现"只是工程选择,不是类型与数据库的一一绑定关系。例如 Preference 是一种 Memory 类型,而 Redis 只是它的缓存实现:
text
Preference
↓
PostgreSQL
↓
Redis Cache
而不是:
text
Preference = Redis
同样,Episodic 可以使用 Milvus 做语义召回,但其真实数据仍然应该由 PostgreSQL 管理。
3.0.2 为什么需要四类,而不是一种统一 Memory
如果所有信息都简单存成:
json
{
"content": "...",
"embedding": [...]
}
虽然可以快速实现一个向量 Memory,但很难表达不同信息的不同更新语义。例如:
Semantic:
text
project → deploy_to → RK3568
重点是当前事实是什么。
Preference:
text
coding.cpp_standard
scope = user
value = C++20
重点是作用域覆盖关系。
Episodic:
text
Episode:
问题:CUDA 首次 Run 很慢
行动:增加 warmup
结果:后续 Run 恢复正常
重点是过去发生了什么。
Procedural:
text
Procedure:
遇到 CUDA 首次 Run 慢
↓
检查是否包含 CUDA Context 初始化
↓
执行 warmup
↓
比较 warmup 前后 latency
重点则是未来如何处理类似问题。
因此四类 Memory 的区别本质上是:
不同的知识形态,需要不同的状态模型和更新语义。
3.0.3 四类 Memory 的关系
四类 Memory 并不是一条严格的线性流水线,而是可以相互关联:
text
Semantic
/ \
↓ ↓
Preference Episodic
↓
Procedural
例如一次实际排障可能同时产生:
text
Semantic
项目使用 ONNX Runtime 1.29
│
├──────────────┐
↓ ↓
Episodic Procedural
某次 CUDA 首次 类似问题优先
Run 约 788ms 进行 warmup
Preference 也可能参与其中:
text
Preference
用户偏好:性能问题优先给出可验证的 benchmark
这些 Memory 通过:
text
memory_relations
建立关联,而不是强制将一次交互归入唯一类型。
3.0.4 为什么 V1 选择这四类
四类不是理论上"唯一正确"的分类,而是当前 Agent Memory 系统的一个工程最小完备集。它覆盖了 Agent 长期协作中最常见的四种信息:
text
Semantic → 长期事实
Preference → 长期偏好
Episodic → 历史经历
Procedural → 可复用方法
相比只建立一个统一的 Memory 表,这种划分可以明确不同类型的:
text
数据结构
↓
更新方式
↓
召回方式
↓
冲突处理
↓
生命周期
因此,V1 采用四类并不是为了"凑四种 Memory",而是为了让后续的数据模型和业务逻辑具有明确的语义边界。
同时,四类也不是最终形态。随着系统规模和业务复杂度增加,可以进一步拆分:
- Entity Memory:当 Semantic 中实体、关系和图谱复杂度明显上升时独立出来。
- Social Memory:需要建模多用户、多 Agent、组织关系和信任关系时引入。
- Reflection Memory:需要专门记录 Agent 对失败任务、决策和行为进行反思时引入。
因此:
四类是当前 V1 的工程边界,而不是对人类记忆系统的完整模拟。
3.1 核心区别
| 类型 | 回答的问题 | 典型内容 | 变化方式 | 主要用途 |
|---|---|---|---|---|
| Semantic | 知道什么、关系是什么? | 用户、项目、环境、实体事实与关系 | 更新事实 | 理解用户/项目/世界 |
| Preference | 喜欢什么? | C++20、中文、输出格式 | 覆盖偏好 | 个性化 |
| Episodic | 发生过什么? | 某次故障排查、某次任务 | 新增历史 | 延续任务 |
| Procedural | 应该怎么做? | 排查流程、显式方法、验证经验 | 迭代规则 | 复用能力 |
三句话看起来都是"记忆",但数据库操作完全不同,而且它们并不互斥:
"我主要使用 C++20。"------事实/偏好
"昨天我们解决了 CUDA 首次 Run 788ms。"------历史事件
"以后遇到 CUDA 首次 Run 慢,先做 warmup。"------方法论### 3.2 为什么不能全部叫 Memory
三句话看起来都是"记忆",但数据库操作完全不同,而且它们并不互斥:
"我主要使用 C++20。"------事实/偏好
"昨天我们解决了 CUDA 首次 Run 788ms。"------历史事件
"以后遇到 CUDA 首次 Run 慢,先做 warmup。"------方法论
3.3 Semantic(事实与关系记忆)
Semantic 保存的是相对稳定、可独立于具体对话存在的事实与关系,不局限于"用户画像"。典型范围包括:
text
User Fact:用户主要使用 C++
Project Fact:项目部署平台为 RK3568
Environment Fact:运行环境使用 CUDA 12
Entity Fact:模型输入特征为 Log-Mel
Relationship Fact:项目 → 使用 → ONNX Runtime
数据天然适合主谓宾三元组或关系表:
json
{
"subject": "project",
"predicate": "deploy_to",
"object": "RK3568"
}
三个特点:
- 不依赖某次对话:记"项目目标平台是 RK3568",而非"某天用户说过"。
- 可被后续信息修改 :需要
current value + version + history。 - 检索由实体/属性/关系驱动 :无需复杂向量检索,直接按
project_id + predicate查询。
3.4 Preference(偏好记忆)
事实与偏好不是一回事:
text
Semantic: 用户使用 C++ → 客观事实
Preference: 示例都用 C++20 → 如何服务这个用户
Preference 本质是 Configuration + Personalization。
最大特点是 Scope(作用域覆盖),由宽到窄:
text
Global → User → Agent → Project → Task
但必须注意:Task 层通常不是长期 Memory,而是任务期间的覆盖配置 。例如"这次回答用英文"不应反向修改 user.language = English。Task 级配置更适合进入 LangGraph State,并在任务结束时过期;长期 Memory 主要负责 Global/User/Agent/Project 层级。
数据模型天然适合 key + value + scope:
json
{
"key": "coding.cpp_standard",
"value": "C++20",
"scope": "user"
}
3.5 Episodic(场景记忆)
记录"某件事具体发生过什么",不能只保存原始聊天记录,需要:
text
Conversation → Summary → Structured Episode
有价值的结构化字段应保留:
text
Goal / Problem / Context / Actions / Decision / Result / Outcome
因为 Agent 之后真正需要回答的是:"这个问题当时是怎么解决的?"而非"过去聊过很多关于 CUDA 的内容"。Episodic 也是 Procedural 的重要抽象来源之一,但二者不是先后强制关系。
3.6 Procedural(流程经验记忆)
Procedural 描述"以后怎么做"。它不要求必须"多次成功案例之后才产生",来源包括:
text
用户显式提供的方法
历史 Episode 抽象
Agent/Tool 验证出的经验
外部规则/策略
不同来源应保留不同 confidence 与 source,便于后续治理。
text
Procedural = 可迁移到未来任务的方法/规则
因此 Procedural 是"方法论/经验层",让 Agent 具备可复用的经验能力。
3.7 四种 Memory 的关系是一条链,但不是互斥分类
四者层层递进:Semantic 提供事实底座,Preference 决定服务方式,Episodic 沉淀历史事件,Procedural 提炼可复用经验,共同构成完整长期记忆。
但必须强调:四类长期记忆之间不是互斥的"四选一"。同一段对话可能同时产生多种记忆,后续通过关联关系连接,而不是强制归入某一类。
3.8 从对话到记忆:Memory Extraction Pipeline
核心问题不是"四类记忆是什么",而是:一段对话进来后,如何判断它属于哪类记忆。
关键结论:不要让 LLM 对整段对话做四选一;分类对象不是 Conversation,而是 Extractor 生成的每个 Memory Candidate。 一段对话可以产生多个 Candidate,每个 Candidate 独立分类,允许多标签及关联。
text
One Conversation → Multiple Memory Candidates
正确的 Pipeline
text
用户对话
↓
Conversation Normalizer
↓
Memory Extractor → 生成多个 Memory Candidate
↓
每个 Candidate 独立分类(Semantic / Preference / Episodic / Procedural,可多标签)
↓
Validator → Dedup → Conflict → Persist
命中多个类别时,建立 memory_relations 关联关系,而不是强制四选一。
四个判断标准
Semantic = 相对稳定、跨任务仍然成立的事实或关系。
Preference = 对未来 Agent 行为具有持续影响的用户偏好/指令。
Episodic = 对未来交互可能有价值的历史事件/任务经历。
Procedural = 可迁移到未来任务的方法/规则;来源可为显式提供、事件抽象、验证经验或外部策略。
多标签分类策略
text
对每个 Memory Candidate,依次判断以下问题(可同时命中):
1. 是否描述相对稳定的事实或关系? → 标记 Semantic
2. 是否表达对未来 Agent 行为的持续偏好/指令? → 标记 Preference
3. 是否是有边界、有复用价值的历史事件? → 标记 Episodic
4. 是否是可迁移到未来任务的方法/规则? → 标记 Procedural
命中多个类别时,保留多标签并建立关联关系。
示例:
text
"上次发现 CUDA 首次 Run 788ms,加 warmup 恢复;以后遇到类似问题先 warmup。"
→ Episodic(历史事件)
→ Procedural(可迁移方法)
两个 Candidate 通过 memory_relations 关联。
3.9 Conversation 与 Long-term Memory 的边界
核心前提:
Conversation History ≠ Long-term Memory
三者必须分开:
- Conversation 记"原话",是原始数据源。
- Memory 记"提炼后的长期认知",需要动态治理:
Candidate → Active → Updated → Superseded → Archived → Deleted。 - LangGraph State 记"现在做到哪里",也承载任务级临时配置,如本次输出的语言/格式覆盖。
因此 Memory Service 不应负责保存原始聊天,而应消费 Conversation/Event,再提炼长期记忆 :数据模型拆成 conversations / messages 与 memories / memory_versions / memory_relations / memory_events。完整对话之所以重要,是因为它可以作为 Memory 的原始依据,在提取错误或模型升级后重新提取、验证、修正记忆。
四、标准记忆生命周期与完整请求链路
4.1 全生命周期
text
Recall → 业务使用 → 生成 Memory Candidate
→ Extract / Validate / Resolve
→ Dedup → Conflict
→ 落地 PG(唯一事实源)
→ 构建索引(Milvus/OpenSearch)
→ 生命周期治理(更新/归档/过期)
4.2 单次完整 Agent 请求链路
text
用户请求 → Recall 多源召回 → 融合/过滤/排序 → 结合 RAG 构建 Prompt → LLM 执行任务
→ 生成 Memory Candidate → Extract / Validate / Resolve → Dedup / Conflict
→ 落地 PG(Active Version)→ 同步更新索引
五、记忆召回精准架构
传统"Redis→Milvus→OpenSearch→PG"瀑布式降级链路并不严谨。更准确的定义是:索引负责"找到谁",PostgreSQL 负责"这个 Memory 现在到底是什么"。 其中 OpenSearch 为可选组件,仅在需要复杂全文检索、高级关键词查询、聚合分析或搜索分析时才引入。
text
Recall Request
│
┌─────┼─────┐
▼ ▼ ▼
结构化召回 语义召回 关键词召回(可选)
(Redis/PG) (Milvus) (OpenSearch)
│ │ │
└─────┼─────┘
▼
Fusion(多源融合)
▼
Filter(过滤无效项)
▼
Reranker(精准排序)
▼
Memory IDs / metadata
▼
PostgreSQL(唯一事实源,返回 Active Version Data)
▼
Context Builder
召回应明确分为三类,其中关键词召回为可选:
- Structured Recall:Redis / PG,适配精确查询、热点记忆、偏好、最近/高频访问。
- Semantic Recall:Milvus,适配模糊自然语言语义匹配。
- Keyword Recall(可选):OpenSearch,适配精准关键词、专有名词、设备型号;仅在需要复杂全文检索、高级关键词查询、Aggregation 或 Search Analytics 时引入。
- Structured Recall:Redis / PG,适配精确查询、热点记忆、偏好、最近/高频访问。
- Semantic Recall:Milvus,适配模糊自然语言语义匹配。
- Keyword Recall:OpenSearch,适配精准关键词、专有名词、设备型号。
Redis 不应被当作一个普通 Recall Engine,它定位是结构化 hot memory 缓存;PostgreSQL 也不是"兜底",而是唯一事实源,负责返回某个 Memory 当前有效版本的真实数据。
六、工业级落地分阶段方案
严格按「先核心、后优化、再工业化」迭代,避免初期堆砌组件。
| 阶段 | 新增内容 | 核心目标 |
|---|---|---|
| V1 | FastAPI + Memory Service + PostgreSQL | 定义记忆领域模型、PG 表结构、Recall/Write 主入口及基础 Write Pipeline |
| V2 | Embedding + Milvus | 语义化记忆召回 |
| V3 | OpenSearch + 混合检索 + Reranker | 语义 + 关键词双模式精准召回 |
| V4 | LLM 记忆提取、校验、去重、冲突修复 | 记忆写入智能化 |
| V5 | Kafka、Redis 结构化热点缓存、定时调度、可观测体系 | 解耦、并发、监控、工业化高可用 |
七、项目启动第一优先级核心工作
停止架构名词堆砌与 PPT 设计,优先落地工程核心:
- 定义完整 Memory 领域模型(单条记忆结构与全部字段)。
- 梳理记忆完整生命周期:产生、提取、验证、落地、召回、更新、冲突、过期、删除。
- 基于模型设计 PG 数据表、索引、版本机制。
- 实现基础 Memory Service 与 LangGraph 适配层,明确区分任务级 State 与长期 Memory。
核心结论:模型是所有架构的根基。模型不清晰,所有中间件堆砌均无意义。