指出当前 Agent 记忆的标准实现(向量存储 + top-k 相似度检索)只擅长语义搜索,无法处理 COUNT/GROUP BY 等聚合统计查询,导致返回错误的数字,需要多层存储架构。
最初发布于 nlqdb.com/blog
标准的 Agent 记忆系统,花一个下午就能搭好:为每条值得保留的信息生成 embedding,将其 upsert 到 vector store,并在每次回复之前,取回相似度最高的 top-k 条记忆,放进上下文。对于它原本要解决的问题,这套方案确实有效。你问:“这位用户对 Berlin migration 说过什么?”正确的片段就会按照余弦距离排序返回。记忆召回的问题解决得足够好了,以至于让人觉得记忆问题本身也已经解决了。
然后,Agent 运行了一个月,你开始向它的记忆提出另一类问题:“这个月有多少用户咨询过定价?”“各阶段的平均交易金额是多少?”“我记录最多的 10 个主题是什么?按出现次数排序。”vector store 会尽职尽责地返回与问题文本最相似的 20 条记忆,LLM 粗略查看一遍,接着给出一个自信、具体,却错误的数字。
召回依赖相似度。报表依赖聚合。
没有任何组件发生故障——只是这两类问题需要两种不同的机器。vector store 的基本能力是最近邻搜索:为查询生成 embedding,按照距离对存储的向量进行排序,返回 top-k 条结果,还可以选择通过 metadata filter 缩小范围。这就是它所承诺的全部能力。它没有 COUNT,没有 GROUP BY,没有 JOIN,也没有 HAVING——相似度引擎不提供 query planner。即便 metadata filter 也只是围绕近似搜索缩小候选范围,因此最终返回的依然是相似条目的排名,而绝不会是经过计算得出的结果集。
“有多少”必须访问每一条符合条件的记录。如果 Agent 记录了 4,000 条记忆,而 top-k 是 20,那么 LLM 看到的上下文从结构上就不可能得出正确计数——让 LLM 基于检索出来的样本做算术,只会得到一个幻觉生成器,而不是 query engine。而且这种失败悄无声息:答案表达流畅、听起来也很合理,却没有任何机制提醒你,它仅根据全部数据的 0.5% 计算得出。
-- "top topics this month, ranked by count" is not a similarity query.
-- It's this — and it must scan every matching row, not the top-k:
SELECT topic, count(*) AS mentions
FROM memories
WHERE created_at >= date_trunc('month', now())
GROUP BY topic
ORDER BY mentions DESC
LIMIT 10;
因此,真正值得记住的分界线是:当问题是“查找与此类似的内容”时——例如 RAG 上下文、相关文档检索、对话内容的模糊召回——vector store 就是完全正确的选择,Pinecone 这类托管服务也确实很擅长这件事。而当问题是“对我存储的数据进行计数、分组或排序”时,记忆就需要以类型明确的行存储在真正的 query planner 背后。这两种机器无法互相替代;给第一种机器接上更大的 LLM,也不会让它变成第二种。
二者可以清晰地组合起来:vector store 作为召回层,relational store 作为分析层。nlqdb 正是这个第二层——它是一个真正的 Postgres,Agent 可以通过 MCP 自行创建,并使用自然语言查询;同时还会展示编译后的 SQL,让你清楚看到实际执行了什么。坦诚地说,另一个方向也有同样的限制:nlqdb 不提供 embedding 搜索,因此它不能充当召回层。应该根据问题的形态选择存储方案——完整的并列对比请参阅 nlqdb 与 Pinecone 的对比。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。