向量陈旧(文档变向量未更新)、语料漂移(新主题覆盖不足)、查询漂移(用户问新问题)、提供商漂移(托管模型悄然升级)——四种失败模式不同修复策略,混淆处理会导致无谓的全量重算。
四个问题,一个名字
需要注意的是,模型的权重并不会衰减。存储在磁盘上的向量也不会随着时间推移而降解。上述每一种情况都是“两个本应对应的事物之间出现了不匹配”,这也是为什么下面的每一个检测器都是一种比较而非测量。
这种区分并非学术讨论,因为四种问题的修复成本差异巨大。Stale vectors 是某一条代码路径中的 bug,修复只需花一个下午。Corpus drift 通常不需要任何成本。Query drift 损失的则是人类编写的内容,要花数月积累。Provider drift 则需要对所有数据进行完整的重新向量化。一个没有将它们区分对待的团队会倾向于选择最昂贵的解决方案——对整个语料库进行重新向量化——因为它看起来能够 plausibly 解决所有四种问题,然后当下一周症状再次出现时他们就会感到惊讶,因为真正的原因其实是一条从未将重新向量化加入队列的摄入路径。
如果你在存储向量的同时存储了一个 embedded_at 时间戳——这是存储时间戳的理由——那么检测器就是一条简单的查询:
SELECT count(*) FILTER (WHERE embedded_at < updated_at) AS stale,
count(*) AS total,
max(updated_at - embedded_at) AS worst_lag
FROM chunks;
把 stale 指标放到仪表板上,当它超过你的摄入延迟时长且持续非零时就发出警报。这就是一个完整的监控器,而且它能捕获大多数现实世界中“搜索返回了页面旧版本”的反馈。
一个相关的检查可以捕获删除操作那一半的问题:文档已不存在但向量仍存在的记录。在单个数据库中由于外键约束这种情况不会发生。但在两个系统之间这种情况就层出不穷,而对齐任务——向量存储中的 id 数量减去真相源中的 id 数量——就是需要绘制成图的那个数字。两个计数之间不断扩大的差异是“将向量与它们所描述的行存放在同一个数据库中”这一建议的最有力论据。
托管模型通常是有版本号的,行为良好的提供商会冻结某个版本。但模型会被静默更新,端点会被重新指向,而一次自我部署的升级可能由一个不知道这有多重要的人完成。后果很严重:你在变更之后写入的每一个向量,都与你在变更之前写入的每一个向量处于不同的空间中,而且系统不会有任何报错。
# once, at setup: store 100 fixed strings and their vectors
canary = ["the quick brown fox", "SELECT * FROM users", ...]
np.save("canary.npy", embed(canary))
# weekly, in CI:
ref = np.load("canary.npy")
now = embed(canary)
sim = (ref * now).sum(axis=1) # both unit length
assert sim.min() > 0.9999, f"embedding model changed: min sim {sim.min()}"
相同的输入经过相同的模型会产生相同的输出,仅有浮点数噪声的差异,所以阈值可以设置得非常严格。相似度 0.97 不是噪声——而是不同的模型。这个测试每周只消耗一百次向量化,却是你和“索引悄悄分裂成两个不兼容的一半”之间唯一的防线。
Corpus drift 和 query drift 是真实的分布漂移,需要的是统计手段而非断言。有三种便宜的方法:
Unanswered rate(未命中率)。最佳相似度低于你判断“无好答案”时所用阈值的查询比例。按周追踪。一条上升的线是你能拥有的最有用的 drift 信号,因为它是用结果来定义的而非用机制来定义的。
Query centroid movement(查询质心移动)。将本周的查询向量取平均,与上周的通过余弦相似度比较。稳定的产品应该保持高位平缓;如果出现急剧下跌,意味着你的用户在问一些新的东西,那一周的日志值得直接阅读。
Novelty of incoming documents(入站文档的新颖度)。对于每个新文档,计算它与最近邻现有文档的相似度。平均值下降意味着你的语料库正在向新领域扩展,这就是重新审视分块策略、以及检查在旧分布上调优的检索阈值是否仍然有效的信号。
这三个都是每个周期一个标量,可以从你已有的数据中计算,都不需要标注集。从第一天起就将它们存为时间序列——价值完全在于趋势,而在一个事件发生一周后才启动的监控无法告诉你它是什么时候开始的。
Stale vectors:修复摄入路径以确保更新会触发重新向量化,然后回填受影响的行。不要对整个语料库重新向量化;你遇到的是 bug,不是模型问题。
Corpus drift:通常不需要处理。通用向量化模型处理新主题完全没有问题。只有在新内容确实具有不同结构——文档长很多、新的语言、大量表格内容——时才需要重新审视,这种情况下要改的是分块器。
Query drift:这是内容缺口,不是向量问题。那些没有好答案的查询是一份优先级的文档待写清单,这是整篇文章中最有直接价值的产出。
Provider drift:立即停止写入新向量,如果提供商提供版本锁定就锁定模型版本,并将恢复作为一次完整的重新向量化迁移来处理,使用双列方式,因为它本质上就是一次迁移。
Re-Embedding: What Happens When You Change Models
Deduplication at Scale With Embeddings
Vector Databases Compared: What Actually Differs