StackTraces
Spring AI已验证

Spring AI 相似度阈值 Similarity threshold

@debug_master13 天前更新阅读 6 min00

similarityThreshold 的取值必须落在 0~1。写超过 1 或小于 0,SearchRequest.Builder 里的 Assert 会先拦下来抛异常。另一头更隐蔽:阈值调太高(比如 0.8)召回 0 条,RetrievalAugmentationAdvisor 默认不允许空上下文,模型就固定回一句“无法根据提供的信息回答”。

Issue & Env(案发现场与环境)

  • 业务场景:给 RAG 检索调参。有人想“过掉不相关结果”,把阈值写到 1 甚至更大;也有人调高阈值后,回答质量反而变差。
  • 环境:Spring AI 1.1.x,QuestionAnswerAdvisor 或 RetrievalAugmentationAdvisor 搭配 VectorStoreDocumentRetriever。

StackTrace(堆栈日志)

阈值越界时,构造 SearchRequest 这一步就抛异常:

java.lang.IllegalArgumentException: Similarity threshold must be in [0,1] range.
    at org.springframework.ai.vectorstore.SearchRequest$Builder.similarityThreshold(SearchRequest.java:186)

还有一种是“没有异常”的版本:检索命中 0 条文档,模型回一句“无法根据提供的信息回答该问题”。日志干干净净,最容易当成模型变笨。

Reproduce(复现代码)

// 越界:构造时就抛异常
SearchRequest request = SearchRequest.builder()
        .query("退款流程")
        .similarityThreshold(1.2d)   // 大于 1,Assert 拦下
        .topK(4)
        .build();

// 静默失忆:阈值偏高 + topK 很小,召回可能为空
SearchRequest strict = SearchRequest.builder()
        .query("退款流程")
        .similarityThreshold(0.8d)
        .topK(2)
        .build();

把 strict 交给 QuestionAnswerAdvisor 或 VectorStoreDocumentRetriever,就可能一条都命中不了。

Root Cause(根因分析)

一是取值范围的硬校验

similarityThreshold 的语义是相似度下限,取值是 0~1 的小数。Javadoc 说得很直白:0.0 表示接受任意相似度、相当于关掉阈值过滤;1.0 表示要求完全匹配。文档里的常量 SIMILARITY_THRESHOLD_ACCEPT_ALL 就是 0.0。源码里对应这一行:

Assert.isTrue(threshold >= 0 && threshold <= 1, "Similarity threshold must be in [0,1] range.");

越界直接抛 IllegalArgumentException,不会帮你裁剪到边界。

二是阈值与 topK 是两个维度

  • similarityThreshold:质量下限,低于它的结果一律丢。
  • topK:条数上限,最多返回几条。

阈值越高,留下的越少;再叠一个很小的 topK,很容易一条不剩。一个管“够不够像”,一个管“要几条”,别混着调。

三是空上下文触发了兜底话术

RetrievalAugmentationAdvisor 默认不允许检索上下文为空。官方原话是:上下文为空时,它会指示模型不要回答用户问题(it instructs the model not to answer the user query)。于是你看到的,就是一句固定的“我无法根据提供的信息回答”。

The Fix(解决方案)

1. 阈值先落回 0~1

想看全部结果就用 0.0:

SearchRequest.builder()
        .query("退款流程")
        .similarityThreshold(0.0d)
        .topK(4)
        .build();

还有个等价写法 similarityThresholdAll(),效果就是把阈值设回 0.0。

2. 调参顺序:先松后紧

先用 0.0 或 0.5 看召回,再一点点收紧。别一上来就给 0.8。

QuestionAnswerAdvisor 的常规配置长这样:

var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
        .searchRequest(SearchRequest.builder().similarityThreshold(0.5d).topK(6).build())
        .build();

3. 允许空上下文时自己兜底

确实想允许召回为空、由应用自行处理,就打开 allowEmptyContext。注意它挂在 ContextualQueryAugmenter 上:

Advisor advisor = RetrievalAugmentationAdvisor.builder()
        .documentRetriever(VectorStoreDocumentRetriever.builder()
                .similarityThreshold(0.50)
                .vectorStore(vectorStore)
                .build())
        .queryAugmenter(ContextualQueryAugmenter.builder()
                .allowEmptyContext(true)
                .build())
        .build();

4. 先确认库里真的有数据

召回为空有时跟阈值无关:文档根本没灌进去,或者写入和查询用的不是同一个 embedding 模型。先确认数据在库里,再谈阈值。

References(参考来源)

最后更新于 2026-09-28

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法