Spring AI 11 · 元数据过滤 FilterExpression

11 · 元数据过滤 FilterExpression

🎯 学完能做什么:用字符串表达式和 FilterExpressionBuilder 两种方式,按 metadata 精确圈定检索范围,实现多租户、分类、时间过滤。
⏱️ 预计耗时:40 分钟(含动手)
🔗 依赖前置:第 09、10 章 + pgvector 文档 14(带过滤的向量检索)
🧩 难度:🟡

一句话:向量检索负责「找最像的」,元数据过滤负责「限定在哪些里面找」;两者结合才能做出多租户、分权限、按分类的生产级检索。

1. 为什么需要过滤

只靠相似度会检索到「全库最像的」,但业务上你往往只想在特定范围内找:

只查 租户 1001 的资料
只查 分类=售后 的资料
只查 2025 年之后更新的资料

这些「范围」信息就存在灌库时写的 metadata(第 09 章)里,过滤就是对 metadata 下条件。

2. 方式一:字符串表达式(直观)

List<Document> hits = vectorStore.similaritySearch(
        SearchRequest.builder()
                .query("退货政策")
                .topK(3)
                .filterExpression("category == '售后' && tenantId == '1001'")
                .build()
);

支持的运算符:

运算符含义示例
== !=等于 / 不等于category == '售后'
> >= < <=比较year >= 2025
in nin在/不在集合category in ['售后','物流']
&& ||与 / 或a == 1 && b == 2
NOT取反NOT (status == 'draft')

3. 方式二:FilterExpressionBuilder(类型安全)

代码里动态拼条件时更安全、不易写错:

FilterExpressionBuilder b = new FilterExpressionBuilder();

Filter.Expression expr = b.and(
        b.eq("tenantId", "1001"),
        b.in("category", "售后", "物流")
).build();

List<Document> hits = vectorStore.similaritySearch(
        SearchRequest.builder()
                .query("退货")
                .topK(3)
                .filterExpression(expr)
                .build()
);

常用构建方法:eq / ne / gt / gte / lt / lte / in / nin / and / or / not,与字符串运算符一一对应。

4. 典型场景

多租户隔离(SaaS 必备)

// 每个请求都强制带上当前租户,避免串数据
String tenantId = currentTenant();
b.eq("tenantId", tenantId).build();

🔐 安全要点:多租户过滤应在服务端强制注入,绝不能让前端传、也不能漏——否则会跨租户泄露数据。

按分类 + 时间

b.and(
    b.eq("category", "公告"),
    b.gte("year", 2025)
).build();

动态拼接(有就加,没有不加)

FilterExpressionBuilder b = new FilterExpressionBuilder();
List<Filter.Expression> parts = new ArrayList<>();
parts.add(b.eq("tenantId", tenantId).build());
if (category != null) parts.add(b.eq("category", category).build());
// 用 and 逐个合并 parts ...

5. 过滤 + 相似度的执行关系

similaritySearch:
  1) 先按 filterExpression 圈定候选(metadata 命中的行)
  2) 在候选里按向量相似度排序,取 topK / 过阈值

⚠️ 性能提醒:过滤条件很「窄」(命中极少)时,纯向量索引可能出现召回不足。这在 pgvector 文档 21、22(带过滤检索的召回塌陷、迭代扫描)有系统讲解,生产必读。

6. 让过滤生效的前提:metadata 要灌对

过滤能不能用,取决于灌库时有没有写对应字段(回顾第 09 章):

// 灌库时写入可过滤字段
vectorStore.add(List.of(new Document(content, Map.of(
        "tenantId", "1001",
        "category", "售后",
        "year", 2026
))));

🔑 「先想清楚要按什么筛,再决定 metadata 放什么」——过滤能力是灌库阶段就注定的。

7. 常见坑

现象原因
过滤后结果为空metadata 里根本没这个字段/值;或类型不符(数字写成字符串)
过滤没生效字段名拼错,或值大小写/类型不匹配
窄过滤召回很差命中太少 + ANN 索引特性,见 pgvector 21/22
跨租户看到别人数据服务端没强制注入租户过滤

8. 一句话总结

元数据过滤用 filterExpression(字符串或 Builder),在相似检索前圈定范围,是多租户、分类、时间等生产场景的基础;能不能过滤取决于灌库时 metadata 写得对不对。


🎉 第二阶段完成! 你现在掌握了:生成向量、配置 pgvector、写入(含幂等)、相似检索、元数据过滤——RAG 的数据层已经打通。

➡️ 下一阶段(第 12–15 章)进入 文档处理 ETL:把真实的 PDF/Markdown 读进来、分块、富化 metadata、批量灌库,让知识库有真正的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倒流时光三十年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值