1. 概述
在傳統的數據庫中,我們通常依賴精確的關鍵詞或基本模式匹配來實現搜索功能。雖然對於簡單的應用程序來説已經足夠,但這種方法並不能完全理解自然語言查詢背後的含義和上下文。
向量存儲解決了這一限制,通過將數據存儲為數值向量,從而捕捉其含義。相似的詞語會彼此靠近,這使得語義搜索成為可能,即使結果不包含查詢中使用的精確關鍵詞,也能返回相關的結果。
在本教程中,我們將探索如何將ChromaDB,一個開源向量存儲,與Ollama集成。
為了將我們的文本數據轉換為 ChromaDB 可以存儲和搜索的向量,我們需要一個嵌入模型。我們將使用 Ollama 在本地運行嵌入模型。
2. 依賴項
讓我們首先添加必要的依賴項到我們項目的 pom.xml文件中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-chroma-store-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>ChromaDB starter 依賴項 允許我們與 ChromaDB 向量存儲建立連接並與之交互。
此外,我們還導入了 Ollama starter 依賴項,我們將使用它來運行我們的嵌入模型。
由於當前版本 1.0.0-M6 是里程碑發佈,我們還需要將 Spring Milestones 倉庫添加到我們的 pom.xml 中:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>此倉庫是發佈里程版本的地方,與標準 Maven Central 倉庫不同。
由於我們項目中使用了多個 Spring AI starter,我們還應該將 Spring AI Bill of Materials (BOM) 添加到我們的 pom.xml 中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>通過此添加,我們現在可以從兩個啓動依賴項中移除 version 標籤。
BOM 消除了版本衝突的風險,並確保我們的 Spring AI 依賴項相互兼容。
3. 使用 Testcontainers 設置本地測試環境
為了方便本地開發和測試,我們將使用 Testcontainers 設置我們的 ChromaDB 向量存儲和 Ollama 服務。
運行所需服務並通過 Testcontainers 運行的前提是需要一個活躍的 Docker 實例。
3.1. 測試依賴
首先,我們需要將必要的測試依賴添加到我們的 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>chromadb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>ollama</artifactId>
<scope>test</scope>
</dependency>這些依賴項為我們提供了啓動外部服務所需的臨時 Docker 實例的必要類。
3.2. 定義 Testcontainers Bean
接下來,讓我們創建一個 @TestConfiguration 類,用於定義我們的 Testcontainers Bean:
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
@Bean
@ServiceConnection
public ChromaDBContainer chromaDB() {
return new ChromaDBContainer("chromadb/chroma:0.5.20");
}
@Bean
@ServiceConnection
public OllamaContainer ollama() {
return new OllamaContainer("ollama/ollama:0.4.5");
}
}我們指定用於容器的最新穩定版本。
我們還使用 @ServiceConnection 註解標記 bean 方法。這動態地註冊了所有用於與我們兩個外部服務建立連接所需的屬性。
即使未使用 Testcontainers 支持,Spring AI 也會在默認端口 8000 和 11434 上自動連接到 ChromaDB 和 Ollama(本地運行時)。
但是,在生產環境中,可以使用相應的 Spring AI 屬性覆蓋連接詳細信息:
spring:
ai:
vectorstore:
chroma:
client:
host: ${CHROMADB_HOST}
port: ${CHROMADB_PORT}
ollama:
base-url: ${OLLAMA_BASE_URL}配置完成後,Spring AI 會自動為我們創建類型為 VectorStore 和 EmbeddingModel 的 Bean,從而使我們能夠分別與我們的向量存儲和嵌入模型進行交互。稍後在本教程中,我們將探討如何使用這些 Bean。
儘管 @ServiceConnection 會自動定義必要的連接詳情,但我們仍然需要在我們的 application.yml 文件中配置一些額外的屬性:
spring:
ai:
vectorstore:
chroma:
initialize-schema: true
ollama:
embedding:
options:
model: nomic-embed-text
init:
chat:
include: false
pull-model-strategy: WHEN_MISSING這裏,我們啓用 ChromaDB 的 Schema 初始化。然後,我們配置 nomic-embed-text 作為我們的嵌入模型,並指示 Ollama 如果該模型不在我們的系統中,則拉取該模型。
或者,我們可以根據需要,使用 Ollama 中的其他嵌入模型,或者利用 Hugging Face 模型。
3.3. 在開發期間使用 Testcontainers
雖然 Testcontainers 主要用於集成測試,但我們也可以在本地開發期間使用它。
要實現這一點,我們將創建一個單獨的主類,位於 src/test/java 目錄中:
class TestApplication {
public static void main(String[] args) {
SpringApplication.from(Application::main)
.with(TestcontainersConfiguration.class)
.run(args);
}
}我們創建了一個 TestApplication 類,並在其 main 方法中啓動我們的主 Application 類,以及我們的 TestcontainersConfiguration 類。
這種設置有助於我們本地設置和管理外部服務。我們可以運行 Spring Boot 應用程序,並使其連接到通過 Testcontainers 啓動的外部服務。
4. 在應用程序啓動時填充 ChromaDB
現在我們已經設置了本地環境,讓我們在應用程序啓動期間使用一些樣例文檔填充 ChromaDB 向量存儲。
4.1. 從 PoetryDB 獲取 詩歌記錄
為了我們的演示,我們將使用 PoetryDB API 來獲取詩歌記錄。
讓我們為這個目的創建一個 PoetryFetcher 工具類:
class PoetryFetcher {
private static final String BASE_URL = "https://poetrydb.org/author/";
private static final String DEFAULT_AUTHOR_NAME = "Shakespeare";
public static List<Poem> fetch() {
return fetch(DEFAULT_AUTHOR_NAME);
}
public static List<Poem> fetch(String authorName) {
return RestClient
.create()
.get()
.uri(URI.create(BASE_URL + authorName))
.retrieve()
.body(new ParameterizedTypeReference<>() {});
}
}
record Poem(String title, List<String> lines) {}我們使用 RestClient 來調用 PoetryDB API,並指定 authorName。為了將 API 響應反序列化為 Poem 記錄的列表,我們使用 ParameterizedTypeReference,而無需顯式指定泛型響應類型,Java 將自動推斷類型。
我們還重載了我們的 fetch() 方法,不接受任何參數,用於通過作者 Shakespeare 獲取詩歌。我們將在此次章節中使用該方法。
4.2. 在 ChromaDB 向量存儲中存儲 文檔
現在,為了在應用程序啓動期間使用詩歌填充 ChromaDB 向量存儲,我們將創建一個 向量存儲初始化器 類,該類實現 應用程序運行器 接口:
@Component
class VectorStoreInitializer implements ApplicationRunner {
private final VectorStore vectorStore;
// standard constructor
@Override
public void run(ApplicationArguments args) {
List<Document> documents = PoetryFetcher
.fetch()
.stream()
.map(poem -> {
Map<String, Object> metadata = Map.of("title", poem.title());
String content = String.join("\n", poem.lines());
return new Document(content, metadata);
})
.toList();
vectorStore.add(documents);
}
}在我們的 VectorStoreInitializer 中,我們自動注入一個 VectorStore 的實例。
在 run() 方法內部,我們使用我們的 PoetryFetcher 工具類檢索一個 Poem 記錄列表。然後,我們將每個 poem 映射為 Document,其中 lines 作為 content,title 作為 metadata。
最後,我們將所有 documents 存儲到我們的向量存儲中。當調用 add() 方法時,Spring AI 會自動將我們的純文本 content 轉換為向量表示形式,然後再將其存儲到我們的向量存儲中。我們無需使用 EmbeddingModel bean 顯式地進行轉換。
默認情況下,Spring AI 使用 SpringAiCollection 作為存儲數據到向量存儲中的集合名稱,但我們可以通過 spring.ai.vectorstore.chroma.collection-name 屬性進行覆蓋。
5. 語義搜索測試
在我們的 ChromaDB 向量存儲中填充數據後,讓我們驗證我們的語義搜索功能:
private static final int MAX_RESULTS = 3;
@ParameterizedTest
@ValueSource(strings = {"Love and Romance", "Time and Mortality", "Jealousy and Betrayal"})
void whenSearchingShakespeareTheme_thenRelevantPoemsReturned(String theme) {
SearchRequest searchRequest = SearchRequest
.builder()
.query(theme)
.topK(MAX_RESULTS)
.build();
List<Document> documents = vectorStore.similaritySearch(searchRequest);
assertThat(documents)
.hasSizeLessThanOrEqualTo(MAX_RESULTS)
.allSatisfy(document -> {
String title = String.valueOf(document.getMetadata().get("title"));
assertThat(title)
.isNotBlank();
});
}在這裏,我們使用 @ValueSource 將一些常見的莎士比亞主題傳遞到我們的測試方法中。然後,我們創建一個 SearchRequest 對象,將 theme 作為 query,並將 MAX_RESULTS 設置為所需的查詢結果數量。
接下來,我們調用 vectorStore 豆的 similaritySearch() 方法,並傳入我們的 searchRequest 對象。 類似於 VectorStore 的 add() 方法,Spring AI 在查詢向量存儲之前,會將我們的 query 轉換為其向量表示。
返回的 documents 將包含與給定 theme 語義相關的詩歌,即使它們不包含確切的關鍵詞。
6. 結論
在本文中,我們探討了如何將 ChromaDB 向量存儲與 Spring AI 集成。
使用 Testcontainers,我們啓動了 ChromaDB 和 Ollama 服務的 Docker 容器,創建一個本地測試環境。
我們查看了如何在應用程序啓動時,使用來自 PoetryDB API 的詩歌,填充我們的向量存儲。然後,我們使用常見的詩歌主題來驗證我們的語義搜索功能。