練習 - 使用 Spring AI 和 Azure OpenAI 開發 RAG 應用程式

已完成

在此單元中,您會使用 Spring AI、Azure OpenAI 及 VectorStore 建置檢索增強生成 (RAG) 應用程式。

設定環境變數

在此練習中,您需要先前練習中的一些環境變數。 如果您使用相同的Bash視窗,這些變數仍應存在。 如果變數已無法使用,請使用下列命令來重新建立它們。 請務必將 <...> 佔位元取代為您自己的值,並使用您先前使用的相同值。

export RESOURCE_GROUP=<resource-group>
export LOCATION=<location>
export DB_SERVER_NAME=<server-name>
export PGHOST=$(az postgres flexible-server show \
    --resource-group $RESOURCE_GROUP \
    --name $DB_SERVER_NAME \
    --query fullyQualifiedDomainName \
    --output tsv \
    | tr -d '\r')

您也需要此單元的新環境變數。 使用下列命令來定義此變數:

export OPENAI_RESOURCE_NAME=OpenAISpringAI

部署 Azure OpenAI 模型

針對您的應用程式,您必須先部署一個聊天模型 - gpt-4o 和一個內嵌模型 - text-embedding-ada-002。 若要部署這些模型,您必須先建立 Azure OpenAI 資源。

建立 Azure OpenAI 帳戶

使用下列命令來建立 Azure OpenAI 帳戶:

az cognitiveservices account create \
    --resource-group $RESOURCE_GROUP \
    --name $OPENAI_RESOURCE_NAME \
    --kind OpenAI \
    --sku S0 \
    --location $LOCATION \
    --yes

部署 Azure OpenAI 聊天模型

使用下列命令來部署名為 gpt-4o的聊天模型:

az cognitiveservices account deployment create \
    --resource-group $RESOURCE_GROUP \
    --name $OPENAI_RESOURCE_NAME \
    --deployment-name gpt-4o \
    --model-name gpt-4o \
    --model-version 2024-11-20 \
    --model-format OpenAI \
    --sku-capacity "15" \
    --sku-name GlobalStandard

部署 Azure OpenAI 內嵌模型

您現在可以使用下列命令來部署名為 text-embedding-ada-002 的內嵌模型:

az cognitiveservices account deployment create \
    --resource-group $RESOURCE_GROUP \
    --name $OPENAI_RESOURCE_NAME \
    --deployment-name text-embedding-ada-002 \
    --model-name text-embedding-ada-002 \
    --model-version 2 \
    --model-format OpenAI \
    --sku-capacity 120 \
    --sku-name Standard

建立 Spring AI 應用程式

使用下列 curl 命令來產生具有所有所需相依性的新 Spring Boot 入門專案:

curl https://start.spring.io/starter.zip \
    -d groupId=com.example \
    -d artifactId=spring-ai-app \
    -d name=spring-ai-app \
    -d description="Spring AI Azure Integration" \
    -d version=0.0.1-SNAPSHOT \
    -d bootVersion=3.4.3 \
    -d javaVersion=17 \
    -d dependencies=web,jdbc,azure-support,spring-ai-azure-openai,spring-ai-vectordb-pgvector \
    -d type=maven-project \
    -d packageName=com.example.springaiapp \
    --output spring-ai-app.zip

產生的 Spring Boot 入門專案包含下列組態和相依性:

  • Spring Boot 版本:3.4.3
  • Java 版本:17
  • 依賴:
    • web:新增建置 Web 應用程式的支援,包括使用 Spring Model View Controller (MVC) 的 RESTful 服務。
    • jdbc:提供數據庫存取的 Java 資料庫連線能力 (JDBC) 支援。
    • azure-support:新增與 Azure 服務整合的支援。
    • spring-ai-azure-openai:新增與 Azure OpenAI 服務整合的支援。
    • spring-ai-vectordb-pgvector:新增對使用 pgvector的支援,這是適用於向量內嵌的 PostgreSQL 延伸模組。

使用下列命令將下載的檔案解壓縮並瀏覽至已建立的目錄:

unzip -u spring-ai-app.zip -d spring-ai-app
cd spring-ai-app

