Spring AI中使用嵌入模型和向量資料庫實現RAG應用

banq發表於2024-03-17


在本文中,我們將探討以下內容:

  • 嵌入模型簡介。
  • 使用 DocumentReaders 載入資料。
  • 將嵌入儲存在 VectorStore 中。
  • 實施 RAG(檢索增強生成),又名提示填充。

您可以在GitHub 儲存庫中找到本文的示例程式碼

OpenAI、Azure Open AI、Google Vertex 等大型語言模型 (LLM) 都是在大型資料集上進行訓練的。但這些模型並未針對您的私人資料進行訓練,因此它們可能無法回答特定於您的領域的問題。但根據您的私人資料訓練模型可能既昂貴又耗時。那麼,我們如何利用這些法學碩士來回答特定於我們領域的問題呢?

實現此目的的一種方法是使用 RAG(檢索增強生成),也稱為提示填充。使用 RAG,我們將從資料儲存中檢索相關文件並將其傳遞給法學碩士以生成答案。在此過程中,我們將使用嵌入模型將文件轉換為嵌入並將其儲存在向量資料庫中。

瞭解檢索增強生成 (RAG)
您的企業可能將結構化資料儲存在關聯式資料庫中,將非結構化資料儲存在 NoSQL 資料庫中,甚至儲存在檔案中。您將能夠使用 SQL 和 NoSQL 資料庫的查詢語言有效地查詢關聯式資料庫。您還可以使用Elasticsearch、Solr等全文搜尋引擎來查詢非結構化資料。

但是,您可能希望使用具有語義意義的自然語言來檢索資料。

例如,“我喜歡 Java 程式語言”和“Java 始終是我的首選語言”具有相同的語義,但使用不同的詞語。嘗試使用確切的單詞檢索資料可能不會有效。

這就是嵌入發揮作用的地方。嵌入是單詞、句子或文件的向量表示。您可以使用這些嵌入來使用自然語言檢索資料。

您可以將結構化和非結構化資料轉換為嵌入並將它們儲存在向量資料庫中。然後,您可以使用自然語言查詢向量資料庫並檢索相關資料。然後,您可以查詢傳遞相關資料的 AI 模型以獲得響應。

檢索增強生成(RAG)是最佳化法學碩士輸出的過程,在生成響應之前,除了訓練資料之外,還使用額外的知識庫。

嵌入API
Embedding API 可以將單詞、句子、文件或影像轉換為嵌入。嵌入是單詞、句子或文件的向量表示。

例如,單詞“Apple”可以表示為向量 [0.1, 0.2, 0.3, 0.4, 0.5]。句子“我愛蘋果”可以表示為向量 [0.1, 10.3, -10.2, 90.3, 2.4, -0.5]。

Spring AI 提供了EmbeddingClient介面,用於將文字或文件轉換為嵌入。您可以使用任何受支援的EmbeddingClient實現,例如OpenAiEmbeddingClient、OllamaEmbeddingClient、 AzureOpenAiEmbeddingClient、VertexAiEmbeddingClient等。

根據您要使用的實現,您可以新增相應的依賴項並在application.properties檔案中配置屬性。

例如,如果您想使用 OpenAI 的 EmbeddingClient,您可以將以下依賴項新增到pom.xml檔案中。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>0.0.8</version>
</dependency>


在application.properties檔案中配置屬性。

spring.ai.openai.api-key=${OPENAI_API_KEY}

# You can override the above common api-key for embedding using the following property
spring.ai.openai.embedding.api-key=${OPENAI_API_KEY}

透過上述配置,您可以注入EmbeddingClient並將文字或文件轉換為嵌入,如下所示:

@Component
class MyComponent {
    private final EmbeddingClient embeddingClient;
    
    public MyComponent(EmbeddingClient embeddingClient) {
        this.embeddingClient = embeddingClient;
    }
    
    public void convertTextToEmbedding() {
        <font>//Example 1: Convert text to embeddings<i>
        List<Double> embeddings1 = embeddingClient.embed(
"I like Spring Boot");
        
       
//Example 2: Convert document to embeddings<i>
        List<Double> embeddings2 = embeddingClient.embed(new Document(
"I like Spring Boot"));
        
       
//Example 3: Convert text to embeddings using options<i>
        EmbeddingRequest embeddingRequest =
                new EmbeddingRequest(List.of(
"I like Spring Boot"),
                        OpenAiEmbeddingOptions.builder()
                                .withModel(
"text-davinci-003")
                                .build());
        EmbeddingResponse embeddingResponse = embeddingClient.call(embeddingRequest);
        List<Double> embeddings3 = embeddingResponse.getResult().getOutput();
    }
}

向量資料庫
向量資料庫是儲存嵌入的資料庫。您可以將單詞、句子或文件的嵌入儲存在向量資料庫中。您可以使用向量資料庫來使用自然語言查詢嵌入並檢索相關資料。

Spring AI 提供了一個VectorStore介面來儲存和檢索嵌入。目前,Spring AI 提供了VectorStore實現,例如SimpleVectorStore、 ChromaVectorStore、Neo4jVectorStore、PgVectorStore、RedisVectorStore等。

讓我們看看如何使用SimpleVectorStore來儲存和檢索嵌入。

@Configuration
class AppConfig {
    @Bean
    VectorStore vectorStore(EmbeddingClient embeddingClient) {
        return new SimpleVectorStore(embeddingClient);
    }
}

@Component
class MyComponent {
    private final VectorStore vectorStore;
    
