Appearance
3.3.4 Metadata Filter 在检索阶段怎么用?
先给结论:metadata filter 的核心作用,不是让检索“更花哨”,而是先把不该进入候选集合的内容挡在外面。
它本质上是在告诉系统:
不是所有文本都该一起比相关性,先把边界收窄,再谈语义匹配。
metadata filter 到底在做什么
最直接地说,metadata filter 就是在检索前或检索时,基于 metadata 限定候选范围。
比如只看:
- 当前租户内容
- 当前有效版本
- 当前用户有权限看的内容
- 某个产品线、地区、部门下的内容
这样后面相关性匹配,不是在全量内容里乱比,而是在“合理边界内”做比较。
为什么它在检索阶段这么重要
因为如果不先做 filter,系统就更容易出现两类问题:
1. 不该看的内容先混进候选集合
这会直接带来权限和多租户风险。
2. 本来就不该比较的内容一起参与相关性竞争
比如旧版本和新版本一起比、不同产品线内容一起比、不同地区规则一起比。
这时即使语义检索本身没错,最终结果也可能不稳。
一个最小示意
python
chunks = [
{
"text": "租户 A 退款规则:7 天内可退。",
"metadata": {"tenant_id": "tenant-a", "status": "active"}
},
{
"text": "租户 B 退款规则:15 天内可退。",
"metadata": {"tenant_id": "tenant-b", "status": "active"}
},
{
"text": "租户 A 旧版退款规则:3 天内可退。",
"metadata": {"tenant_id": "tenant-a", "status": "deprecated"}
}
]
def retrieve(query: str, tenant_id: str) -> list[dict]:
return [
chunk
for chunk in chunks
if chunk["metadata"]["tenant_id"] == tenant_id
and chunk["metadata"]["status"] == "active"
]这个例子虽然简单,但已经体现了 metadata filter 的核心价值:
先缩小边界,再谈相关性。
Metadata Filter 常见会过滤什么
最常见的通常包括这几类:
1. 版本和状态
比如:
- 只看
active - 排除
draft - 排除
expired
2. 权限和租户
比如:
- 当前
tenant_id - 当前角色可见范围
- 当前部门或组织边界
3. 业务范围
比如:
- 某个产品线
- 某个区域
- 某个语言
- 某类文档类型
这些 filter 的价值,不只是“更精准”,而是让检索先在合理空间里发生。
为什么 metadata filter 不是“语义检索的替代品”
它和语义检索不是二选一关系,而是分工不同。
- metadata filter 负责先收边界
- 语义检索负责在边界内找相关内容
如果只有 filter,没有语义检索,系统可能只是在一个小集合里乱翻。
如果只有语义检索,没有 filter,系统又可能在一个本来就不该混在一起的大集合里瞎比。
所以更稳的检索通常是两者配合。
一个更贴近实际的检索顺序
很多真实系统更像这样:
- 先拿到当前用户、租户、时间、业务上下文
- 把这些上下文翻译成 metadata filter
- 在 filter 限定范围内做向量检索、关键词检索或混合检索
- 再做后续排序和上下文构造
这条顺序的价值是:先把边界做对,再去谈“相关性更准”。
怎么判断当前问题更像 filter 没做好
如果你经常看到这些现象,通常更像 metadata filter 有问题:
- 新旧版本总一起出现
- 不同租户内容会混进同一次召回
- 某个产品线的问题总被其他产品线内容干扰
- 用户没权限看的内容偶尔还能被命中
这类问题表面上像“检索不准”,但很多时候根因并不在 embedding,而在 filter 根本没先收好边界。
一个常见误区
很多团队会把 metadata filter 理解成“后面需要时再加”的优化项。
这通常很危险。
因为很多边界如果不在检索阶段就挡住,后面即使模型不展示原文,内容也可能已经:
- 进入候选集合
- 进入日志和 trace
- 进入缓存
- 进入模型上下文
所以 metadata filter 在很多场景里其实不是优化项,而是第一道闸门。
一句话总结
Metadata filter 在检索阶段的核心作用,是先把不该参与比较的内容挡掉,让语义匹配只发生在合理边界内。很多看起来像检索问题的故障,本质上其实是 filter 没先做好。