跳到主要内容

PGVector

LangChain4j 与 PGVector 无缝集成,使开发者能够将 向量嵌入直接存储在 PostgreSQL 中并查询。该集成非常适合语义搜索、 RAG 等应用场景。

Maven 依赖


<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>1.18.1-beta28</version>
</dependency>

Gradle 依赖

implementation 'dev.langchain4j:langchain4j-pgvector:1.18.1-beta28'

API

  • PgVectorEmbeddingStore

参数摘要

纯 Java 属性说明默认值必需/可选
datasource用于数据库连接的 DataSource 对象。仅在 PgVectorEmbeddingStore.datasourceBuilder() 构建器变体中可用。若未提供,则必须在 PgVectorEmbeddingStore.builder() 构建器变体中分别提供 hostportuserpassworddatabase若未分别提供 hostportuserpassworddatabase,则为必需。
hostPostgreSQL 服务器主机名。未提供 DataSource 时必需。未提供 DataSource 时必需
portPostgreSQL 服务器端口号。未提供 DataSource 时必需。未提供 DataSource 时必需
user数据库认证用户名。未提供 DataSource 时必需。未提供 DataSource 时必需
password数据库认证密码。未提供 DataSource 时必需。未提供 DataSource 时必需
database要连接的数据库名称。未提供 DataSource 时必需。未提供 DataSource 时必需
table用于存储嵌入的数据库表名。必需
dimension嵌入向量的维度。应与所用嵌入模型匹配。可使用 embeddingModel.dimension() 动态设置。必需
useIndexIVFFlat 索引将向量划分为多个列表,然后搜索与查询向量最接近的子集列表。与 HNSW 相比,构建更快、内存占用更少,但查询性能(速度-召回权衡)较低。应使用 IVFFlat 索引。false可选
indexListSizeIVFFlat 索引的列表数量。何时必需:若 useIndextrue,则必须提供 indexListSize 且必须大于零。否则程序在表初始化期间会抛出异常。何时可选:若 useIndexfalse,则忽略该属性,无需设置。
createTable是否自动创建嵌入表。true可选
dropTableFirst是否在重新创建表之前先删除表(对测试很有用)。false可选
searchMode使用的搜索模式。选项:
  • VECTOR:使用余弦距离的标准向量相似度搜索。
  • HYBRID:将向量搜索与全文关键词搜索结合,使用 Reciprocal Rank Fusion(RRF)。
VECTOR可选
rrfKRRF(Reciprocal Rank Fusion)算法中使用的常数 kScore = 1/(k + rank_vector) + 1/(k + rank_keyword)。较低的值(20-40)更强调顶部结果;较高的值(80-100)产生更均衡的排名。仅在 searchMode 设为 HYBRID 时相关。60可选。仅在 HYBRID 搜索模式下使用。
textSearchConfig关键词搜索使用的 PostgreSQL 文本搜索配置名称(例如 simpleenglishgerman)。仅在 searchModeHYBRID 时适用。simple可选。仅在 HYBRID 搜索模式下使用。
metadataStorageConfig处理与嵌入关联的元数据的配置对象。支持三种存储模式:
  • COLUMN_PER_KEY:适用于事先知道元数据键的静态元数据。
  • COMBINED_JSON:适用于事先不知道元数据键的动态元数据。以 JSON 存储。(默认)
  • COMBINED_JSONB:与 JSON 类似,但以二进制格式存储,便于在大数据集上优化查询。
COMBINED_JSON可选。若未设置,则使用带有 COMBINED_JSON 的默认配置。

示例

为演示 PGVector 的能力,可以使用 Docker 化的 PostgreSQL 环境。它借助 Testcontainers 运行带有 PGVector 的 PostgreSQL。

使用 Docker 快速开始

要快速搭建带有 PGVector 扩展的 PostgreSQL 实例,可以使用以下 Docker 命令:

docker run --rm --name langchain4j-postgres-test-container -p 5432:5432 -e POSTGRES_USER=my_user -e POSTGRES_PASSWORD=my_password pgvector/pgvector

命令说明:

  • docker run:运行新容器。
  • --rm:容器停止后自动删除,确保不留残留数据。
  • --name langchain4j-postgres-test-container:将容器命名为 langchain4j-postgres-test-container,便于 识别。
  • -p 5432:5432:将本地机器的 5432 端口映射到容器的 5432 端口。
  • -e POSTGRES_USER=my_user:将 PostgreSQL 用户名设为 my_user。
  • -e POSTGRES_PASSWORD=my_password:将 PostgreSQL 密码设为 my_password。
  • pgvector/pgvector:指定要使用的 Docker 镜像,已预配置 PGVector 扩展。

