ADR-0002: 时间检索 — time_range 门控 + 正则快通道,不做第三路融合
- 状态:Accepted(2026-07-21 提出,随设计 PR #14 评审定稿)
- 日期:2026-07-21
- 实施:⚠️ 部分落地 —— 时间门控本体已落地:#26 #27 #31(跟踪 issue #25);§6 的 recency 衰减项尚未实现,默认值待 bench 数据,与 ADR-0006 ④ 同属一个里程碑
- 关联:ADR-0001(日记原语,本决策的检索对象)、memU ADR-0007(min-max 融合公式来源)、XnneHangLab ADR-0004(宿主侧时间意图识别 tool call)
背景
需求:回答"前天晚上我吃了什么"类查询。时间没有相关性分数,无法进现有的 min-max 加权融合;直觉备选是把三路(BM25 / 向量 / 时间)全改 RRF 排名融合。
设计讨论(博客《RRF vs Hybrid Search》)得出的关键分析:
- 时间和另外两路不是同类信号。语义分和关键词分回答"这条记忆是不是在说这件事"——相关性,适合投票;时间回答"这条记忆在不在所问的范围里"——约束。窗口外的记忆不是"不太相关",是错的。约束不该投票,该过滤。
- 无条件投票者 = 系统性偏置。BM25/向量的候选列表是查询条件化的——不匹配就缺席;时间对任何 query 都能排出完整榜单。让它永远参与 RRF 投票,等于给所有查询注入 recency 噪声。
- 三路 RRF 也省不掉时间解析器。时间路要有意义,得先把"前天晚上"解析成区间才能按距离排序——解析器横竖要写;而解析完手里已有区间,投票反而是绕路:投票保证不了赢家落在区间内(一条三周前印象深刻的晚餐可能在语义上碾压前天那顿平平无奇的)。
- "双融合 + 分歧触发"的触发器测错了对象:时间榜与语义榜分歧是常态不是信号,"该不该用时间"的证据在 query 里。
结论:时间做门控(pre-filter),不做检索路。
决策
1. API:一个参数,两条来路
python
index.retrieve(query, time_range=None, ...)- time_range 显式传入(宿主意图识别 / tool call 的出口)→ 直接使用,跳过内部解析。
- 未传入 → 正则快通道:纯 stdlib(
re+datetime)解析可枚举的时间表达(昨天 / 前天 / 上周X / X天前 / X月X号 / ISO 日期等,清单实现期定,宁窄勿误——解析不出就当无时间意图,不猜)。命中即开窗。 - 不引入 dateparser / arrow / TimeNLP——零依赖硬约束优先,
timedelta能算的事不加库。 - 正则是框架的地板,LLM 是宿主的天花板,中间不存在第三个解析器。
2. 门控语义
窗口 → 日记文件集(文件名即时间索引,ADR-0001)→ 在窗口内候选集上执行现有检索原样不动(BM25 即时构建;向量走既有 content-hash 缓存按窗口子集过滤)。min-max 融合公式零改动。
3. 退化情形:时间独立工作
query 为空或无实义、但有窗口 → 按时间倒序取 top-N。这就是 MoeChat"整段召回日记"行为的受控版——时间检索无需第三路投票也能独立工作。
4. 兜底:放宽而非空手
窗口内零命中 → 自动放宽(如 ±1 天;"上周中午"这类模糊边界直接取宽区间),explain=True 中标注放宽动作。仍零命中才返回空(fail-open 由宿主处理)。别让一次误解析变成"我不记得了"。
5. 时区
存储 ts 保持 UTC ISO-8601 不变;正则快通道解析"昨天"这类相对表达时使用传入的 tz 参数(缺省取系统本地时区),窗口比较统一换算到 UTC。
6. 可选 recency 衰减项(隐式新鲜度)
用户没提时间、但新近记忆应更易浮现的需求,不做第三路:作为小权重衰减项(形如 exp(-Δt/τ))参与现有 min-max 分数融合。默认关闭,是否默认开启由 bench 数据决定。
7. 永不
- 时间永不作为 RRF / 融合的投票路。
- wiki 状态层默认不受 time_range 影响——时间轴只属于日记("上周新增的偏好"这类过滤需求出现了再议)。
理由
- 门控在工程上就是向量检索里标准的 metadata pre-filter,成熟、可解释、零公式改动。
- 显式时间指称是硬约束(过滤),隐式新鲜度是软偏好(衰减项)——时间的两个角色各得其所,"第三路"这个概念整体消失。
- 事件锚定的两跳查询("上次我们吵架那天"→ 先检索事件拿日期,再开窗)天然由门控支持,由宿主 tool call 编排;投票路线根本无法表达。
后果
正面
- 时间检索可独立工作(退化情形),也可与语义检索组合(窗口内 hybrid)。
- 融合公式、explain、token 预算裁剪全部复用,改动面收敛在"候选集从哪来"。
负面 / 代价
- 正则清单是一个持续维护面(中文时间表达是开放集)。缓解:宁窄勿误 + 解析不出交给宿主 LLM。
- 窗口内 BM25 即时构建有小成本(天级文件量可忽略;全库 BM25 索引现状不变)。
time_range的边界语义(闭开区间、跨时区日界)需在 API 文档一次性钉死。
实施
依赖 ADR-0001 落地;建议同一里程碑(M5)内实现:快通道解析器(含测试表)→ 窗口检索 → explain 扩展。