ADR-0003: 向量缓存记录 model/dim — 失配警告并降级,而非报错重建
- 状态:Accepted(2026-07-21 提出,随设计 PR #14 评审定稿)
- 日期:2026-07-21
- 实施:✅ 已落地 —— #33
- 关联:XnneHangLab ADR-0001(四次修订:向量分层与 keys.jsonl 形态)、
docs/reference/vectors.md
背景
现状:vectors.keys.jsonl 的 header 只记录 vectors_file,embedding 模型名与维度都没有记录。
风险:用户更换 embedding 端点/模型后,缓存里的旧向量与新查询向量来自不同语义空间。维度不同时会在运算处炸出难解的错误;维度相同时甚至不会报错——静默错配,融合质量悄悄劣化,且无从排查。
现有哲学(必须对齐):embedding 失败从不 raise,静默降级 BM25-only;zero-embedding 模式是一等公民。
决策
1. header 增记模型元数据
写缓存时在 vectors.keys.jsonl 首行 header 落盘:
json
{"vectors_file": "vectors-000123.npy", "model": "<配置的模型标识>", "dim": 1024}2. 打开时比对,失配即降级
与当前配置的模型标识比对;端点实际返回的维度与 dim 不符同样按失配处理:
- 一次性警告(日志 + CLI 提示),说明缓存由哪个模型产生、当前配置是什么;
- 本会话内该缓存整体视为不可用,检索降级 BM25-only——零 API 成本,行为可预期;
- 不自动重嵌:重嵌要花钱(调 embedding 端点),必须由用户显式触发(CLI 子命令或配置项,实现期定名)。content-hash 键控使重嵌天然增量、可中断续跑。
3. 没有"重建数据库"这个概念
真相是 markdown(硬约束 3),向量只是派生缓存。换模型 = 缓存作废 + 显式增量重嵌,仅此而已。向量永不写入真相文件——不在记忆条目上"预留 embedding 位置",那会毁掉"磁盘无不可读真相"。
4. legacy 缓存的兼容
旧 header(无 model/dim 字段):继续可用 + 警告一次("来源模型未知,建议重嵌以打标");下次任何缓存写入自然补全 header 字段。不做升级即作废的惩罚。
理由
- 静默错配是当前设计里唯一"坏得无声无息"的路径,元数据比对是最小修复。
- 失配后的行为完全落在既有降级路径上(BM25-only 本来就是 zero-embedding 的日常形态),不引入新状态。
- "警告 + 降级 + 显式重嵌"比"报错 + 强制重建"更符合 fail-open 哲学,也不会让用户在无网/无预算时被锁死。
后果
正面
- 换模型从"静默劣化"变成"显式可见、可控成本的一次操作"。
- header 仍是明文 jsonl,可读性不破。
负面 / 代价
- 模型标识以宿主配置字符串为准(同模型不同别名会被误判为失配)——文档言明即可,不做模糊匹配。
- 多一条 CLI 子命令(重嵌)的维护面。
实施
改动集中在 vectors.py(header 读写与比对)+ CLI 一条子命令 + 测试(失配降级、legacy 兼容、维度防御)。