跳到主要内容

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

パラメータ概要

Plain 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 を指定し、かつ 0 より大きい必要があります。そうでないとテーブル初期化時に例外が投げられます。任意の場合: useIndexfalse ならこのプロパティは無視され、設定不要です。
createTable埋め込みテーブルを自動作成するかどうか。true任意
dropTableFirst再作成前にテーブルをドロップするかどうか(テスト向けに有用)。false任意
searchMode使用する検索モード。選択肢:
  • VECTOR: コサイン距離を用いた標準的なベクトル類似度検索。
  • HYBRID: Reciprocal Rank Fusion(RRF)によりベクトル検索と全文キーワード検索を組み合わせる。
VECTOR任意
rrfKRRF(Reciprocal Rank Fusion)アルゴリズムで使う定数 k: Score = 1/(k + rank_vector) + 1/(k + rank_keyword)。値が小さいほど(20–40)上位結果を強調し、大きいほど(80–100)よりバランスの取れたランキングになります。searchModeHYBRID のときのみ意味があります。60任意。HYBRID 検索モードでのみ使用。
textSearchConfigキーワード検索に使う PostgreSQL テキスト検索設定名(例: simpleenglishgerman)。searchModeHYBRID のときのみ適用されます。simple任意。HYBRID 検索モードでのみ使用。
metadataStorageConfig埋め込みに関連するメタデータの扱いを設定するオブジェクト。3 つの保存モードをサポート:
  • 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: PGVector 拡張があらかじめ設定された Docker イメージを指定します。

以下は PgVectorEmbeddingStore の作成例です。1 つ目は必須パラメータのみ、 2 つ目は利用可能な全パラメータを設定します。

  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();

すばやく始めたい場合は 1 つ目の例を使ってください。 2 つ目の例は、より細かく制御・カスタマイズするために利用可能なビルダーパラメータをすべて活かす方法を示します。

PGVector を使った完全な RAG 例

このセクションでは、セマンティック検索のために PGVector 拡張付き PostgreSQL を使い、完全な Retrieval-Augmented Generation(RAG)システムを構築する方法を示します。

概要

RAG システムは主に 2 つの段階から成ります:

  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 トークン): 精度が高く、より具体的な回答
  • 大きめのチャンク(500–800 トークン): より多くのコンテキストだが、関連性が下がる可能性

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

スコア範囲の注意

  • 両検索で 1 位のときの最大スコアは 2/(k+1)(例: k=60 → 約 0.0328、k=80 → 約 0.0247)。
  • 順位が下がるにつれスコアは 0 に近づき、1.0 には達しません
  • RRF スコアは順位ベースであり、ベクトルのみ検索のコサイン類似度(0.0–1.0)と直接比較できません。

ベクトルのみ検索との主な違い

観点ベクトル検索ハイブリッド検索
クエリ入力queryEmbedding のみqueryEmbedding および 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 example を参照してください。

この例では次を示します:

  • PgVectorEmbeddingStore の Spring Boot 自動構成

  • ドキュメント取り込みとクエリ用の REST API エンドポイント

  • 適切なコネクションプーリングとエラー処理

  • ローカル開発用の Docker Compose セットアップ

  • その他の例