ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent 记忆放哪里:六种存储介质深度对比与选型

AI Agent 记忆放哪里:六种存储介质深度对比与选型 开篇存什么 vs 放哪里是两个问题设计 Agent Memory 时最容易把两个问题混成一个“记忆可以存内容、存硬盘、存关系型数据库、存内存数据库、也可以存向量数据库、还可以做知识图谱。”这句话听起来什么都对但什么也没说清。因为它把两个正交维度混在一起了维度 1存什么形态数据结构 - 原始消息ChatMessage日志形态 - 结构化条目MemoryEntry有 subject / type / provenance / status - 向量嵌入embedding一串数字 - 图谱节点边entity relation有向图 维度 2放哪里存储介质 - 内存Map进程内 - 文件JSONL / SQLite本地持久 - 关系型数据库MySQL / PostgreSQL - 内存型数据库Redis快但易失 - 向量数据库Milvus / pgvector / FAISS / Pinecone - 图数据库Neo4j / Graphiti两个维度正交同一份记忆介质可以换形态也可以混。结构化条目可以存内存、可以存 MySQL图谱节点边可以存 Neo4j、也可以用 JSONL 手搓。本文聚焦维度 2六种存储介质各自是什么、数据怎么落、怎么查、优缺点、什么时候选。读完你能拿到任何 Agent 的记忆后端立刻判断它在哪一档给自己设计 Memory 时按规模和能力选介质不盲目上向量数据库。一、六种存储介质逐个拆1. 内存 Map进程内数据怎么落ConcurrentHashMapString, MemoryEntry key entry.id() value MemoryEntry record没有序列化没有 I/O对象直接活在 JVM 堆里。怎么查Stream filter 链entries.values().stream() .filter(e - query.scopes().contains(e.scope())) scope 隔离 .filter(e - e.status() ACTIVE) 只回活跃 .filter(e - !e.isExpired(now)) TTL 惰性过滤 .filter(e - e.content().contains(keyword)) keyword 包含 .sorted(comparing(MemoryEntry::createdAt).reversed()) .limit(limit)优点零依赖不需要任何外部组件查询延迟纳秒级deterministic单元测试好写不抖动数据结构灵活record 随便改缺点进程崩了数据全丢容量受 JVM 堆限制几十万条到头无法多进程共享每个 Agent 实例各存各的没有索引大数据量时 filter 全表扫适合场景教学型框架、单进程 Demo、单元测试、原型验证代表实现LangChain 的ConversationBufferMemory、教学型框架的内存实现2. 文件 JSONL / SQLite本地持久数据怎么落JSONL 方式–每行一个 JSON 序列化的 MemoryEntry{id:a1,scope:user:u1,type:PREFERENCE,subject:diet,content:对花生过敏,status:ACTIVE,...} {id:a2,scope:user:u1,type:FACT,subject:tz,content:UTC8,status:ACTIVE,...}SQLite 方式–一张本地文件数据库表CREATETABLEmemory_entry(idTEXTPRIMARYKEY,scopeTEXT,typeTEXT,subjectTEXT,contentTEXT,statusTEXT,created_atINTEGER,expire_atINTEGER);CREATEINDEXidx_scopeONmemory_entry(scope);CREATEINDEXidx_subjectONmemory_entry(scope,subject);怎么查JSONL启动时全量加载到内存 Map之后同内存方案或按行扫描大数据量时慢SQLite标准 SQL 查询带索引优点进程崩了数据还在文件持久人可读JSONL 可以 cat / diff / 手动编辑零部署依赖SQLite 就是单文件调试方便出问题直接看文件内容缺点JSONL 全量加载到内存或按行扫不适合大数据量并发写要加锁JSONL或依赖 SQLite 的文件锁无向量检索能力多进程共享要小心SQLite 多写者性能差适合场景单机生产、单租户 Agent、开发调试、需要数据可审计可回溯但规模不大代表实现dsh 的 session logJSONL、LangChain 的ConversationBufferFileMemory3. 关系型数据库MySQL / PostgreSQL数据怎么落标准关系表CREATETABLEmemory_entry(idVARCHAR(64)PRIMARYKEY,scopeVARCHAR(128)NOTNULL,typeVARCHAR(32)NOTNULL,subjectVARCHAR(256),contentTEXTNOTNULL,importanceFLOATDEFAULT0.5,provenance JSON,-- 来源溯源存 JSONstatusVARCHAR(32)NOTNULLDEFAULTACTIVE,created_atTIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,expire_atTIMESTAMPNULL,tenant_idVARCHAR(64)NOTNULL-- 多租户隔离);CREATEINDEXidx_scopeONmemory_entry(tenant_id,scope);CREATEINDEXidx_scope_subjectONmemory_entry(tenant_id,scope,subject);CREATEINDEXidx_statusONmemory_entry(tenant_id,status);怎么查标准 SQLSELECT*FROMmemory_entryWHEREtenant_id?ANDscopeIN(?,?)-- scope 隔离ANDstatusACTIVE-- 只回活跃AND(expire_atISNULLORexpire_atNOW())-- TTLANDcontentLIKECONCAT(%,?,%)-- keywordORDERBYcreated_atDESCLIMIT?;优点事务保证ACID写入要么全成要么全败并发支持多 Agent 实例同时读写多租户天然隔离tenant_id scope 双重过滤索引能力强BTree按 scope/subject/status 查询毫秒级审计友好完整的变更日志可以单独建表生态成熟运维、监控、备份方案多缺点部署依赖要跑一个数据库进程keyword 用 LIKE 走全表扫除非加全文索引没有语义检索能力“过敏” 匹配不到 “禁忌”schema 变更要迁移适合场景多租户企业 Agent、需要事务和审计的生产环境、ChatGPT memory 的 saved memories 落地代表实现企业 Agent 生产环境、ChatGPT memory 后端进阶PostgreSQL pgvector 扩展PostgreSQL 装上 pgvector 扩展后可以加一列向量字段CREATEEXTENSION vector;ALTERTABLEmemory_entryADDCOLUMNembedding vector(384);CREATEINDEXidx_embeddingONmemory_entryUSINGivfflat(embedding vector_cosine_ops)WITH(lists100);这样同一张表既能 SQL 查 keyword又能向量查语义–关系型 DB 的能力扩展到第 2 档检索不用单独引一个向量数据库。4. 内存型数据库Redis数据怎么落用 Redis 的 Hash 或 Sorted SetHash: HSET memory:a1 scope user:u1 type PREFERENCE content 对花生过敏 status ACTIVE ... Sorted Set按时间排序方便取最近: ZADD memory:user:u1:timeline 1724054400 a1 ZADD memory:user:u1:timeline 1724140800 a2怎么查按哈希键精确查HGET memory:a1按 scope 查所有HGETALL memory:user:u1:*要 SCAN按时间范围ZRANGE memory:user:u1:timeline 0 10 REV无原生 keyword 包含匹配要自己实现或用 RedisSearch 模块优点延迟极低微秒级全内存操作支持过期自动清理TTL 原生支持不用惰性过滤数据结构丰富Hash / Set / Sorted Set / List 各有场景缺点数据易失Redis 默认异步刷盘崩溃可能丢最近几秒内存成本高全量数据驻内存比磁盘贵得多查询能力弱无 SQL无向量复杂过滤要客户端做或加 RedisSearch不适合做主存储适合做缓存层适合场景热数据缓存层当前会话活跃记忆、配合关系型 DB 用的加速层、Session 级短期记忆代表实现大规模 Agent 系统的热数据层、配合 MySQL 用的缓存典型混合用法写入写关系型 DB持久 写 Redis加速 读取先查 Redis快miss 时回退关系型 DB 过期Redis TTL 自动清理关系型 DB 靠惰性过滤5. 向量数据库Milvus / pgvector / FAISS / Pinecone这是和前面四种介质最大的分水岭前四种介质回答放哪里向量数据库额外回答了怎么查–从字面匹配升级到语义匹配。数据怎么落结构化条目 向量字段原有 MemoryEntry 字段不变 id / scope / type / subject / content / status / provenance / ... 新增向量字段 embedding VECTOR(384) -- 384 维浮点数组 向量怎么来 对花生过敏 - 过 embedding 模型 - [0.12, -0.34, 0.56, ..., 0.08]怎么查向量相似度检索步骤 1把查询用户问过敏过 embedding 模型 - 查询向量 [0.13, -0.32, ...] 步骤 2在向量库里找余弦相似度最高的 K 条 步骤 3返回 Top-K 记忆条目 SQL 版本pgvector SELECT *, 1 - (embedding $query_vec) AS similarity FROM memory_entry WHERE scope IN (?, ?) AND status ACTIVE ORDER BY embedding $query_vec -- 余弦距离 LIMIT 10; Milvus 版本 collection.search( data[query_vec], anns_fieldembedding, param{metric_type: COSINE, params: {nprobe: 10}}, limit10, exprscope in [user:u1] and status ACTIVE )embedding 是什么把文字过 embedding 模型变成一串几百到几千维的浮点数。语义相近的文字向量距离也近。我对花生过敏 - [0.12, -0.34, 0.56, ...] 花生是我的饮食禁忌 - [0.11, -0.32, 0.55, ...] - 很接近 今天天气不错 - [0.87, 0.21, -0.09, ...] - 完全不同所以向量检索能匹配过敏和禁忌这种字面不同但语义相同的内容。优点语义模糊匹配核心价值字面不同的能匹配到检索质量高用户换说法、用同义词都能命中工业成熟Milvus/Pinecone 生态完整缺点依赖 embedding 模型每次写入都要算 embedding要么调 API 要么跑本地模型向量索引占空间384 维 float 1.5KB/条百万条 1.5GB每次结果可能微抖动取决于 embedding 模型单元测试不好写查询不是精确的匹配/不匹配而是相似度 Top-K四种向量数据库对比数据库类型部署方式适合pgvectorPostgreSQL 扩展跟着 PG 跑已有 PG不想引新组件百万级以下Milvus专用向量 DB独立集群大规模生产亿级向量需要高吞吐FAISS库不是服务进程内嵌入单机、库内调用、不想部署服务Pinecone云托管 SaaSAPI 调用不想运维、快速验证、按量付费适合场景语义检索RAG、需要模糊匹配的记忆系统、mem0 / ChatGPT memory 的历史引用通道代表实现mem0OpenAI embedding Qdrant、RAG 系统关键认知向量数据库解决的是怎么查不是放哪里很多人以为上了向量数据库就不用关系型 DB 了。错。向量数据库解决的是检索方式从字面到语义不是存储介质。生产级常见做法是主存还是关系型 DB向量作为旁路索引主存MySQL / PostgreSQL存结构化条目支持事务和审计 旁路Milvus / pgvector只存 id embedding负责语义检索 查询时 1. 先在向量库找相似 idTop-K 2. 拿 id 去主存捞完整条目 3. 主存做 scope/status/TTL 过滤 4. 返回结果这样事务、审计、多租户隔离由主存保证向量库只管找相似。6. 图数据库Neo4j / Graphiti这是最复杂也最强的一档。前面的介质都是存条目图数据库存的是条目之间的关系。数据怎么落节点 边有向图节点 (userA:User {id: u1}) (fact1:Fact {content: 对花生过敏, validFrom: T1, validTo: T3}) (fact2:Fact {content: 不过敏, validFrom: T3, validTo: null}) (userB:User {id: u2}) 边 (userA)-[:SAID {at: T1, runId: r1}]-(fact1) (fact1)-[:SUPERSEDED_BY {at: T3, by: admin}]-(fact2) (userA)-[:RELATIVE_OF]-(userB) (userB)-[:SAID {at: T2}]-(fact3:Fact {content: 家族坚果过敏史})每条边可以带属性什么时候说的、谁说的、从哪次 run 来的。怎么查图遍历 / 路径推理Cypher 查询语言// 查用户亲属的过敏史 -- 跨实体推理 MATCH (u:User {id: u1})-[:RELATIVE_OF]-(rel:User)-[:SAID]-(f:Fact) WHERE f.content CONTAINS 过敏 AND f.validTo IS NULL RETURN f; // 查这个事实谁说的、什么时候、后来改过没 MATCH (f:Fact {content: 对花生过敏})-[:SAID]-(sayer) OPTIONAL MATCH (f)-[:SUPERSEDED_BY]-(corrected) RETURN sayer, f, corrected;双时间轴Zep / Graphiti 的核心特色validFrom / validTo 事实在现实世界中的有效期 fact1: 对花生过敏 validFromT1, validToT3 T1 到 T3 期间为真 fact2: 不过敏 validFromT3, validTonull T3 之后为真 recordedAt 系统什么时候录入这条记忆 fact1.recordedAt T1 T1 时录入 fact2.recordedAt T3 T3 时录入这让你能回答上周三时用户对花生过敏吗–查 validFrom 周三 validTo 的事实。关系型 DB 的记忆只有录入时间回答不了这种时序问题。优点关系推理能力强跨实体关联回答A 的亲属的病史双时间轴回答时序问题审计链天然完整supersede 就是图的一条边缺点实现最复杂schema 设计难查询语法学习曲线陡运维重独立图数据库集群不擅长大规模简单查询按 scope 查所有条目不如关系型 DB 快生态不如关系型 DB 成熟适合场景复杂关联推理、时序推理、需要回答什么时候知道/什么时候过期的场景代表实现Zep / Graphiti二、六种介质速查表介质数据结构查询方式持久多租户延迟适合规模内存 Map对象Stream filter否进程级纳秒万级JSONL 文件JSON 行全量加载/扫描是否加载后纳秒万级SQLite关系表SQL 索引是否毫秒十万级MySQL/PG关系表SQL 索引是是毫秒亿级RedisKV/Hash/Set键查/SCAN半持久是微秒千万级向量 DB条目向量向量相似是是毫秒亿级向量图 DB节点边图遍历是是毫秒~秒千万级节点三、检索能力三档进化存储介质决定了能查多准–这是选型时最容易被忽略的维度第 1 档关键词匹配字面包含 过敏 只匹配含过敏两字的条目 禁忌 查不到 -- 字面不一样就不命中 - 介质内存 Map、关系型 DB 的 LIKE 第 2 档向量相似语义接近 过敏 能匹配到禁忌花生不耐受 - 需要embedding 模型 向量索引 - 介质向量 DB / pgvector 第 3 档图谱推理关系推理 问用户亲属的过敏史 能顺着用户-亲属-病史边推理出来 这个 PR 沉寂 3 天了 能关联 PR-CI-失败-用户 - 需要图结构 路径查询 - 介质图 DB / Zep检索能力越强存储结构越复杂、运维越重。教学型用第 1 档够工业级主用第 2 档复杂场景才上第 3 档。这也是为什么很多团队用了向量数据库但效果不好–向量只解决语义相似不解决关系推理。如果你的核心需求是用户上周说过 X这周问 YX 和 Y 有关联向量帮不了你要图谱。四、选型决策树你的 Agent 要记忆吗 └── 是 - 规模多大 ├── 教学 / Demo / 单进程 │ - 内存 Map │ - 要持久加 JSONL 文件 ├── 单租户 / 单机生产 │ - SQLite / JSONL 内存缓存 │ - 检索要语义加 pgvector ├── 多租户企业 Agent │ - MySQL / PostgreSQL │ - 检索要语义pgvector 扩展 │ - 审计严格加审计日志表 │ - 热数据要快加 Redis 缓存层 ├── RAG / 语义检索 │ - 向量 DBMilvus / Pinecone │ - 主存还是关系 DB向量旁路 └── 复杂关联推理 - 图 DBNeo4j / Graphiti - 通常作为旁路主存还是别的五、混合存储生产级做法大规模 Agent 系统不是选一个数据库是按访问模式分层每种数据放最适合的介质┌──────────────────────────────────────────────────┐ │ 热数据当前会话活跃记忆 │ │ 介质Redis │ │ 原因微秒延迟TTL 自动清理 │ │ 内容当前用户最近 N 轮的记忆 会话状态 │ ├──────────────────────────────────────────────────┤ │ 温数据近期记忆跨会话 │ │ 介质MySQL / PostgreSQL │ │ 原因事务、索引、审计、多租户隔离 │ │ 内容用户偏好、事实、历史会话摘要 │ ├──────────────────────────────────────────────────┤ │ 冷数据历史归档 │ │ 介质对象存储 / JSONL 文件 │ │ 原因便宜、容量大 │ │ 内容超过 N 天的记忆条目归档 │ ├──────────────────────────────────────────────────┤ │ 语义检索索引旁路 │ │ 介质Milvus / pgvector │ │ 原因向量相似检索 │ │ 内容每条温/冷记忆的 embedding只存 id向量 │ ├──────────────────────────────────────────────────┤ │ 关系推理索引旁路可选 │ │ 介质Neo4j / Graphiti │ │ 原因跨实体关联推理 │ │ 内容实体节点 关系边 │ └──────────────────────────────────────────────────┘典型查询流程用户问帮我订午餐 - 1. Redis 查当前会话有没有相关记忆热- 命中返回 - 2. miss - 向量库找语义相似条目语义- 拿到 id 列表 - 3. 拿 id 去主存 MySQL 捞完整条目温- scope/status/TTL 过滤 - 4. 返回用户对花生过敏 - 5. 写回 Redis 缓存下次快六、起步选择 扩展路径起步推荐存什么结构化条目有元数据可治理不存原始消息 放哪内存 Map教学/原型/ SQLite单机持久/ MySQL多租户生产 怎么查keyword 包含匹配 ↑ 第 1 档最简版先用最简单的跑通扩展路径关键接口抽象藏住怎么查设计 MemoryStore 接口时按query(MemoryQuery)抽象把怎么查藏在实现里路径 A换存储介质同一形态换实现 内存 Map - JSONL 文件持久 - JDBCMySQL/PG多租户 检索方式不变还是 keyword 调用方零改动 路径 B换检索能力数据加字段查法升级 记忆条目加 vector 字段 query 方法换向量相似 - 向量 DBMilvus/pgvector 调用方retriever / contextBuilder零改动起步用 keyword、中期换向量、后期换图谱调用方都不用改一行。这是接口抽象的价值把存储怎么实现和记忆怎么用解耦。七、防错清单设计时容易踩的坑1. 盲目上向量数据库 - 听说向量数据库好就上但你的规模根本不需要 - 教学/Demo 用内存 Map 够单机生产用 SQLite多租户用 MySQL - 向量数据库解决怎么查不是放哪里 2. 用了向量但效果不好 - 向量只解决语义相似不解决关系推理 - 核心需求是跨实体关联要图谱不是向量 3. Redis 当主存 - Redis 快但易失崩溃可能丢最近几秒数据 - Redis 适合缓存层主存还是要持久介质 4. 存储介质和检索方式焊死 - 换个数据库要改一堆代码 - 接口按 query(MemoryQuery) 抽象藏住查法 5. 只看介质不看检索能力 - 用 MySQL 存记忆 听起来没问题但 LIKE 全表扫 - 同样是关系型 DBPostgreSQL pgvector 能做向量检索MySQL 不能 - 选介质时同时考虑检索能力 6. 冷热不分离 - 所有记忆都堆在同一个介质 - 热数据要快Redis冷数据要便宜文件混在一起性能和成本都不好 - 按访问模式分层八、总结设计 Agent Memory 的存储层三个层次想清楚 1. 存什么形态数据结构 原始消息 / 结构化条目 / 向量嵌入 / 图谱节点边 主流选结构化条目有元数据可治理 2. 放哪里存储介质 内存 Map教学/ 文件单机持久/ 关系 DB多租户生产 / Redis热缓存/ 向量 DB语义检索/ 图 DB关系推理 按规模和能力选见决策树 3. 怎么查检索能力 keyword字面- 向量语义- 图谱推理 能力越强结构越复杂运维越重一句话收束Agent Memory 的存储层不是选哪个数据库是按规模选介质、按能力选检索、按访问模式分层。存储介质是可换的接口抽象是不变的。
返回列表