以下是两个创建 PgVectorEmbeddingStore 的代码示例。第一个仅使用必需参数, 第二个配置了所有可用参数。

  1. 仅必需参数
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost") // Required: Host of the PostgreSQL instance
.port(5432) // Required: Port of the PostgreSQL instance
.database("postgres") // Required: Database name
.user("my_user") // Required: Database user
.password("my_password") // Required: Database password
.table("my_embeddings") // Required: Table name to store embeddings
.dimension(embeddingModel.dimension()) // Required: Dimension of embeddings
.build();
  1. 设置全部参数

在这个变体中,我们包含了所有常用的可选参数,如 useIndex、indexListSize、 createTable、dropTableFirst 和 metadataStorageConfig。请根据需要调整这些值:

EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
// Required parameters
.host("localhost")
.port(5432)
.database("postgres")
.user("my_user")
.password("my_password")
.table("my_embeddings")
.dimension(embeddingModel.dimension())

// Optional parameters
.useIndex(true) // Enable IVFFlat index
.indexListSize(100) // Number of lists for IVFFlat index
.createTable(true) // Automatically create the table if it doesn’t exist
.dropTableFirst(false) // Don’t drop the table first (set to true if you want a fresh start)
.metadataStorageConfig(MetadataStorageConfig.combinedJsonb()) // Store metadata as a combined JSONB column

.build();

如果只想用最小配置快速上手,请使用第一个示例。 第二个示例展示了如何利用所有可用的 builder 参数,以获得更多控制和自定义能力。

使用 PGVector 的完整 RAG 示例

本节演示如何使用带有 PGVector 扩展的 PostgreSQL 构建完整的检索增强生成(RAG)系统,用于语义搜索。

概述

RAG 系统包含两个主要阶段:

  1. 索引阶段(离线):加载文档、切分为块、生成嵌入,并存储到 pgvector
  2. 检索阶段(在线):对用户查询做嵌入、搜索相似块、将上下文注入 LLM 提示

前置条件

确保已运行带有 PGVector 的 PostgreSQL 实例(参见上文的 Docker 设置)。

1. 文档摄入(索引阶段)

此示例展示如何加载文档、切分为块,并将嵌入存储到 pgvector:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;

import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument;

// Load document (PDF, TXT, etc.)
Document document = loadDocument("/path/to/document.pdf", new ApachePdfBoxDocumentParser());

// Split document into smaller chunks
// 300 tokens per chunk, 50 tokens overlap for context continuity
DocumentSplitter splitter = DocumentSplitters.recursive(300, 50);

// Create embedding model (384 dimensions for AllMiniLmL6V2)
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

// Create pgvector embedding store
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("postgres")
.user("my_user")
.password("my_password")
.table("document_embeddings")
.dimension(embeddingModel.dimension()) // 384 for AllMiniLmL6V2
.build();

// Ingest: split document, generate embeddings, and store in pgvector
EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build()
.ingest(document);

System.out.println("Document ingested successfully!");

2. 查询(检索阶段)

此示例展示如何用用户问题查询 RAG 系统:

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;

import java.util.List;
import java.util.stream.Collectors;

// User's question
String question = "What is the refund policy?";

// Generate embedding for the question
Embedding questionEmbedding = embeddingModel.embed(question).content();

// Search for the most similar text segments (top 3 results)
List<EmbeddingMatch<TextSegment>> relevantSegments = embeddingStore.findRelevant(
questionEmbedding,
3 // Retrieve top 3 most similar chunks
);

// Build context from retrieved segments
String context = relevantSegments.stream()
.map(match -> match.embedded().text())
.collect(Collectors.joining("\n\n"));

// Create prompt with retrieved context
String promptWithContext = String.format("""
Answer the question based on the following context.
If the context doesn't contain relevant information, say "I don't have enough information to answer."

Context:
%s

Question: %s

Answer:
""", context, question);

// Send to LLM with context
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();

String answer = chatModel.generate(promptWithContext);
System.out.println("Answer: " + answer);

生产环境注意事项

基于实际使用经验,以下是生产部署的重要注意事项:

1. 连接池

在生产环境中,应使用带有连接池的 DataSource,而不是单独的连接参数:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
config.setUsername("my_user");
config.setPassword("my_password");
config.setMaximumPoolSize(10);

HikariDataSource dataSource = new HikariDataSource(config);

EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.datasourceBuilder()
.datasource(dataSource)
.table("document_embeddings")
.dimension(384)
.build();

2. 索引优化

对于大型数据集(>10 万条嵌入),启用 IVFFlat 索引以提升查询性能:

EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
// ... other config ...
.useIndex(true)
.indexListSize(100) // Adjust based on dataset size
.build();

注意:在大型数据集上创建索引可能需要较长时间。请在查询速度与索引构建时间之间取得平衡。

