Memoria 开发记录 27:日期不是语义——年度时间范围与检索计划可视化
前言
用户说“10月的照片”“国庆节假期”“春天的花”,这些查询看起来带有自然语言色彩,但其中的时间信息不应该交给语义模型猜。
语义模型可以理解“秋天氛围”“节日旅行”“春天花朵”,但照片是否拍摄于 10 月,应该由时间索引判断。否则搜索结果会变成看起来像 10 月的照片,而不是确实拍摄于 10 月的照片。
因此 Memoria 将这类查询拆成两部分:机械时间过滤 + 可选视觉语义。
年度日期为什么单独建模
有些时间查询没有指定年份:
10月
国庆节
春天
周末
晚霞
它们不是某一个绝对时间段,而是在每一年都会重复出现。用绝对时间无法表达“所有年份的 10 月”,用普通月份字段又无法表达“10 月 1 日到 10 月 7 日”这种节假日范围。
因此系统引入年度 MM-DD 范围:
10月 -> annual: 10-01..10-31
国庆节假期 -> annual: 10-01..10-07
春天 -> annual: 03-01..05-31
夏天 -> annual: 06-01..08-31
秋天 -> annual: 09-01..11-30
冬天 -> annual: 12-01..02-28
客户端只需要根据照片拍摄日期的 month/day 判断是否落在范围内。
为什么不让 LLM 算一年中的第几天
让 LLM 输出 day-of-year 看似更接近数据库字段,但这是不必要的复杂性。LLM 擅长语言理解,不擅长稳定做日历计算。闰年、月份天数和跨年范围都会增加错误概率。
更稳妥的方式是让 LLM 输出人类可读的 MM-DD:
{"start_date": "10-01", "end_date": "10-07"}
客户端再解析为结构化字段:
annualStartMonth = 10
annualStartDayOfMonth = 1
annualEndMonth = 10
annualEndDayOfMonth = 7
这样责任边界清楚:LLM 做语言归纳,客户端做确定性解析。
时间语义和视觉语义要分离
“晚霞”既包含视觉内容,也隐含时间。搜索时应该同时使用:
时间过滤:当地 15:30..21:00
视觉语义:sunset sky, warm evening glow
负向语义:sunrise and morning glow
如果只用视觉语义,可能搜出朝霞;如果只用时间过滤,又可能搜出普通傍晚照片。正确做法是机械条件和视觉条件共同生效。
同理,“国庆节假期的旅行照片”应该拆成:
年度日期:10-01..10-07
视觉主体:travel photo / sightseeing photo
日期负责过滤,旅行负责语义检索。
检索计划必须让用户看见
之前“10月”已经被解析成年度日期范围,但 UI 没有展示这个 filter,用户会以为系统根本没识别。对于搜索系统来说,这种不可见状态很危险:功能即使存在,也无法建立信任。
搜索页需要把所有 query plan 展示为 chip:
time=annual:10-01..10-31
time=local:15:30..21:00
weekday=周六/周日
geo=青岛市
semantic=cat
negative=sunrise
这既帮助用户理解结果,也帮助开发者快速定位 LLM 是否漏解析、客户端是否漏执行、UI 是否漏展示。
UI 展示不是装饰
很多系统把 filter 展示当成 UI 细节,但在自然语言搜索里,它是产品正确性的一部分。因为自然语言本身是模糊的,系统必须告诉用户它到底把话理解成了什么。
如果用户说“10月的海边”,UI 应该明确显示:
时间:每年 10-01 到 10-31
语义:beach / sea / coast
如果用户发现时间不对,可以调整查询;如果没有任何展示,只能猜系统坏了。
总结
日期不是语义,能用索引判断的时间信息就应该进入机械过滤。LLM 的任务是把自然语言映射到结构化条件,而不是替代数据库索引。
关键经验包括:
- 不指定年份的月份、季节、节假日需要年度日期范围;
- LLM 输出
MM-DD,客户端负责解析; - 晚霞、白天、夜晚等词同时可能包含时间和视觉语义;
- 时间条件不能被 embedding 分数替代;
- UI 必须展示完整 query plan;
- 可视化 filter 是搜索可信度的一部分。
对应提交:07c235e、89ecfe6、bda3372。
浙公网安备 33010602011771号