为什么 Agent Memory 默认不需要外部向量数据库:Hindsight 的 PostgreSQL 多策略检索架构剖析
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
大多数"给 Agent 加上记忆"的教程都以同一个动作开场:安装一个向量数据库。Mem0 的快速开始指向 Qdrant 或 Pinecone,LangChain 的记忆示例默认使用 Chroma,"Agent memory 就等于向量数据库"几乎成了行业共识。但本篇文章要论证的是:大部分时候,这一步完全没有必要。本文基于 Hindsight 项目的架构主张,深入分析 Agent Memory 与 RAG 在读写形态上的本质差异,剖析外部向量数据库隐藏的运维税,并展示 Hindsight 如何用"一个 PostgreSQL + pgvector"承载事实抽取、实体解析、矛盾处理、失效与反思(reflect)构成的完整学习层,以及语义、实体、时间、图四种检索策略并行融合的实现路径。读完本文,你将能判断自己的 Agent 记忆场景究竟属于"该用 Postgres"还是"确实需要专用向量库"。
本文核心论点来源于 Hindsight 官方博客 The Case Against External Vector DBs for Agent Memory,并结合仓库源码(
hindsight-api-slim/hindsight_api/engine/下的检索与融合实现)进行佐证与扩展。
Agent Memory 不是 RAG
先看工作负载,因为一切结论都从负载形态推导而来。
| 维度 | RAG | Agent memory |
|---|---|---|
| 读写比例 | 读密集;语料一次构建 | 写密集;每一轮对话都会产生事实 |
| 单租户体量 | 百万到十亿级向量 | 几十万到几百万条事实 |
| 检索形态 | 以语义相似度为主 | 语义 + 实体 + 时间 + 图混合 |
| 延迟预算 | 数百毫秒(搜索 UI 可接受) | 亚 200ms(Agent 循环内) |
RAG 是读密集型的:语料一次性构建,批量计算 embedding,索引建成之后,系统余下的一生都在对近乎静态的索引服务相似度查询。更新虽然存在,但绝不是热路径;热路径是"用户输入一个问题,返回最语义相似的 chunk"。ANN(近似最近邻)吞吐是它的头号指标。
Agent memory 几乎把这张表翻转过来,而且不只是因为"写得更频繁"。每一次写入都要穿过一条学习流水线:原始对话被分解为原子事实(fact extraction);跨事实出现的实体被解析合并——"认证服务""我们的登录系统""OAuth 微服务"应当落在同一个规范节点上(entity resolution);新事实与旧事实冲突时触发矛盾消解(contradiction handling);被取代的事实要被标记失效(invalidation);最后,定期有一个合成步骤读取累积事实、写回更高阶的观察(reflection/synthesis)。这些环节在 RAG 摄取里统统不存在,也统统不是向量数据库擅长的事。
检索形态同样不同。RAG 查询大多是单次语义查找;Agent memory 查询是混合的——"用户上周二关于定价做了什么决定?"是时间锚定 + 实体锚定;"展示所有与数据库迁移线程相关的内容"是图形态的;"用户是否对新仪表盘提过任何担忧?"需要语义搜索,同时要靠实体解析来捕捉同义词。这些都不是纯粹的 ANN 问题。
第三个差异是单租户体量:典型的 agent-memory 语料是每个租户几十万到几百万条事实,比"值得在生产环境运行 Pinecone"的 RAG 规模低两到三个数量级。正是那个让专用向量数据库的运维成本物有所值的条件(十亿级向量),在 agent memory 场景里通常根本不存在。
外部向量数据库的隐藏税
如果 agent memory 与 RAG 形态相同,单独维护一个向量数据库的运维税只是正常的业务成本。但它不是,所以这笔税大多买不到任何东西。
运维表面积(Operational surface area)
运行一个外部向量数据库,意味着要为一个第二个有状态服务做容量规划、扩缩容、安全加固、备份、监控和版本升级。对于本来就在生产环境运营 Pinecone 或 Weaviate 的团队,这笔成本可以摊薄;对于没有的团队,agent-memory 项目直接让数据库数量翻倍。
这个模式在主流 agent-memory 框架的自托管方案里看得一清二楚:自托管 Mem0 需要额外搭建 Qdrant 或 Pinecone;Zep 的 Community Edition 已弃用,自托管 Graphiti 意味着在 Graphiti 自身服务之外再搭 Neo4j、FalkorDB 或 Kuzu。框架本身不是重担,数据库依赖才是。
网络跳数与延迟
Agent 循环的延迟预算比 RAG 管道紧得多。一次 400ms 的记忆检索在搜索 UI 里毫无感觉,在每轮要做四次工具调用的 Agent 里就是"坏了"。路径上每多一个外部服务,就多一次往返和一次序列化。对低流量的 agent-memory 负载,到托管向量数据库的网络跳数往往比查询本身占掉更大比例的延迟预算。
小规模下的成本
托管向量数据库有定价下限。Pinecone 的 serverless 与 pod 档位都存在不随小数据集线性缩放的定价地板,Weaviate Cloud、Zilliz、Qdrant Cloud 结构类似(具体价格请以各家最新报价为准,供应商经常调整)。对一个几个 GB 的 Postgres 就能轻松装下的负载,去支付托管向量库的地板价,是用钱买"运维简化"的税,却什么负载真正需要的东西都没买到。
向量数据库没有的学习层
如果把检索层面的争论剥开,更根本的问题其实在检索之前。
有用的 agent memory 不是一堆对话 chunk,而是一个随着对话累积不断被构建与重建的结构化表示。支撑它的架构原语根本不是向量:
- 事实抽取(Fact extraction):模型把每一轮对话分解为原子化、可检索的声明(claim),并保留回源到原始消息的 provenance(溯源)。
- 实体解析(Entity resolution):新的提及被链接到已有实体。同一个客户、项目或服务,无论怎么被称呼,都归一到同一个规范节点。
- 矛盾处理(Contradiction handling):当新事实与旧事实冲突,系统必须决定谁胜出、谁被失效、谁作为历史保留。
- 失效(Invalidation):事实有生命周期。"仪表盘还在 beta"在仪表盘上线之后就不该再被检索到。
- 反思(Reflection):系统定期读取累积事实,写回合成的观察——模式、摘要、反复出现的主题。它们不存在于任何单次对话,却从众多对话中涌现。
向量数据库对这一切没有任何意见。它只接受一个id和一个vector。结构、关系、时间有效性、去重逻辑——所有这些都必须住在别处。在实践中,"别处"通常意味着第二个数据库(或一大摊应用逻辑与缓存),而向量存储退化为你本来就在维护的更大 schema 里的一列。
到这一步,你不是从技术栈里去掉了一个数据库,而是加上了一个。
把学习层和检索底座放进同一个事务性数据库,架构就被压缩了:事实抽取流水线直接写入 pgvector 正在索引的那个 Postgres;实体解析是一次 join;矛盾消解是一个事务;reflect 输出与输入一样是事实,紧挨着被索引。Agent 真正需要的混合检索从数据模型里自然涌现,而不是跨服务拼装。
单策略检索才是真正的天花板
向量数据库只把一件事做好:对 embedding 做 ANN。即便抛开上面的运维争论,纯向量系统对 agent memory 来说仍然是错误的底座——因为大多数 agent-memory 查询根本不能分解为纯语义相似度。
Hindsight 对每个查询并行运行四种检索策略:语义搜索、基于实体的检索、时间过滤、图遍历。这四种里有三种不是 ANN。基于实体的检索是对实体表的结构化查找;时间过滤是对时间戳的范围查询;图遍历是对边的递归游走。向量数据库能做第一种,其余三种要么委托出去,要么重复实现。
这一点不是 Hindsight 独有的观察。任何足够完整的 agent-memory 系统最终都会具备这四种检索模式中的至少三种,因为查询就是这么要求的。Zep 的 temporal knowledge graph 是"认真对待图与时间"的最强例子,而代价是:一旦你真的去遍历 Zep 的图数据库,它同样不再是"一个向量数据库问题"。架构论证是双向切割的。
经验结果跟架构一致。在标准的长程对话记忆评测 LongMemEval 上,向量主打的系统得分更低:Mem0 公布 49.0%;多策略系统得分更高,Hindsight 公布 91.4%,同样运行多检索模式的 SuperMemory 公布 81.6%。准确率差距正是架构差距的可见产物。
Hindsight 源码中的四路并行检索
Hindsight 把这四种策略落实为 retrieval.py 中的ParallelRetrievalResult:语义、BM25(关键词全文检索)、图(通过可插拔的GraphRetriever接口)、时间(带 spreading 的时间感知检索),四路并行后带独立的 timings。模块 docstring 明确写着 "Retrieval module for 4-way parallel search"。
语义与 BM25 甚至被合并进同一个 SQL:retrieve_semantic_bm25_combined_sql用UNION ALL把按 fact_type 划分的子查询联合起来,让每个 arm 拥有独立的ORDER BY ... LIMIT,从而可以利用每个 fact_type 上的部分 HNSW 索引;纯向量模式(enable_text_search=False)下则只发语义 UNION,把 BM25 的全部开销(分词、term 选择查询、@@/rank 扫描)跳过。
图检索走 graph_retrieval.py 的GraphRetriever抽象基类——实现遍历 memory graph(实体链接、时间链接、因果链接)去找到语义与关键词都找不到的相关事实。默认实现是LinkExpansionRetriever(链接扩展检索)。
最后,多路结果在 fusion.py 里用**倒数排名融合(Reciprocal Rank Fusion, RRF)**合并:score(d) = sum_over_lists(1 / (k + rank(d))),默认k=60。每个来源先按 cap 截断(cap_per_source),防止单一过度扩展的 arm 挤占其它来源,再进入融合与重排。这就是博客所说"在应用层合并、再做 cross-encoder 重排"的具体实现。
向量数据库为错误的负载做了优化
向量索引结构(HNSW、IVF、ScaNN)是读优化的:构建昂贵,增量更新也昂贵。有些实现支持 delete-and-rebuild 模式,另一些对非平凡变更要求完全重建索引。没有哪一种喜欢 agent memory 这种高频写入、更新、失效的负载。
更糟的是,向量索引看到的写入甚至不是实质性的写入。有意义的工作——抽取、解析、矛盾、reflect——发生在流水线上游,产出大量小型派生记录,向量存储只在其最终 embedding 形态下看到它们。大部分学习逻辑根本不碰向量索引,而这正是要点:向量存储只是一个系统里的一个底座,而那个系统的重心在别处。
PostgreSQL 用三十年把事务性负载做到极致:MVCC、WAL、时间点恢复、在线 schema 变更、成熟的复制。这些都不是为向量而生的,但恰恰是为 agent memory 真正拥有的访问模式(写入、更新、删除、并发读)而生的。通过 pgvector 加上向量搜索,所有这些能力全部继承。
在十亿向量规模上,结论反转:专用向量数据库在原始吞吐和索引构建时间上胜出,运维开销也值回票价。但大多数 agent-memory 负载不在十亿级。
pgvector 对 Agent Memory 来说已经够用
对"直接用 Postgres"的历史性质疑是性能:pgvector 早期的 IVFFlat 索引在规模上缺乏竞争力。这已不再是制约条件:pgvector 支持 HNSW 索引,在 agent-memory 负载关心的召回与延迟曲线上具有竞争力;Postgres 16 的并行索引构建与查询并行度补齐了剩余的大部分差距。
更重要的是,把向量放进 Postgres,意味着实体表、时间事实、图边和应用数据住在同一个事务性数据库里。多策略检索变成一次 join 而非分布式查询;跨全部四种检索底座的原子写是免费的;备份就是一个pg_dump。
在实践中,它看起来就是一条 SQL:
-- 三种检索策略在同一个数据库的同一条查询里合并 WITH semantic AS ( SELECT fact_id, 1 - (embedding <=> $query_embedding) AS score FROM facts ORDER BY embedding <=> $query_embedding LIMIT 50 ), temporal AS ( SELECT fact_id, 1.0 AS score FROM facts WHERE created_at BETWEEN $window_start AND $window_end ), entity AS ( SELECT f.fact_id, 1.0 AS score FROM facts f JOIN fact_entities fe USING (fact_id) WHERE fe.entity_id = ANY($resolved_entity_ids) ) SELECT f.*, COALESCE(s.score, 0) AS semantic_score, COALESCE(t.score, 0) AS temporal_score, COALESCE(e.score, 0) AS entity_score FROM facts f LEFT JOIN semantic s USING (fact_id) LEFT JOIN temporal t USING (fact_id) LEFT JOIN entity e USING (fact_id) WHERE s.fact_id IS NOT NULL OR t.fact_id IS NOT NULL OR e.fact_id IS NOT NULL;图遍历加上对 edges 表的递归 CTE,cross-encoder 重排在应用层对合并后的候选集运行。同一个数据库装下这一切,而这一切是同一个事务。
选择不是"用 Postgres 代替向量数据库",而是"因为检索模式想要 join,所以用 Postgres"。
Hindsight 的向量后端如何落地在 Postgres 上
Hindsight 的向量层不是硬编码 pgvector,而是通过 _vector_index.py 做统一分派,支持四种可配置扩展:pgvector(HNSW)、pgvectorscale(DiskANN)、vchord(vchordrq)以及 AlloyDB 上的scann,运行时还会解析 Azure 上的pg_diskann。环境变量HINDSIGHT_API_VECTOR_EXTENSION控制选择,默认pgvector。
各后端的索引子句(_INDEX_USING_CLAUSES):
| 后端 | CREATE INDEX USING 子句 | 扩展安装顺序 |
|---|---|---|
| pgvector | USING hnsw (embedding vector_cosine_ops) | vector |
| pgvectorscale | USING diskann (embedding vector_cosine_ops) WITH (num_neighbors = 50) | vector→vectorscale CASCADE |
| vchord | USING vchordrq (embedding vector_cosine_ops) | vchord CASCADE |
| scann | USING scann (embedding cosine) WITH (mode = 'AUTO') | vector→alloydb_scann CASCADE |
源码还体现了 ANN 检索时的调优细节:pgvector 支持hnsw.ef_search与hnsw.iterative_scan两档配置——低延迟档(retain 侧链接探测用)ef_search=60且关闭 iterative scan;高召回档(连接池初始化用)ef_search=200并开启strict_order迭代扫描,配合hnsw.max_scan_tuples上限(ann_iterative_scan/ann_max_scan_tuples两个配置项控制),让查询按自己的 LIMIT 决定扫描深度,而不是受一次扫描的候选列表封顶。索引创建策略上,ScaNN 有SCANN_MIN_ROWS_FOR_AUTO_INDEX = 10_000的最少行数门槛,其它后端默认在 bank 创建时即构建每 bank 的部分索引(per_bank_index_min_rows=0时急切构建),也可通过阈值配置改为按需维护。
这一切都证明:Hindsight 的向量能力是 Postgres 之上的可插拔底座,而不是一个独立的第二数据库。
AI Agent 需要向量数据库吗?
默认不需要。大多数 agent-memory 负载足够小,可以和应用数据一起放进带 pgvector 的 Postgres。外部向量数据库是为 RAG 规模的语料和读密集的相似度搜索而建的;agent memory 是写密集、更新密集的,从向量+图+时间组合检索中得到的收益大于原始 ANN 吞吐。确实存在真正的例外,见下节。
什么时候外部向量数据库仍然有意义
有三种情况会真正反转上面的结论。
超大向量数的多租户 SaaS。如果你为几百个租户各存几千万条向量,pgvector 的索引构建时间和共享资源争用会成为真问题。Pinecone 的 serverless 索引和 Weaviate 的租户隔离值得那份运维成本。
Agent memory 与真实 RAG 语料共享基础设施。如果你的团队已经为百万级文档的产品 RAG 运行向量数据库,把 agent memory 叠加到同一套基础设施上可能比另起一套 Postgres 更高效——向量库的边际成本已经付过了。
读吞吐主导的负载。有些 agent-memory 部署(企业知识库上的长尾召回、多区域读副本)形态上更像 RAG 而非 agent memory。专用向量数据库就是为这种形态设计的。
这些都不是默认情况。它们是负载已经跨回"向量数据库形态"领域的边界情形。
Agent Memory 的更好默认方案
适合大多数 agent-memory 负载的默认架构:
- 一条学习流水线,把事实、实体、边和合成观察写入同一个 Postgres——pgvector 管 embedding、图表管边、时间索引管事实生命周期;
- 多策略检索实现为对同一个数据库的并行查询,在应用层合并;
- 在应用层对合并结果做 cross-encoder 重排;
- 以单个容器部署,像任何 Postgres 一样备份。
这就是 Hindsight 实际发布的架构,而且不是巧合。架构之所以这样选,是因为负载要求如此;负载之所以这样要求,是因为 agent memory 不是 RAG。自托管只需要一条 Docker 命令,存储用的是嵌入式 PostgreSQL——没有需要额外搭建的外部向量库,也没有需要并行运维的图数据库。
结论
正确的问题不是"该为 agent memory 运行哪个向量数据库",而是两个问题:检索上游的学习流水线到底做什么?我的检索混合形态到底是什么样?对大多数团队,答案是:事实抽取、实体解析、矛盾处理和反思;以及一个大多不是语义相似度、大多不在十亿级、大多写密集、大多小到可以和应用数据并存的检索混合形态。一个 Postgres 同时承载这两层,比单独的向量数据库更好,运维成本还更低。
外部向量数据库在它们被建造的用途上依然出色。Agent memory 通常不在其中——而其中最不像 RAG 的部分是学习层,不是搜索层。
延伸阅读(仓库内对应文档):
- 本文档原文:The Case Against External Vector DBs for Agent Memory
- Hindsight 项目总览与架构背景见 hindsight-api-slim/README.md
- 四路并行检索实现:engine/search/retrieval.py、engine/search/fusion.py、engine/search/graph_retrieval.py
- 向量后端分派与索引策略:hindsight_api/_vector_index.py
- 相关测试:test_recall_min_score.py、test_interleave_fusion.py、test_combined_scoring.py
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考