    public MyComponent(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }
    
    public void storeAndRetrieveEmbeddings() {
        <font>// Store embeddings<i>
        List<Document> documents = 
                List.of(new Document(
"I like Spring Boot"),
                        new Document(
"I love Java programming language"));
        vectorStore.add(documents);
        
       
// Retrieve embeddings<i>
        SearchRequest query = SearchRequest.query(
"Spring Boot").withTopK(2);
        List<Document> similarDocuments = vectorStore.similaritySearch(query);
        String relevantData = similarDocuments.stream()
                            .map(Document::getContent)
                            .collect(Collectors.joining(System.lineSeparator()));
    }
}

在上面的程式碼中,我們將文件新增到 VectorStore,VectorStore 在內部使用 EmbeddingClient 將文件轉換為嵌入,並將它們儲存在向量資料庫中。

然後,我們使用自然語言查詢 VectorStore 並檢索相關資料。我們使用withTopK()方法指定了要返回的相似文件的最大數量。

文件閱讀器和文件編寫器
在上面的示例中,我們直接從 String 構造了一個Document例項來表示文字或文件。但在實際應用程式中,您可能希望從檔案、資料庫或任何其他來源讀取文件。

Spring AI提供了DocumentReader和DocumentWriter介面來從不同的源讀取和寫入文件。

截至目前,Spring AI 提供了DocumentReader實現,例如JsonReader、 TextReader、PagePdfDocumentReader等。

VectorStore介面擴充套件了DocumentWriter介面,因此您可以使用任何VectorStore實現作為DocumentWriter。

讓我們看看如何使用TextReader讀取文字文件並將其儲存在 VectorStore 中。

@Component
class MyComponent {
    private final VectorStore vectorStore;
    
    @Value(<font>"classpath:myfile.txt")
    private Resource resource;
    
    public MyComponent(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }
    
    public void storeEmbeddingsFromTextFile() {
        var textReader = new TextReader(resource);
        textReader.setCharset(Charset.defaultCharset());
        List<Document> documents = textReader.get();

        vectorStore.add(documents);
    }
}

在上面的示例中,我們從類路徑檔案中讀取文字並將其儲存在 VectorStore 中。

實施 RAG(檢索增強生成)
現在我們已經瞭解瞭如何將文件轉換為嵌入並將其儲存在向量資料庫中,以及如何使用自然語言檢索相關文件,讓我們看看如何實現 RAG。

@RestController
class RAGController {
    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    RAGController(ChatClient chatClient, VectorStore vectorStore) {
        this.chatClient = chatClient;
        this.vectorStore = vectorStore;
    }
    
    <font>// 假設我們已經從包含人員資訊的檔案中讀取了文件 <i>
   
// 並按照上一節的描述將其儲存在 VectorStore 中。<i>
    
    @GetMapping(
"/ai/rag/people")
    Person chatWithRag(@RequestParam String name) {
       
// 使用自然語言查詢 VectorStore,查詢有關個人的資訊。<i>
        List<Document> similarDocuments = 
                vectorStore.similaritySearch(SearchRequest.query(name).withTopK(2));
        String information = similarDocuments.stream()
                .map(Document::getContent)
                .collect(Collectors.joining(System.lineSeparator()));
        
       
//構建 systemMessage 以指示人工智慧模型使用傳遞的資訊<i>
       
// 回答問題。<i>
        var systemPromptTemplate = new SystemPromptTemplate(
"""
              You are a helpful assistant.
              
              Use the following information to answer the question:
              {information}
             
""");
        var systemMessage = systemPromptTemplate.createMessage(
                Map.of(
"information", information));

       
// 使用 BeanOutputParser 將響應解析為 Person 的例項。<i>
        var outputParser = new BeanOutputParser<>(Person.class);
        
       
// 構建使用者資訊(userMessage),要求人工智慧模型介紹這個人。<i>
        PromptTemplate userMessagePromptTemplate = new PromptTemplate(
"""
        Tell me about {name} as if current date is {current_date}.

        {format}
       
""");
        Map<String,Object> model = Map.of(
"name", name,
               
"current_date", LocalDate.now(),
               
"format", outputParser.getFormat());
        var userMessage = new UserMessage(userMessagePromptTemplate.create(model).getContents());

        var prompt = new Prompt(List.of(systemMessage, userMessage));

        var response = chatClient.call(prompt).getResult().getOutput().getContent();

        return outputParser.parse(response);
    }
}

record Person(String name,
              String dateOfBirth,
              int experienceInYears,
              List<String> books) {
}

上述程式碼的解釋已包含在註釋中。

總體而言,RAG 過程涉及以下步驟:

  • 使用DocumentReaders從不同來源載入文件。
  • 使用EmbeddingClient將文件轉換為嵌入並將其儲存在VectorStore中。
  • 使用自然語言查詢VectorStore並檢索相關文件。
  • 構造SystemMessage以指示 AI 模型使用傳遞的資訊來回答問題。
  • 構造UserMessage向 AI 模型詢問資訊。
  • 構造提示並呼叫 AI 模型以獲得響應。
  • 使用OutputParsers將響應解析為所需的格式。
  • 返回響應。

結論
在本文中,我們瞭解瞭如何使用 Embedding API 將文字或文件轉換為嵌入。我們還了解了如何使用向量資料庫來儲存和檢索嵌入。我們實施了 RAG(檢索增強生成),利用檢索到的資訊來回答使用 AI 模型的問題。
 

相關文章