將 Oracle 向量資料庫與 Spring AI 結合使用
1.概述
在傳統資料庫中,我們通常依賴精確的關鍵字或基本模式匹配來實現搜尋功能。雖然這種方法對於簡單的應用來說已經足夠,但它無法完全理解自然語言查詢背後的含義和上下文。
向量儲存透過將資料儲存為能夠捕捉其含義的數字向量來解決此限制。相似的詞語被聚集在一起,從而實現相似性搜尋。即使結果不包含查詢中使用的確切關鍵字,資料庫也會傳回相關的結果。
Oracle Database 23ai將此向量儲存功能整合到其現有生態系統中,使我們能夠建立 AI 應用程序,而無需單獨的向量儲存。使用同一個資料庫,我們可以創建同時使用傳統結構化資料管理和向量相似性搜尋的解決方案。
在本教程中,我們將探索如何將 Oracle 向量資料庫與 Spring AI 整合。我們將實現原生相似性搜索,以查找語義相關的內容。然後,我們將在此基礎上實作一個檢索增強生成 (RAG) 聊天機器人。
2. 設定項目
在深入實施之前,我們需要包含必要的依賴項並正確配置我們的應用程式。
2.1. 依賴項
讓我們先為專案的pom.xml
檔案加入必要的依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-oracle</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0</version>
</dependency>
Oracle 向量儲存啟動器相依性使我們能夠與 Oracle 向量資料庫建立連線並進行互動。此外,我們也為 RAG 實作匯入了向量儲存顧問相依性。
最後,我們匯入 Spring AI 的OpenAI 啟動依賴項,我們將使用它與聊天完成和嵌入模型進行互動。
鑑於我們在專案中使用了多個 Spring AI 啟動器,我們還需要在pom.xml
中包含Spring AI 物料清單 (BOM) :
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
透過此添加,我們現在可以從啟動器依賴項中刪除version
標籤。 BOM 消除了版本衝突的風險,並確保 Spring AI 依賴項彼此相容。
2.2. 配置 AI 模型和向量儲存屬性
為了將文字資料轉換為 Oracle 向量資料庫可以儲存和搜尋的向量,我們需要一個嵌入模型。此外,對於我們的 RAG 聊天機器人,我們還需要一個聊天補全模型。
為了演示,我們將使用 OpenAI 提供的模型。讓我們在application.yaml
檔案中設定OpenAI API 金鑰和模型:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
embedding:
options:
model: text-embedding-3-large
chat:
options:
model: gpt-4o
我們使用${}
屬性佔位符從環境變數載入 API 金鑰的值。
此外,我們分別指定text-embedding-3-large
和gpt-4o
作為嵌入和聊天補全模型。配置這些屬性時, Spring AI 會自動建立一個ChatModel
類型的 bean ,我們將在本教學的後面部分使用它。
或者,我們可以使用不同的模型,因為特定的 AI 模型或提供者與此演示無關。
接下來,為了在我們的向量資料庫中儲存和搜尋數據,我們必須先初始化它的模式:
spring:
ai:
vectorstore:
oracle:
initialize-schema: true
在這裡,我們將spring.ai.vectorstore.oracle.initialize-schema
設定為true
。
這指示 Spring AI 在應用程式啟動時自動建立必要的預設向量儲存模式,這方便本地開發和測試。但是,對於生產應用程序,我們應該使用資料庫遷移工具(例如 Flyway)手動定義模式。
3. 填充 Oracle 向量資料庫
配置完成後,讓我們設定一個工作流程,在應用程式啟動期間用一些範例資料填充我們的 Oracle 向量資料庫。
3.1. 從外部 API 取得Quote
記錄
為了演示,我們將使用《絕命毒師》語錄 API來取得語錄。
讓我們為此創建一個QuoteFetcher
實用程式類別:
class QuoteFetcher {
private static final String BASE_URL = "https://api.breakingbadquotes.xyz/v1/quotes/";
private static final int DEFAULT_COUNT = 150;
static List<Quote> fetch() {
return fetch(DEFAULT_COUNT);
}
static List<Quote> fetch(int count) {
return RestClient
.create()
.get()
.uri(URI.create(BASE_URL + count))
.retrieve()
.body(new ParameterizedTypeReference<>() {});
}
}
record Quote(String quote, String author) {
}
使用RestClient
,我們呼叫預設計數為150
的外部 API,並使用ParameterizedTypeReference
將 API 回應反序列化為Quote
記錄清單。
3.2. 將Documents
儲存在向量資料庫中
現在,為了在應用程式啟動期間用報價填充我們的 Oracle 向量資料庫,我們將建立一個實作ApplicationRunner
介面的VectorStoreInitializer
類別:
@Component
class VectorStoreInitializer implements ApplicationRunner {
private final VectorStore vectorStore;
// standard constructor
@Override
public void run(ApplicationArguments args) {
List<Document> documents = QuoteFetcher
.fetch()
.stream()
.map(quote -> {
Map<String, Object> metadata = Map.of("author", quote.author());
return new Document(quote.quote(), metadata);
})
.toList();
vectorStore.add(documents);
}
}
在我們的VectorStoreInitializer
類別中,我們自動組裝VectorStore
的實例,Spring AI 會自動為我們建立該實例。
在run()
方法中,我們使用QuoteFetcher
工具類別來檢索Quote
記錄清單。然後,我們將每個quote
映射到Document
中,並將author
欄位配置為metadata
。
最後,我們將所有documents
儲存在資料庫中。當我們呼叫add()
方法時,Spring AI 會自動將純文字內容轉換為向量表示,然後再儲存到資料庫中。
4. 使用 Testcontainers 設定本機測試環境
為了方便本地開發和測試,我們將使用Testcontainers來建立Oracle向量資料庫,其前提條件是有一個活動的Docker實例。
首先,讓我們在pom.xml
中加入必要的測試依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-free</artifactId>
<scope>test</scope>
</dependency>
我們導入 Spring Boot 的Spring AI Testcontainers 依賴項和 Testcontainers 的Oracle Database 模組。
這些相依性提供了為 Oracle 向量資料庫啟動臨時 Docker 執行個體所需的類別。
接下來,讓我們建立一個@TestConfiguration
類別來定義我們的Testcontainers bean:
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
@Bean
@ServiceConnection
OracleContainer oracleContainer() {
return new OracleContainer("gvenzl/oracle-free:23-slim");
}
}
我們在建立OracleContainer
bean 時指定 Oracle 資料庫映像的最新穩定精簡版本。
另外,我們用@ServiceConnection
來註解我們的 bean 方法。這將動態註冊與 Docker 容器建立連線所需的所有資料來源屬性。
現在,我們可以透過使用@Import(TestcontainersConfiguration.class)
註解來註解我們的測試類,從而在整合測試中使用此配置。
5. 執行相似性搜索
現在我們已經設定了本地測試環境,並用《絕命毒師》的台詞填充了我們的 Oracle 向量資料庫,讓我們探索如何執行相似性搜尋。
5.1. 基本相似性搜索
讓我們先執行基本的相似性搜尋操作來尋找與《絕命毒師》各種主題相符的引言:
private static final int MAX_RESULTS = 5;
@Autowired
private VectorStore vectorStore;
@ParameterizedTest
@ValueSource(strings = { "Sarcasm", "Regret", "Violence and Threats", "Greed, Power, and Money" })
void whenSearchingBreakingBadTheme_thenRelevantQuotesReturned(String theme) {
SearchRequest searchRequest = SearchRequest
.builder()
.query(theme)
.topK(MAX_RESULTS)
.build();
List<Document> documents = vectorStore.similaritySearch(searchRequest);
assertThat(documents)
.hasSizeGreaterThan(0)
.hasSizeLessThanOrEqualTo(MAX_RESULTS)
.allSatisfy(document -> {
assertThat(document.getText())
.isNotBlank();
assertThat(String.valueOf(document.getMetadata().get("author")))
.isNotBlank();
});
}
在這裡,我們使用@ValueSource
將《絕命毒師》系列的主要主題傳遞給測試方法。然後,我們建立一個SearchRequest
對象,並以此theme
作為查詢。此外,我們將MAX_RESULTS
傳遞給topK()
方法,將結果限制為最相似的前五句台詞。
接下來,我們使用searchRequest
呼叫vectorStore
bean的similaritySearch()
方法。與VectorStore
的add()
方法類似,Spring AI 在查詢資料庫之前會將查詢轉換為其向量表示形式。
傳回的文件將包含與給定主題在語義上相關的引文,即使它們不包含精確的關鍵字。
5.2. 使用元資料進行過濾
除了執行基本的相似性搜尋之外, Oracle 向量資料庫還支援根據已儲存的元資料過濾搜尋結果。當我們需要縮小搜尋範圍並在資料子集內執行語義搜尋時,這非常有用。
讓我們再次搜尋與給定主題相關的引文,但按特定作者進行過濾:
@ParameterizedTest
@CsvSource({
"Walter White, Pride",
"Walter White, Control",
"Jesse Pinkman, Abuse and foul language",
"Mike Ehrmantraut, Wisdom",
"Saul Goodman, Law"
})
void whenSearchingCharacterTheme_thenRelevantQuotesReturned(String author, String theme) {
SearchRequest searchRequest = SearchRequest
.builder()
.query(theme)
.topK(MAX_RESULTS)
.filterExpression(String.format("author == '%s'", author))
.build();
List<Document> documents = vectorStore.similaritySearch(searchRequest);
assertThat(documents)
.hasSizeGreaterThan(0)
.hasSizeLessThanOrEqualTo(MAX_RESULTS)
.allSatisfy(document -> {
assertThat(document.getText())
.isNotBlank();
assertThat(String.valueOf(document.getMetadata().get("author")))
.contains(author);
});
}
在這裡,我們使用@CsvSource
註釋來尋找使用各種字元主題組合的引號。
我們像以前一樣建立SearchRequest
,但這次,我們使用filterExpression()
方法將結果限制為來自特定作者的引言。
6. 建構 RAG 聊天機器人
雖然原生相似性搜尋本身就很強大,但我們可以在此基礎上創建一個智慧的、可感知上下文的 RAG 聊天機器人。
6.1. 定義提示模板
為了更好地指導 LLM 的行為,我們將定義一個自訂提示範本。讓我們在src/main/resources
目錄中建立一個新的prompt-template.st
檔:
`You are a chatbot built for analyzing quotes from the 'Breaking Bad' television series.
Given the quotes in the CONTEXT section, answer the query in the USER_QUESTION section.
The response should follow the guidelines listed in the GUIDELINES section.
CONTEXT:
USER_QUESTION:
GUIDELINES:
Base your answer solely on the information found in the provided quotes.
Provide concise, direct answers without mentioning "based on the context" or similar phrases.
When referencing specific quotes, mention the character who said them.
If the question cannot be answered using the context, respond with "The provided quotes do not contain information to answer this question."
If the question is unrelated to the Breaking Bad show or the quotes provided, respond with "This question is outside the scope of the available Breaking Bad quotes."`
在這裡,我們明確定義了聊天機器人的角色並為其提供了一套可遵循的指導方針。
在我們的模板中,我們使用了兩個用尖括號括起來的佔位符。 Spring AI 會自動將question_answer_context
和query
佔位符分別替換為從向量資料庫檢索到的上下文和使用者的問題。
6.2. 配置ChatClient
Bean
接下來,讓我們定義一個ChatClient
類型的 bean,它作為與配置的聊天完成模型互動的主要入口點:
private static final int MAX_RESULTS = 10;
@Bean
PromptTemplate promptTemplate(
@Value("classpath:system-prompt.st") Resource promptTemplate) {
String template = promptTemplate.getContentAsString(StandardCharsets.UTF_8);
return PromptTemplate
.builder()
.renderer(StTemplateRenderer
.builder()
.startDelimiterToken('<')
.endDelimiterToken('>')
.build())
.template(template)
.build();
}
@Bean
ChatClient chatClient(
ChatModel chatModel,
VectorStore vectorStore,
PromptTemplate promptTemplate) {
return ChatClient
.builder(chatModel)
.defaultAdvisors(
QuestionAnswerAdvisor
.builder(vectorStore)
.promptTemplate(promptTemplate)
.searchRequest(SearchRequest
.builder()
.topK(MAX_RESULTS)
.build())
.build()
)
.build();
}
這裡,我們首先使用@Value
註解檢索提示範本的內容,並使用它定義一個PromptTemplate
bean。我們也將其配置為使用尖括號作為分隔符號。
接下來,我們使用PromptTemplate
bean 以及ChatModel
和VectorStore
bean 來定義ChatClient
bean。我們使用defaultAdvisors()
方法註冊一個QuestionAnswerAdvisor
,它是實作 RAG 模式的元件。
此外,在顧問程式中,我們設定了一個SearchRequest
來檢索前 10 個最相關的引語。 Spring AI 會在呼叫 LLM 之前將它們注入到提示範本中。
6.3. 執行 RAG 操作
現在,配置好ChatClient
bean 後,讓我們看看如何與它互動以詢問自然語言問題:
@Autowired
private ChatClient chatClient;
@ParameterizedTest
@ValueSource(strings = {
"How does the show portray the mentor-student dynamic?",
"Which characters in the show portray insecurity through their quotes?",
"Does the show contain quotes with mature themes inappropriate for young viewers?"
})
void whenQuestionsRelatedToBreakingBadAsked_thenRelevantAnswerReturned(String userQuery) {
String response = chatClient
.prompt(userQuery)
.call()
.content();
assertThat(response)
.isNotBlank();
.doesNotContain(OUT_OF_SCOPE_MESSAGE, NO_INFORMATION_MESSAGE);
}
在這裡,當我們將userQuery
傳遞給prompt()
方法時,我們配置的QuestionAnswerAdvisor
會在背景執行 RAG 工作流程。此顧問會查詢 Oracle 向量資料庫,尋找與使用者問題相關的引語,將其註入提示模板,並將組合後的提示傳送到配置的 LLM 以獲得回應。
我們驗證回應不是空白的,並且不包含我們在範本中定義的後備訊息。
7. 結論
在本文中,我們探討如何將 Oracle 向量資料庫與 Spring AI 整合。
我們完成了必要的配置,並實現了兩個關鍵的向量儲存功能:相似性搜尋和 RAG。我們使用 Testcontainers 搭建了 Oracle 向量資料庫,創建了一個本地測試環境。
首先,我們在應用程式啟動時從《絕命毒師》語錄 API 取得語錄,填入向量儲存。然後,我們對儲存的資料進行了相似性搜索,以獲取與劇集中常見主題相符的語錄。
最後,我們實作了一個 RAG 聊天機器人,它使用從相似性搜尋中檢索到的引語作為上下文來回答使用者查詢。
與往常一樣,本文中使用的所有程式碼範例均可在 GitHub 上找到。