使用 Spring AI 和 PGVector 實現語義搜索
1. 概述
搜尋是軟體中的一個基本概念,旨在在大量數據中找到相關資訊。它涉及在一組物品中查找特定物品。
在本教學中,我們將探討如何使用 Spring AI、PGVector 和 Ollama 實作語意搜尋。
2. 背景
語意搜尋是一種進階搜尋技術,它利用字詞的意思來找出最相關的結果。要建立語義搜尋應用程序,我們需要了解一些關鍵概念:
- 詞嵌入:詞嵌入是一種詞語表示,它允許具有相似意義的詞語具有相似的表示。詞嵌入將單字轉換為可用於機器學習模型的數字向量。
- 語意相似度:語意相似度是衡量兩段文本在意義上的相似程度的標準。用於比較單字、句子或文件的意思。
- 向量空間模型:向量空間模型是一種數學模型,用於將文字文件表示為高維空間中的向量。在該模型中,每個單字都表示為向量,透過兩個單字向量之間的距離來計算兩個單字之間的相似度。
- 餘弦相似度:餘弦相似度是內積空間中兩個非零向量之間的相似度量,它測量它們之間角度的餘弦。它計算向量空間模型中兩個向量之間的相似度。
現在讓我們建立一個應用程式來演示這一點。
3. 先決條件
首先,我們應該在我們的機器上安裝 Docker 來運行 PGVector 和 Ollama。
然後,我們的 Spring 應用程式中需要Spring AI Ollama 和 PGVector 依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
我們還將新增 Spring Boot 的 Docker Compose 支援來管理 Ollama 和 PGVector Docker 容器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<version>3.1.1</version>
</dependency>
除了依賴關係之外,我們還將透過在docker-compose.yml
檔案中描述這兩個服務將它們放在一起:
services:
postgres:
image: pgvector/pgvector:pg17
environment:
POSTGRES_DB: vectordb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5434:5432"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 10s
timeout: 5s
retries: 5
ollama:
image: ollama/ollama:latest
ports:
- "11435:11434"
volumes:
- ollama_data:/root/.ollama
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:11435/api/health" ]
interval: 10s
timeout: 5s
retries: 10
volumes:
ollama_data:
4.配置應用程式
接下來,我們需要配置我們的 Spring Boot 應用程式以使用 Ollama 和 PGVector 服務。在application.yml
檔案中,我們定義了幾個屬性。讓我們特別注意一下ollama
和vectorstore
屬性的選擇:
spring:
ai:
ollama:
init:
pull-model-strategy: when_missing
chat:
include: true
embedding:
options:
model: nomic-embed-text
vectorstore:
pgvector:
initialize-schema: true
dimensions: 768
index-type: hnsw
docker:
compose:
file: docker-compose.yml
enabled: true
datasource:
url: jdbc:postgresql://localhost:5434/vectordb
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
我們為 Ollama 模型選擇了[nomic-embed-text](https://ollama.com/library/nomic-embed-text)
。如果我們沒有下載它,Spring AI 會幫我們拉取它。
PGVector 設定透過初始化資料庫模式( initialize-schema: true
)、將向量維度與常見嵌入大小對齊( dimensions: 768
)以及使用分層可導航小世界(HNSW)索引( index-type: hnsw
)優化搜尋效率來確保正確的向量儲存設置,以實現快速近似最近鄰搜尋。
5. 執行語義搜尋
現在我們的基礎架構已經準備就緒,我們可以實作一個簡單的語義搜尋應用程式。我們的用例是一個智慧圖書搜尋引擎,允許用戶根據圖書內容搜尋圖書。
首先,我們將使用 PGVector 建立一個簡單的搜尋功能,然後,我們將使用 Ollama 來增強它,以提供更多上下文感知回應。
讓我們定義一個代表書籍實體的Book
類別:
public record Book(String title, String author, String description) {
}
在我們搜尋書籍之前,我們需要將書籍資料匯入 PGVector 儲存。以下方法添加了一些範例書籍資料:
void run() {
var books = List.of(
new Book("The Great Gatsby", "F. Scott Fitzgerald", "The Great Gatsby is a 1925 novel by American writer F. Scott Fitzgerald. Set in the Jazz Age on Long Island, near New York City, the novel depicts first-person narrator Nick Carraway's interactions with mysterious millionaire Jay Gatsby and Gatsby's obsession to reunite with his former lover, Daisy Buchanan."),
new Book("To Kill a Mockingbird", "Harper Lee", "To Kill a Mockingbird is a novel by the American author Harper Lee. It was published in 1960 and was instantly successful. In the United States, it is widely read in high schools and middle schools."),
new Book("1984", "George Orwell", "Nineteen Eighty-Four: A Novel, often referred to as 1984, is a dystopian social science fiction novel by the English novelist George Orwell. It was published on 8 June 1949 by Secker & Warburg as Orwell's ninth and final book completed in his lifetime."),
new Book("The Catcher in the Rye", "JD Salinger", "The Catcher in the Rye is a novel by JD Salinger, partially published in serial form in 1945–1946 and as a novel in 1951. It was originally intended for adults but is often read by adolescents for its themes of angst, alienation, and as a critique on superficiality in society."),
new Book("Lord of the Flies", "William Golding", "Lord of the Flies is a 1954 novel by Nobel Prize-winning British author William Golding. The book focuses on a group of British")
);
List<Document> documents = books.stream()
.map(book -> new Document(book.toString()))
.toList();
vectorStore.add(documents);
}
現在我們已將範例書籍資料新增至 PGVector 儲存體中,我們可以實作語義搜尋功能。
5.1.語意搜尋
我們的目標是實現一個語義搜尋 API,讓使用者能夠根據書籍內容找到書籍。
讓我們定義一個與 PGVector 互動的控制器來執行相似性搜尋:
@RequestMapping("/books")
class BookSearchController {
final VectorStore vectorStore;
final ChatClient chatClient;
BookSearchController(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) {
this.vectorStore = vectorStore;
this.chatClient = chatClientBuilder.build();
}
...
接下來,我們將建立一個POST /search
端點,接受來自使用者的搜尋條件並傳回符合的書籍清單:
@PostMapping("/search")
List<String> semanticSearch(@RequestBody String query) {
return vectorStore.similaritySearch(SearchRequest.builder()
.query(query)
.topK(3)
.build())
.stream()
.map(Document::getText)
.toList();
}
請注意,我們使用了VectorStore#
similaritySearch.
這將對我們之前獲取的書籍執行語義搜尋。
啟動應用程式後,我們就可以進行搜尋了。讓我們使用 cURL 搜尋1984:
curl -X POST --data "1984" http://localhost:8080/books/search
回應包含三本書:一本完全匹配,兩本部分匹配:
[
"Book[title=1984, author=George Orwell, description=Nineteen Eighty-Four: A Novel, often referred to as 1984, is a dystopian social science fiction novel by the English novelist George Orwell.]",
"Book[title=The Catcher in the Rye, author=JD Salinger, description=The Catcher in the Rye is a novel by JD Salinger, partially published in serial form in 1945–1946 and as a novel in 1951.]",
"Book[title=To Kill a Mockingbird, author=Harper Lee, description=To Kill a Mockingbird is a novel by the American author Harper Lee.]"
]
5.2.使用 Ollama 增強語義搜尋
我們可以整合 Ollama 來產生釋義回應,提供額外的上下文來改善語意搜尋結果,具體步驟如下:
- 從搜尋查詢中檢索最匹配的三本書籍描述。
- 將這些描述輸入到 Ollama 中以產生更自然、更具情境感知的回應。
- 提供包含總結和釋義資訊的回應,提供更清晰、更相關的見解。
讓我們在BookSearchController
中建立一個新方法,使用 Ollama 產生查詢的釋義:
@PostMapping("/enhanced-search")
String enhancedSearch(@RequestBody String query) {
String context = vectorStore.similaritySearch(SearchRequest.builder()
.query(query)
.topK(3)
.build())
.stream()
.map(Document::getText)
.reduce("", (a, b) -> a + b + "\n");
return chatClient.prompt()
.system(context)
.user(query)
.call()
.content();
}
現在讓我們透過向/books/enhanced-search
端點發送POST
請求來測試增強語意搜尋功能:
curl -X POST --data "1984" http://localhost:8080/books/enhanced-search
Ollama 不會像簡單的語意搜尋那樣傳回三個單獨的書籍描述,而是從搜尋結果中綜合出最相關的資訊。在這種情況下, 1984
是最相關的匹配,因此 Ollama 專注於提供詳細的摘要,而不是列出不相關的書籍。這模仿了人類的搜尋幫助,使結果更具吸引力和洞察力。
6. 結論
在本文中,我們探討如何使用 Spring AI、PGVector 和 Ollama 實作語意搜尋。我們比較了兩個端點;一個對我們的圖書目錄進行語義搜索,另一個使用 Ollama LLM 提供並增強該搜索結果。
與往常一樣,這些範例的完整實作可以在 GitHub 上找到。