知識庫 / Spring / Spring AI RSS 訂閱

利用 Spring AI 實現語義緩存

Artificial Intelligence,Spring AI
HongKong
11
10:35 AM · Dec 06 ,2025

1. 概述

在與大型語言模型 (LLM) 集成的現代應用程序中,當用户提交相似或改寫的提示時,我們實際上會向 LLM 發出重複的調用,從而導致不必要的成本和更高的延遲。

語義緩存 通過將用户的查詢以及 LLM 的響應存儲在向量存儲中來解決這一挑戰。當收到新的查詢時,我們首先在向量存儲中查找語義上相似、先前回答過的提問。如果找到匹配項,我們將返回緩存的響應,從而完全繞過原始的 LLM 調用。

在本教程中,我們將使用 Spring AI 和 Redis 構建語義緩存層

2. 設置項目

在開始實施我們的語義緩存層之前,我們需要包含必要的依賴項並正確配置我們的應用程序。

2.1. 配置嵌入模型

首先,我們將配置一個嵌入模型,該模型會將自然語言文本轉換為數值向量。 為了演示目的,我們將使用 OpenAI 提供的一個嵌入模型。

接下來,請在項目的 pom.xml 文件中添加必要的依賴項:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.3</version>
</dependency>

這裏,我們導入 Spring AI 的 OpenAI starter 依賴項,用於與嵌入模型進行交互。

接下來,我們配置 OpenAI API 密鑰,並在 application.yaml 文件中指定嵌入模型:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-3-small
          dimensions: 512

