作者指出向量数据库在高并发同步、ANN召回抖动、无法精确过滤等场景下运维成本高,结构化时序数据用SQL可实现可预测的精确召回,单机SQLite即可承载典型Agent记忆场景。
Vector Store 为大多数 Agent Memory 增加了延迟和复杂度
2024 年 Agentic LLM Memory 的默认方案是向量数据库,配上光鲜的 API 宣称支持大规模语义搜索。文档大肆宣传 embedding 驱动的查询,把 SQL 当作遗留技术。
但对大多数 Agentic 工作负载而言,这恰恰搞反了。除非你拥有上亿级向量规模,否则向量数据库只会带来同步/异步胶水代码、运维开销、令人意外的 ANN 召回 quirks,以及调试头疼。你失去了按用户、时间戳或标签对查询进行可预测推理的能力。
当你需要精确、有作用域的召回时——"最近的工作记忆里,关于 X 主题、自 10 分钟前以来的事实是什么"——一条 SQL 查询就能搞定,经典 SQL 在这里胜出。向量搜索给你的是尽力而为的 top-k 返回,当 embedding 漂移或升级模型时常常不稳定。ANN 召回不是经典键值+过滤查询的对手。
向量数据库做的是纯粹的语义"砸抢",不是分层或情景记忆。SQL 直接处理所有细节——结构化、时序、过滤召回——统统搞定。
具体例子:SQLite "memory" 表,包含 text、timestamp、tag 和 blob embedding。要获取过去一小时内标记为 'alpha' 的 Agent 记忆:
import sqlite3
import time
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("""
CREATE TABLE memory (
id INTEGER PRIMARY KEY,
text TEXT,
ts REAL,
tag TEXT,
embedding BLOB
)
""")
now = time.time()
cur.execute("INSERT INTO memory (text, ts, tag) VALUES (?, ?, ?)", ("Fix bug in alpha repo", now - 60, "alpha"))
cur.execute("INSERT INTO memory (text, ts, tag) VALUES (?, ?, ?)", ("Lunch with team", now - 120, "social"))
cur.execute("INSERT INTO memory (text, ts, tag) VALUES (?, ?, ?)", ("Review alpha doc", now - 180, "alpha"))
conn.commit()
recent = cur.execute(
"SELECT text FROM memory WHERE tag = ? AND ts > ?", ("alpha", now - 120)
).fetchall()
print([r[0] for r in recent]) # Output: ['Fix bug in alpha repo']
没有 ANN 近似,没有混合后过滤。确定性、可解释、即时可扩展。
用 Chroma 或 Pinecone 试试,你最后只能和事后过滤、多阶段 API 或自己硬凑查询+过滤循环较劲。
"好吧,那语义相似性呢?"你可以在 SQL 中存储 embedding,然后对 Agent 记忆做暴力相似度计算。对于 10 万条以下的数据表,带向量列的内存 SQLite 依然快速且本地化。
SQLite 表 + 余弦相似度(numpy)
import numpy as np
import sqlite3
def make_embedding(text):
return np.random.rand(384).astype(np.float32) # Real model would go here
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("""
CREATE TABLE memory (
id INTEGER PRIMARY KEY,
text TEXT,
embedding BLOB
)
""")
for txt in ["Fix alpha bug", "Go to lunch", "Review docs"]:
emb = make_embedding(txt).tobytes()
cur.execute("INSERT INTO memory (text, embedding) VALUES (?, ?)", (txt, emb))
conn.commit()
def cosine_sim(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
q_emb = make_embedding("alpha bugfix")
rows = cur.execute("SELECT text, embedding FROM memory").fetchall()
sims = [(txt, cosine_sim(np.frombuffer(emb, np.float32), q_emb)) for txt, emb in rows]
top = sorted(sims, key=lambda x: -x[1])[0]
print("Best SQL match:", top)
Chroma/向量数据库示例
# pip install chromadb sentence-transformers
import chromadb
from sentence_transformers import SentenceTransformer
client = chromadb.Client()
collection = client.create_collection("memory")
model = SentenceTransformer('all-MiniLM-L6-v2')
docs = ["Fix alpha bug", "Go to lunch", "Review docs"]
ids = [str(i) for i in range(len(docs))]
embeddings = model.encode(docs).tolist()
collection.add(documents=docs, ids=ids, embeddings=embeddings)
q_emb = model.encode(["alpha bugfix"]).tolist()
results = collection.query(query_embeddings=q_emb, n_results=1)
print("Best vector DB match:", results['documents'][0][0])
在 10 万行以下的数据表上,SQLite 中暴力 numpy 计算可达 5-20ms/查询。网络向量数据库除非走批处理或分片模式,否则很难在单次查询上匹敌这个速度。达到 100 万行以上或多写入者并发时,SQL 就跟不上了,除非你迁移到运维密集型的 faiss 或 pq-index 方案。对于每次轮次 4k token 以下或 <50 条文档召回的 Agent 记忆,经典 SQL 在速度和透明度上难以被击败。
Agent 记忆不是扁平的事件日志。它有近期缓存、一个情景/工作区切片,以及长期归档。这些在 SQL 中都能干净地建模。
memory_event: id, timestamp, agent_id, level (cache/working/long_term), text, tag, embedding
memory_index: 聚合上下文、多 Agent 链接、会话链
复合索引: (agent_id,level,timestamp), (embedding)
多级召回示意图:
想象三张 SQL 表:
Cache 表通过 TTL 过期或被基于时间的任务清除。近期高频事件,召回边界紧凑。
Working Memory 每个 Agent/会话最近 N 条事件。按 (agent_id, timestamp) 建索引以实现快速切片。
Long-Term Archive 压缩或向量化。用于远程召回、批处理任务或语义搜索。
升级与 TTL 三角:事件向上移动到长期存储;过期/休眠项向下清除。跨级别查询通过 SQL UNION 或连接谓词完成——无需路由、无需多轮图遍历。
带 embedding 的纯 SQL 在以下场景会失效:
数千万以上向量和暴力查询(除非你再加上 faiss/ann-index)。
密集多关系图(例如带多跳遍历的知识图谱)。
大规模语义+图混合搜索。
这些场景在 Agentic 记忆工作负载中很罕见。除非你需要对数百万以上条目做即时非结构化 ANN 或深度语义图推理,否则经典 SQL 仍然是最低延迟、最易理解的答案。
除非你真正需要大规模语义或图搜索,否则 SQL 仍然是 Agentic "记忆"的最佳默认方案。在假设向量数据库或知识图谱是升级之前,先基准测试你的工作负载。对于大多数 Agent 记忆,它们是复杂度——SQL 才是真正的快车道。