Skip to content

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》)得出的关键分析:

  1. 时间和另外两路不是同类信号。语义分和关键词分回答"这条记忆是不是在说这件事"——相关性,适合投票;时间回答"这条记忆在不在所问的范围里"——约束。窗口外的记忆不是"不太相关",是错的。约束不该投票,该过滤。
  2. 无条件投票者 = 系统性偏置。BM25/向量的候选列表是查询条件化的——不匹配就缺席;时间对任何 query 都能排出完整榜单。让它永远参与 RRF 投票,等于给所有查询注入 recency 噪声。
  3. 三路 RRF 也省不掉时间解析器。时间路要有意义,得先把"前天晚上"解析成区间才能按距离排序——解析器横竖要写;而解析完手里已有区间,投票反而是绕路:投票保证不了赢家落在区间内(一条三周前印象深刻的晚餐可能在语义上碾压前天那顿平平无奇的)。
  4. "双融合 + 分歧触发"的触发器测错了对象:时间榜与语义榜分歧是常态不是信号,"该不该用时间"的证据在 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 扩展。

Released under the Apache-2.0 License.