1. 概述
搜索是軟件中的一個基本概念,旨在在一個大型數據集內查找相關信息。它涉及在項目集合中找到特定項目。
在本教程中,我們將探索如何使用 Spring AI、PGVector 和 Ollama 實現語義搜索。
2. 背景
語義搜索是一種高級搜索技術,它使用詞語的含義來找到最相關的結果。為了構建一個語義搜索應用程序,我們需要理解一些關鍵概念:
- 詞嵌入 (Word Embeddings):詞嵌入是一種詞語表示方法,允許具有相似含義的詞語具有相似的表示。詞嵌入將詞語轉換為數值向量,這些向量可用於機器學習模型。
- 語義相似度 (Semantic Similarity):語義相似度是衡量兩個文本片段在含義上的相似程度的指標。它用於比較詞語、句子或文檔的含義。
- 向量空間模型 (Vector Space Model):向量空間模型是一種用於將文本文檔表示為高維空間中向量的數學模型。在該模型中,每個詞語都表示為一個向量,並且兩個詞語之間的相似度根據它們向量之間的距離進行計算。
- 餘弦相似度 (Cosine Similarity):餘弦相似度是衡量兩個非零向量在內積空間中相似度的指標,它測量它們之間的餘弦值。它計算在向量空間模型中兩個向量之間的相似度。
現在,讓我們開始構建一個演示這些概念的應用。
3. 先決條件
首先,我們應該在我們的機器上安裝 Docker,以便運行 PGVector 和 Ollama。
然後,我們需要在我們的 Spring 應用程序中添加 Spring AI 和 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我們選擇了 nomic-embed-text 作為我們的 Ollama 模型。Spring AI 在我們沒有下載該模型時,會幫我們獲取它。
PGVector 的設置確保了適當的向量存儲配置,通過初始化數據庫模式 (initialize-schema: true),將向量維度與常見嵌入尺寸對齊 (dimensions: 768),並使用 Hierarchical Navigable Small World (HNSW) 索引 (index-type: hnsw) 優化搜索效率,從而實現快速近似最近鄰搜索。
5. 執行語義搜索
現在我們的基礎設施已經就緒,我們可以實現一個簡單的語義搜索應用程序。我們的用例將是一個智能書籍搜索引擎,允許用户根據書籍內容進行搜索。
最初,我們將使用 PGVector 構建一個簡單的搜索功能,稍後將通過 Ollama 增強它,以提供更具上下文感知性的響應。
讓我們定義一個 <em >Book</em> 類,該類表示書籍實體:
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", "J. D. Salinger", "The Catcher in the Rye is a novel by J. D. 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=J. D. Salinger, description=The Catcher in the Rye is a novel by J. D. 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-search1984 is a classic dystopian novel written by George Orwell. Here's an excerpt from the book: "He loved Big Brother. He even admired him. After all, who wouldn't? Big Brother was all-powerful, all-knowing, and infinitely charming. And now that he had given up all his money in bank accounts with his names on them, and his credit cards, and his deposit slips, he felt free." This excerpt sets the tone for the novel, which depicts a totalitarian society where the government exercises total control over its citizens. The protagonist, Winston Smith, is a low-ranking member of the ruling Party who begins to question the morality of their regime. Would you like to know more about the book or its themes?
與其返回三個獨立的圖書描述,像簡單的語義搜索那樣,Ollama 會從搜索結果中合成最相關的信息。在這種情況下,1984是最相關的匹配項,因此Ollama 專注於提供詳細的摘要,而不是列出無關的書籍。這模擬了人類般的搜索輔助,使結果更具吸引力和洞察力。
6. 結論
在本文中,我們探討了如何使用 Spring AI、PGVector 和 Ollama 實現語義搜索。我們比較了兩個端點:一個對我們的圖書目錄進行語義搜索,另一個則將搜索結果與 Ollama LLM 進行反饋和增強。