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 上踩过什么坑?评论区聊聊,说不定能帮到其他人~


相关推荐
站大爷IP6 小时前
列表推导式一用就爽?大数据量下它把我服务器内存榨干了,原来生成器才是yyds
后端
Slice_cy6 小时前
Mint 自研框架设计与实现:从重复开发走向配置驱动(二)
前端·后端·架构
程序员cxuan6 小时前
OpenAI 把 Hugging Face 打穿了,然后 GLM 5.2 当了救火队长?
人工智能·后端·程序员
长情_6 小时前
给 AI 编程助手装一个"代码大脑":我是怎么用知识图谱 + 语义检索解决 Agent 代码理解问题的
ai编程
程序员天天困6 小时前
Arthas Profiler 火焰图实战:CPU 热点在哪一目了然
java·jvm·后端
今夜有雨.6 小时前
C++JSON 解析器
c++·笔记·后端·学习·json
AskHarries7 小时前
日志系统怎么搭
后端
swipe7 小时前
09|(前端转全栈)商品为什么不能随便上下架?后端状态机思维入门
前端·后端·全栈
码哥字节7 小时前
AI知识库搜不准?缺的不是更好的模型,是这三层内容筛选
ai编程