我們使用 `${}</strong> 屬性佔位符從環境變量中加載我們的 API 密鑰。

此外,我們指定了 text-embedding-3-large 作為我們的 512 維嵌入模型。 另一種嵌入模型也可以使用,因為具體的 AI 模型或提供商對於本次演示並不重要。

在配置這些屬性時,Spring AI 自動為我們創建一個類型為 EmbeddingModel 的 Bean</strong>。

2.2. 配置 Redis 作為向量存儲

接下來,我們需要一個向量存儲來保存我們的查詢嵌入和它們對應的 LLM 響應。我們將使用 Redis 作為目的,但請注意,我們也可以根據需求選擇其他向量存儲。

首先,讓我們添加所需的依賴項:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-redis</artifactId>
    <version>1.0.3</version>
</dependency>

Redis 向量存儲啓動器依賴項使我們能夠與 Redis 建立連接並將其作為向量存儲進行交互。

現在,讓我們配置連接 URL,以便我們的應用程序能夠連接到提供的 Redis 實例:

spring:
  data:
    redis:
      url: ${REDIS_URL}

我們再次使用屬性佔位符從環境變量中加載 Redis 連接 URL。需要注意的是,URL 應遵循 redis://username:password@hostname:port 的格式。

接下來,我們需要為我們的語義緩存實現配置一些自定義屬性。我們將這些屬性存儲在項目的 application.yaml 文件中,並使用 @ConfigurationProperties 將值映射到一個記錄。

@ConfigurationProperties(prefix = "com.baeldung.semantic.cache")
record SemanticCacheProperties(
    Double similarityThreshold,
    String contentField,
    String embeddingField,
    String metadataField
) {}

這裏,相似度閾值決定了新查詢必須與緩存查詢在語義上有多相似(在0到1的範圍內)才能被認為是匹配項。

內容字段指定我們在向量存儲中存儲原始自然語言查詢的字段名稱。 嵌入字段存儲該自然語言查詢的向量表示,而 元數據字段存儲相應的LLM的答案。

現在,讓我們定義這些屬性的值在我們的 application.yaml 中:

com:
  baeldung:
    semantic:
      cache:
        similarity-threshold: 0.8
        content-field: question
        embedding-field: embedding
        metadata-field: answer

我們設置了一個相似度閾值為 0.8,確保僅當高度相似的查詢觸發緩存命中時

接下來,我們選擇的三個字段名稱清楚地表明瞭每個字段包含的數據。

在定義好我們的屬性後,讓我們創建與我們的向量存儲交互所需的 Bean。

@Configuration
@EnableConfigurationProperties(SemanticCacheProperties.class)
class LLMConfiguration {

    @Bean
    JedisPooled jedisPooled(RedisProperties redisProperties) {
        return new JedisPooled(redisProperties.getUrl());
    }

    @Bean
    RedisVectorStore vectorStore(
      JedisPooled jedisPooled,
      EmbeddingModel embeddingModel,
      SemanticCacheProperties semanticCacheProperties
    ) {
        return RedisVectorStore
          .builder(jedisPooled, embeddingModel)
          .contentFieldName(semanticCacheProperties.contentField())
          .embeddingFieldName(semanticCacheProperties.embeddingField())
          .metadataFields(
            RedisVectorStore.MetadataField.text(semanticCacheProperties.metadataField()))
          .build();
    }
}

首先,我們創建了一個 JedisPooled Bean,Spring AI 使用它來與 Redis 進行通信。我們使用在我們的 application.yaml 文件中配置的連接 URL,通過自動配置的 RedisProperties Bean 進行傳遞。

接下來,我們定義了我們的 RedisVectorStore Bean,並傳遞了 JedisPooled Bean 和自動配置的 EmbeddingModel Bean。 此外,我們使用我們的 semanticCacheProperties Bean 來定義自定義的字段名稱。 RedisVectorStore Bean 是我們將會在下一部分中使用,用於與向量存儲進行交互的核心類。

3. 實現語義緩存

有了我們配置好的方案,現在我們來構建負責保存和搜索語義緩存的服務。

3.1. 將 LLM 響應緩存到緩存中

讓我們首先創建一個方法來保存 LLM 響應:

@Service
@EnableConfigurationProperties(SemanticCacheProperties.class)
class SemanticCachingService {

    private final VectorStore vectorStore;
    private final SemanticCacheProperties semanticCacheProperties;

    // standard constructor

    void save(String question, String answer) {
        Document document = Document
          .builder()
          .text(question)
          .metadata(semanticCacheProperties.metadataField(), answer)
          .build();
        vectorStore.add(List.of(document));
    }
}

在這裏,在我們的 SemanticCachingService 類中,我們定義了一個 save() 方法,它接受自然語言 question(問題)及其對應的 answer(答案)作為輸入。

在我們的方法內部,我們創建一個 Document 對象,將 question 作為主要 text 內容,並將 answer 存儲在元數據中。

最後,我們使用自動注入的 vectorStore bean 的 add() 方法來保存 document該 bean 會自動為文檔的文本生成嵌入(embedding)——即 question ——並將其與 questionanswer 一起存儲在配置的語義緩存中。

3.2. 在緩存中執行語義搜索

現在,讓我們實現搜索功能以檢索緩存響應:

Optional<String> search(String question) {
    SearchRequest searchRequest = SearchRequest.builder()
      .query(question)
      .similarityThreshold(semanticCacheProperties.similarityThreshold())
      .topK(1)
      .build();
    List<Document> results = vectorStore.similaritySearch(searchRequest);

    if (results.isEmpty()) {
        return Optional.empty();
    }

    Document result = results.getFirst();
    return Optional
      .ofNullable(result.getMetadata().get(semanticCacheProperties.metadataField()))
      .map(String::valueOf);
}

在這裏,在我們的 search()方法中,我們首先構建一個 SearchRequest實例。我們將 question作為查詢,從我們的屬性中設置 similarityThreshold,並將 1傳遞給 topK()方法以僅檢索最佳匹配項。

然後,我們將我們的 searchRequest傳遞給 vectorStore bean 的 similaritySearch()方法。 再次強調,該 bean 在後台會自動為 input 問題生成嵌入,並搜索我們的語義緩存中與我們的閾值相匹配的最相似條目

如果未找到任何相似條目,我們只需返回一個空的 Optional

或者,如果找到匹配項,我們從 results 中的第一個 Document 中提取 answer,並將其包裝在 Optional 中返回。

4. 測試我們的實現

最後,讓我們編寫一個簡單的測試,以驗證我們的語義緩存實現是否正確工作:

String question = "How many sick leaves can I take?";
String answer = "No leaves allowed! Get back to work!!";
semanticCachingService.save(question, answer);

String rephrasedQuestion = "How many days sick leave can I take?";
assertThat(semanticCachingService.search(rephrasedQuestion))
    .isPresent()
    .hasValue(answer);

String unrelatedQuestion = "Can I get a raise?";
assertThat(semanticCachingService.search(unrelatedQuestion))
    .isEmpty();

首先,我們使用我們的 語義緩存服務 (semanticCachingService) 將原始問題和答案對存儲到向量存儲中。

然後,我們使用原始問題的重新表述版本進行搜索。儘管措辭不同,我們的服務能夠識別相似性並返回緩存的答案 (answer)。

最後,我們驗證一個語義完全不同的無關問題導致緩存未命中 (cache miss)。

5. 結論

在本文中,我們探討了使用 Spring AI 實現語義緩存的方法。

我們配置了 OpenAI 的嵌入模型,將文本轉換為向量表示,並使用 Redis 作為向量存儲來存儲和搜索這些嵌入。然後,我們構建並測試了一個緩存服務,用於保存 LLM 的響應併為語義相似的查詢檢索它們,從而降低成本和延遲。

為了演示,我們保持了簡單性。您可以在這裏找到一個更高級的示例,該示例在 Retrieval-Augmented Generation (RAG) 聊天機器人之上構建語義緩存:這裏

如往常一樣,本文中使用的所有代碼示例都可以在 GitHub 上找到:這裏

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.