接下來,您必須變更 pom.xml 檔案,以包含 PostgreSQL 無密碼驗證的相依性。 開啟 pom.xml 檔案,找出 <dependencies> 區段,然後新增下列相依性:

<dependency>
  <groupId>com.azure.spring</groupId>
  <artifactId>spring-cloud-azure-starter-jdbc-postgresql</artifactId>
</dependency>

然後,使用下列命令來編譯應用程式,略過測試:

mvn clean package -DskipTests

輸出應該包含類似下列範例的成功訊息:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  25.392 s
[INFO] Finished at: 2025-03-02T15:53:07-05:00
[INFO] ------------------------------------------------------------------------

專案結構

spring-ai-app 目錄,使用下列命令為要新增的新原始程式檔建立新目錄:

mkdir -p src/main/java/com/example/springaiapp/controller
mkdir -p src/main/java/com/example/springaiapp/service

使用 Visual Studio Code 或您慣用的 IDE 檢查程式代碼。 入門程式代碼包含下列結構:

src/
├── main/
│   ├── java/
│   │   └── com/example/springaiapp/
│   │       ├── controller/
│   │       ├── service/
│   │       └── SpringAiAppApplication.java
│   └── resources/
│       └── application.properties
├── test/
│   └── java/
│       └── com/example/springaiapp/
│           └── SpringAiAppApplicationTests.java
└── pom.xml

設定 Spring AI

您必須先新增下列必要組態,才能成功執行應用程式:

  • Azure OpenAI 端點。
  • Azure OpenAI API 金鑰。
  • PostgreSQL URL。

使用下列命令擷取 Azure OpenAI 端點:

export AZURE_OPENAI_ENDPOINT=$(az cognitiveservices account show \
    --resource-group $RESOURCE_GROUP \
    --name $OPENAI_RESOURCE_NAME \
    --query "properties.endpoint" \
    --output tsv \
    | tr -d '\r')

使用下列命令擷取 Azure OpenAI API 金鑰:

export AZURE_OPENAI_API_KEY=$(az cognitiveservices account keys list \
    --resource-group $RESOURCE_GROUP \
    --name $OPENAI_RESOURCE_NAME \
    --query "key1" \
    --output tsv \
    | tr -d '\r')

使用下列命令擷取 PostgreSQL URL:

az postgres flexible-server show \
    --resource-group $RESOURCE_GROUP \
    --name $DB_SERVER_NAME \
    --query fullyQualifiedDomainName \
    --output tsv

更新 application.properties 檔案

src/main/resources 目錄中找出並開啟 application.properties 檔案。 有三個屬性是使用下列環境變數中的值初始化: AZURE_OPENAI_API_KEYAZURE_OPENAI_ENDPOINTPGHOST。 以下列內容取代檔案的內容:

spring.application.name=spring-ai-app
spring.ai.azure.openai.api-key=${AZURE_OPENAI_API_KEY}
spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT}
spring.ai.azure.openai.chat.model=gpt-4o
spring.ai.azure.openai.embedding.model=text-embedding-ada-002

spring.datasource.url=jdbc:postgresql://${PGHOST}/postgres?sslmode=require
spring.datasource.username=azureuser
spring.datasource.azure.passwordless-enabled=true
spring.ai.vectorstore.pgvector.initialize-schema=true
spring.ai.vectorstore.pgvector.schema-name=postgres

實作 RAG 服務

建立服務

服務 目錄中,建立名為 RagService.java 的新檔案,並新增下列程式代碼:

package com.example.springaiapp.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.ai.document.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RagService {
    private static final Logger logger = LoggerFactory.getLogger(RagService.class);

    private final ChatClient chatClient;

    @Autowired
    VectorStore vectorStore;

    public RagService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    public String processQuery(String query) {
        List<Document> similarContexts = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .similarityThreshold(0.8)
                .topK(3)
                .build()
        );
        String context = similarContexts.stream()
                .map(ch -> String.format("Q: %s\nA: %s", ch.getMetadata().get("prompt"), ch.getText()))
                .collect(Collectors.joining("\n\n"));
        logger.debug("Found {} similar contexts", similarContexts.size());
        String promptText = String.format("""
            Use these previous Q&A pairs as context for answering the new question:

            Previous interactions:
            %s

            New question: %s

            Please provide a clear and educational response.""",
            context,
            query
        );
        return this.chatClient.prompt()
            .user(promptText)
            .call()
            .content();
    }
}