3. 元数据存储

为在大型数据集上获得更好的查询性能,请使用 JSONB 存储元数据:

import dev.langchain4j.store.embedding.pgvector.MetadataStorageConfig;

EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
// ... other config ...
.metadataStorageConfig(MetadataStorageConfig.combinedJsonb())
.build();

4. 块大小调优

根据用例试验不同的块大小:

  • 较小的块(200-300 tokens):精度更高,答案更具体
  • 较大的块(500-800 tokens):上下文更多,但可能降低相关性

5. 错误处理

始终优雅地处理数据库连接失败:

try {
embeddingStore.add(embedding, textSegment);
} catch (Exception e) {
logger.error("Failed to store embedding", e);
// Implement retry logic or fallback behavior
}

混合搜索(向量 + 关键词)

PGVector 支持将向量相似度搜索与 PostgreSQL 全文关键词搜索相结合的 混合搜索。通过同时利用语义理解与精确关键词匹配,这种方法往往比仅向量搜索提供更好的结果。

何时使用混合搜索

  • 当你既需要语义相似度又需要精确关键词匹配时
  • 对于包含领域特定术语、产品名称或技术黑话的查询
  • 为提升 RAG 应用中的检索准确性

配置

通过设置 searchMode 参数启用混合搜索:

import dev.langchain4j.store.embedding.pgvector.SearchMode;

EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("postgres")
.user("my_user")
.password("my_password")
.table("document_embeddings")
.dimension(embeddingModel.dimension())
.searchMode(SearchMode.HYBRID) // Enable hybrid search (default: SearchMode.VECTOR)
.textSearchConfig("english") // Optional: PostgreSQL text search config (default: "simple")
.rrfK(60) // Optional: RRF algorithm parameter (default: 60)
.build();

用法

使用混合搜索时,必须同时提供嵌入 查询文本:

import dev.langchain4j.store.embedding.EmbeddingSearchRequest;

String question = "How to configure PostgreSQL vector search?";

// Generate embedding for the query
Embedding questionEmbedding = embeddingModel.embed(question).content();

// Search with both embedding and text (required for HYBRID mode)
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(questionEmbedding) // For vector similarity search
.query(question) // For keyword search (REQUIRED in HYBRID mode)
.maxResults(3)
.build();

List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(request);

工作原理

混合搜索使用 Reciprocal Rank Fusion(RRF) 合并结果:

  1. 向量搜索:使用余弦相似度查找语义相似的文本
  2. 关键词搜索:使用 PostgreSQL 的 tsvector 查找匹配关键词的文本
  3. RRF 融合:使用以下公式合并排名:
RRF_Score = 1/(k + rank_vector) + 1/(k + rank_keyword)

其中:

  • k 是一个常数(可通过 rrfK() 配置,默认:60)
  • rank_vector 是向量搜索的排名位置(1 = 最佳匹配)
  • rank_keyword 是关键词搜索的排名位置(1 = 最佳匹配)

分数计算示例(测试中使用的 k = 80):

如果某文档在向量搜索和关键词搜索中都排第 1:

Score = 1/(80+1) + 1/(80+1)
= 1/81 + 1/81
≈ 0.0247

分数范围说明

  • 当结果在两种搜索中都排第一时,最高分为 2/(k+1)(例如 k=60 → ~0.0328;k=80 → ~0.0247)。
  • 随着排名下降,分数趋近于 0;它们 不会 达到 1.0。
  • RRF 分数基于排名,不能直接与仅向量搜索的余弦相似度(0.0–1.0)比较。

与仅向量搜索的主要区别

方面向量搜索混合搜索
查询输入queryEmbeddingqueryEmbedding query 文本
分数类型余弦相似度(0.0-1.0)基于 RRF 排名的分数(最大 ≈ 2/(k+1);k=60 时约 ~0.033)
最适合语义相似度、改写精确关键词 + 语义含义

调优 RRF 参数

调整 rrfK 参数以控制排名敏感度:

.rrfK(40)   // More weight to top-ranked results (higher scores for top matches)
.rrfK(80) // More balanced between top and lower-ranked results
  • 较低的 k(20-40):更强调排名靠前的结果
  • 较高的 k(80-100):排名分布更均衡
  • 默认(60):对大多数用例是良好的平衡

Spring Boot 集成

关于将 pgvector 与 Spring Boot 微服务集成的完整生产级示例, 请参阅 pgvector RAG Spring Boot 示例

该示例演示了:

  • PgVectorEmbeddingStore 的 Spring Boot 自动配置

  • 用于文档摄入和查询的 REST API 端点

  • 恰当的连接池与错误处理

  • 用于本地开发的 Docker Compose 设置

  • 更多示例