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

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法