此程式碼透過生成給定查詢的答案來實現RAG,並利用從VectorStore中獲取的類似上下文來增強模型的知識。

建立RAG控制器

接下來,您必須公開RAG應用程式的REST端點。 在控制器目錄中建立名為 RagController.java 的新檔案,然後新增下列程式代碼:

package com.example.springaiapp.controller;

import com.example.springaiapp.service.RagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/rag")
public class RagController {

    @Autowired
    private RagService ragService;

    @GetMapping
    public String processQuery(@RequestParam String query) {
        return ragService.processQuery(query);
    }
}

測試RAG應用程式

在這些變更就緒后,請使用下列命令來編譯並執行程序代碼:

mvn spring-boot:run

從瀏覽器或使用下列命令測試新的 REST 端點:

curl -G "http://localhost:8080/api/rag" \
    --data-urlencode "query=What is pgvector?"

您應該會看到類似下列範例的有效回應:

pgvector is an open-source PostgreSQL extension that enables efficient storage, indexing,
and querying of vector embeddings within a PostgreSQL database.

接下來,請嘗試下列命令:

curl -G "http://localhost:8080/api/rag" \
    --data-urlencode "query=How does QuestionAnswerAdvisor work in Spring AI?"

儘管答案看起來可能有效,但其措辭可能顯示這是一個有理所據的猜測。

使用額外的知識測試應用程式

接下來,藉由將檔新增至向量存放區來提供額外的知識。 在服務目錄中建立名為 DocumentService.java 的新檔案,然後新增下列程式代碼:

package com.example.springaiapp.service;

import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;

@Service
public class DocumentService {

    private static final Logger logger = LoggerFactory.getLogger(DocumentService.class);

    @Autowired
    VectorStore vectorStore;

    @PostConstruct
    private void init() {
        vectorStore.add(documents);
        logger.info("DocumentService initialized with. Document count: {}", documents.size());
    }

    List<Document> documents = List.of(
        new Document("3e1a1af7-c872-4e36-9faa-fe53b9613c69",
                    """
                    The Spring AI project aims to streamline the development of applications that
                    incorporate artificial intelligence functionality without unnecessary complexity.
                    The project draws inspiration from notable Python projects, such as LangChain
                    and LlamaIndex, but Spring AI is not a direct port of those projects.
                    The project was founded with the belief that the next wave of Generative AI
                    applications will not be only for Python developers but will be ubiquitous
                    across many programming languages.
                    """,
                     Map.of("prompt", "What is Spring AI?")),
        new Document("7a7c2caf-ce9c-4dcb-a543-937b76ef1098",
                    """
                    A vector database stores data that the AI model is unaware of. When a user
                    question is sent to the AI model, a QuestionAnswerAdvisor queries the vector
                    database for documents related to the user question.
                    The response from the vector database is appended to the user text to provide
                    context for the AI model to generate a response. Assuming you have already
                    loaded data into a VectorStore, you can perform Retrieval Augmented Generation
                    (RAG) by providing an instance of QuestionAnswerAdvisor to the ChatClient.
                    """,
                     Map.of("prompt", "How does QuestionAnswer Advisor work?"))
        );
}

若要測試這些變更,請使用下列命令來編譯和執行程序代碼:

mvn spring-boot:run

然後,使用下列命令提出問題:

curl -G "http://localhost:8080/api/rag" \
    --data-urlencode "query=How does QuestionAnswerAdvisor work in Spring AI?"

您現在應該會看到一個明確解釋 QuestionAnswerAdvisor 在 Spring AI 中角色的答案。

單元摘要

在此單元中,您已使用 Spring AI、Azure OpenAI 和 Spring AI 的 VectorStore成功建置 RAG 應用程式。 此模組會透過 RagController 類別透過專用 REST 端點公開 RAG 功能。