1. 概述
在傳統的數據庫中,我們通常依賴精確的關鍵詞或基本模式匹配來實現我們的搜索功能。雖然對於簡單的應用程序來説已經足夠,但這種方法並不能完全理解自然語言查詢背後的含義和上下文。
向量存儲解決了這一侷限性,通過將數據存儲為數值向量,從而捕捉其含義。相似的詞語會被聚類在一起,這使得相似搜索成為可能,數據庫即使沒有包含查詢中使用的精確關鍵詞也能返回相關的結果。
Oracle Database 23ai 將這種向量存儲能力集成到其現有生態系統中,使我們能夠在不依賴單獨的向量存儲的情況下構建 AI 應用。使用相同的數據庫,我們可以創建既使用傳統結構化數據管理,又使用向量相似搜索的解決方案。
在本教程中,我們將探索將 Oracle 向量數據庫與 Spring AI 集成。我們將實現原生相似搜索,以查找語義相關的內容。然後,我們將在此能力的基礎上構建 Retrieval-Augmented Generation (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 向量存儲啓動器依賴項(https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-vector-store-oracle)使我們能夠與 Oracle 向量數據庫建立連接並與之交互。 此外,我們還導入了用於 RAG 實現的 vector store advisors 依賴項。
最後,我們導入了 Spring AI 的 OpenAI 啓動器依賴項,我們將使用它與 chat completion 和 embedding 模型進行交互。
鑑於我們項目中使用多個 Spring AI 啓動器,我們還應在 pom.xml 中包含 Spring AI Bill of Materials (BOM):https://mvnrepository.com/artifact/org.springframework.ai/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>通過此次添加,我們現在可以將 版本 標籤從我們的啓動依賴項中移除。BOM 消除了版本衝突的風險,並確保 Spring AI 依賴項相互兼容。
2.2. 配置 AI 模型和向量存儲屬性
為了將我們的文本數據轉換為 Oracle 向量數據庫可存儲和搜索的向量,我們需要一個嵌入模型。 此外,為了我們的 RAG 聊天機器人,我們還需要一個聊天完成模型。
對於我們的演示,我們將使用 OpenAI 提供提供的模型。 讓我們配置 OpenAI API 密鑰 以及在我們的 application.yaml 文件中的模型:
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 作為我們的嵌入式模型和聊天完成模型, respectively。
在配置這些屬性時,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 獲取 引用 記錄
為了演示目的,我們將使用 Breaking Bad Quotes 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,我們調用外部 API,採用默認計數 150,並使用 ParameterizedTypeReference 將 API 響應反序列化為 Quote 記錄列表。
3.2. 在向量數據庫中存儲 文檔
現在,為了在應用程序啓動時將引言填充到我們的 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 vector 數據庫,這是先決條件,需要一個活躍的 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 AI Testcontainers 依賴項,用於 Spring Boot,以及 Testcontainers 中的 Oracle Database 模塊。
這些依賴項提供了啓動用於 Oracle 向量數據庫的臨時 Docker 實例所需的類。
接下來,讓我們創建一個 類來定義我們的 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() 方法來實現。
接下來,我們調用我們的 vectorStore bean 的 similaritySearch() 方法,並傳入我們的 searchRequest。 類似於 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:
<question_answer_context>
USER_QUESTION:
<query>
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 向量數據庫的本地測試環境。
首先,我們從 Breaking Bad 引用 API 中獲取引用,並在應用程序啓動期間填充向量存儲。然後,我們對存儲的數據執行相似性搜索,以檢索與該劇主題相匹配的引用。
最後,我們實現了使用相似性搜索檢索到的引用作為上下文來回答用户查詢的 RAG 聊天機器人。