Spring AI RAG踩坑:我骂了半年的FilterExpression,其实是背锅侠

从 SimpleVectorStore 切到 Redis VectorStore,metadata 过滤直接罢工。我以为是 Spring AI 的 FilterExpression 是空壳,翻完源码才发现------是我自己少配了一个字段。


背景:为什么需要 metadata 过滤

我在做一个基于 Spring AI 的简历优化应用,核心是 RAG 检索。用户上传简历后,AI 根据岗位 JD 生成优化建议。

问题来了------不同行业、不同岗位、不同经验级别的简历,优化侧重点完全不同。10 年经验的技术总监不能参考应届生的模板。

所以检索逻辑需要精准过滤:

less 复制代码
FilterExpressionBuilder builder = new FilterExpressionBuilder();
FilterExpression filter = builder.and(
    builder.eq("industry", "互联网"),
    builder.eq("experienceLevel", "中级")
).build();
​
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder()
        .query("Java开发简历模板")
        .topK(5)
        .filterExpression(filter)
        .build()
);

逻辑没问题,接下来就是选 VectorStore 了。


SimpleVectorStore:开局很顺

一开始用的 SimpleVectorStore,内存版,配置极简:

typescript 复制代码
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
    return new SimpleVectorStore(embeddingModel);
}

三行代码搞定。然后 metadata 过滤完美运行------按行业过滤没问题、按经验级别过滤没问题、组合过滤也没问题。

我当时觉得 Spring AI 这波稳了。


切到 Redis VectorStore:过滤直接废了

数据量上来之后,内存版重启丢数据的问题不能忍,必须换持久化存储。

改配置:

scss 复制代码
@Bean
public VectorStore vectorStore(JedisPooled jedis, EmbeddingModel embeddingModel) {
    return RedisVectorStore.builder()
        .jedisPooled(jedis)
        .embeddingModel(embeddingModel)
        .build();
}

看起来比 SimpleVectorStore 就多两个参数,没什么难度。

结果一试检索------返回空。

Redis 里有数据,FT.SEARCH 也能搜到。但只要带上 FilterExpression,结果就是空的。


误判:我以为是 FilterExpression 的锅

搜了一圈,发现社区里确实有人吐槽 Spring AI 的 FilterExpression 不好用,说底层实现有问题。

我信了。

在代码里加了个注释:// TODO: FilterExpression在Redis VectorStore里疑似空壳,等官方修复

上一篇文章里也暗示了这一点。

但这个判断是错的。


翻源码:真相让人尴尬

有天闲下来翻了 Spring AI 的源码,看完就傻了。

SimpleVectorStore 的 FilterExpression 实现,完整得不能再完整。 底层遍历文档、解析表达式、逐条匹配,逻辑清晰,根本不是空壳。

然后翻 Redis VectorStore 的 doSave 方法,发现了关键:

typescript 复制代码
// RedisVectorStore源码(简化)
public void doSave(List<Document> documents) {
    if (this.metadataFields != null) {
        // 只有配了metadataFields,metadata才会被提取并存储
        for (Document doc : documents) {
            Map<String, Object> meta = extractMetadata(doc, this.metadataFields);
            // 存到Redis...
        }
    }
}

metadataFields 没配置 → metadata 存不进去 → 过滤时匹配不上 → 返回空。

这不是 FilterExpression 的锅,是 Redis VectorStore 少配了 metadataFields


正解:Redis VectorStore 配置三件套

翻文档、反复测试,终于搞清楚完整配置。三件套一个都不能少:

scss 复制代码
@Bean
public VectorStore vectorStore(JedisPooled jedis, EmbeddingModel embeddingModel) {
    return RedisVectorStore.builder()
        .jedisPooled(jedis)
        .embeddingModel(embeddingModel)
        .initializeSchema(true)              // ① 自动创建索引
        .indexName("resume-ai")              // ② 索引名
        .prefix("doc:")                      // ③ key前缀
        .metadataFields(                     // ④ 元数据字段schema(关键!)
            MetadataField.text("experienceLevel"),
            MetadataField.text("industry"),
            MetadataField.text("position")
        )
        .build();
}

逐个说明:

配置项 作用 踩坑点
initializeSchema(true) 自动创建 Redis 搜索索引 手动建可能格式不对
indexName + prefix 索引名和 key 前缀,隔离不同项目 不设会跟其他数据混
metadataFields 告诉 Redis 哪些是元数据字段 不配就存不进去,过滤必返回空!

重点说 metadataFields。它的本质是一个 schema,告诉 Redis:"这些文档里有 experienceLevel、industry、position 字段,过滤时靠它们。"

没配这个,Redis 不知道你存了什么字段,FilterExpression 再怎么写也是白搭。


验证:配齐之后

配齐三件套,重新写入数据,再跑过滤:

less 复制代码
FilterExpressionBuilder builder = new FilterExpressionBuilder();
FilterExpression filter = builder.and(
    builder.eq("industry", "互联网"),
    builder.eq("experienceLevel", "中级")
).build();
​
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder()
        .query("Java开发简历模板")
        .topK(5)
        .filterExpression(filter)
        .build()
);
// ✅ 结果正常返回

对比:SimpleVectorStore vs Redis VectorStore

特性 SimpleVectorStore Redis VectorStore
存储 内存,重启丢失 Redis 持久化
FilterExpression ✅ 完整实现 ✅ 完整实现
metadata 过滤 开箱即用 需配 metadataFields
生产可用
配置复杂度 零配置 三件套必须配齐

结论:两个 VectorStore 的 FilterExpression 都不是空壳。 是我在切 Redis 的时候少配了 metadataFields,导致元数据根本没存进去。


四条经验

  1. 别用 SimpleVectorStore 做生产------重启一次数据全丢
  2. Redis VectorStore 的 metadataFields 必须配------不配元数据存不进去,过滤必返回空
  3. 有问题先翻源码------别轻信社区传言,我信了"FilterExpression 空壳"的说法,结果是自己配错了
  4. 搜索问题加上版本号------Spring AI 迭代很快,去年的问题今年可能已经修了

你在 Spring AI RAG 上踩过什么坑?评论区聊聊,说不定能帮到其他人~


相关推荐
甲维斯1 小时前
GLM5.2超过Opus4.8Think,全球第二了!
前端·人工智能·ai编程
by————组态1 小时前
Ricon组态系统 - 新一代Web可视化组态平台
前端·后端·物联网·架构·组态·组态软件
云技纵横1 小时前
ThreadLocal 内存泄漏:你的应用正在悄悄 OOM
后端
小撒的私房菜1 小时前
Multi-Agent 里谁来指挥?我用一个调度员,让多个 Agent 开始协作
人工智能·后端·agent
范什么特西1 小时前
Spring boot细节
java·spring boot·后端
苍何2 小时前
高考填志愿,我做了个 Skill,300 个 Agent 同时查公司
后端
小林coding2 小时前
编程卷王 Kimi K2.7 Code 上线!一手实测,夯爆了还是拉完了?
ai编程
yspwf2 小时前
NestJS 配置管理完整方案
后端·架构·node.js
雪隐2 小时前
个人电脑玩AI-03让5060 Ti给你打工——paddleOCR
人工智能·后端