ベクターデータベースとRAG:セマンティック検索、pgvector、そして自社データから質問に答える

Dev.to / 2026/4/7

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical UsageModels & Research

要点

  • この記事では、ベクターデータベースが「意味」に基づいて文書を照合することで、完全一致のキーワード検索ではなくセマンティック検索を可能にし、ドキュメントやカスタマーサポートといったユースケースで結果を改善できることを説明します。
  • LLMの埋め込み(embeddings)モデルを用いた埋め込みパイプラインを概説し(例としてOpenAIのtext-embedding-3-smallを使用)、テキストをベクトルに変換してインデックス作成と取得(リトリーブ)に利用する方法を示します。
  • pgvectorを使ってPostgres上にベクトルを直接保存し、問い合わせる方法を説明し、Postgresをセマンティック検索ワークフローのための実用的なバックエンドとして位置づけます。
  • RAG(Retrieval-Augmented Generation)を、ベクトル検索(リトリーブ)とLLMを組み合わせて、組織自身のデータに根拠づけられた形で質問に答える手法として提示します。
  • さらに、埋め込みの代替手段(例:Voyage AI経由でClaudeを使う)についても簡単に触れ、大規模なインデックス作成におけるモデルの速度やコストといった考慮事項にも触れます。

ベクターデータベースにより、セマンティック検索が可能になります。つまり、厳密なキーワードではなく意味によってドキュメントを見つけられるのです。LLMと組み合わせることで、RAG(Retrieval-Augmented Generation:検索拡張生成)アプリケーションが実現し、あなた自身のデータから質問に答えられます。では実装してみましょう。

ベクター検索で解決できること

キーワード検索:完全に一致する単語を含むドキュメントを見つけます。
ベクター検索:意味が近いドキュメントを見つけます。

質問: "パスワードをリセットするにはどうすればいいですか?

キーワード検索で見つかるもの: "パスワードリセット手順", "パスワードリセットのページ"
ベクター検索でも見つかるもの: "アカウント復旧", "認証情報を忘れた場合", "ログインの問題"

ドキュメント、カスタマーサポート、ナレッジベースでは、ベクター検索は非常に関連性の高い結果を返します。

埋め込み(Embedding)パイプライン

import OpenAI from 'openai'

const openai = new OpenAI()

async function embedText(text: string): Promise<number[]> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small', // 1536次元、$0.02/100万トークン
    input: text,
  })
  return response.data[0].embedding
}

// あるいはClaude(Voyage AI経由)で
// voyage-3-lite:大規模なインデックス作成向けに高速かつ低コスト

pgvectorを使ってPostgresにベクターを保存する

-- pgvector拡張を有効化
CREATE EXTENSION IF NOT EXISTS vector;

-- ベクター列を持つテーブル
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  metadata JSONB,
  embedding vector(1536)  -- 次元数はモデルに合わせる
);

-- 高速な類似検索用のインデックス
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);
// 埋め込み(embedding)付きでドキュメントを挿入する
async function indexDocument(content: string, metadata: object) {
  const embedding = await embedText(content)
  await db.$executeRaw`
    INSERT INTO documents (content, metadata, embedding)
    VALUES (${content}, ${JSON.stringify(metadata)}, ${JSON.stringify(embedding)}::vector)
  `
}

セマンティック検索クエリ

async function semanticSearch(query: string, limit = 5) {
  const queryEmbedding = await embedText(query)

返却形式: {"translated": "翻訳されたHTML"}const results = await db.$queryRaw<Array<{
    id: number
    content: string
    metadata: object
    similarity: number
  }>>`
    SELECT id, content, metadata,
      1 - (embedding <=> ${JSON.stringify(queryEmbedding)}::vector) AS similarity
    FROM documents
    ORDER BY embedding <=> ${JSON.stringify(queryEmbedding)}::vector
    LIMIT ${limit}
  `

  return results.filter(r => r.similarity > 0.7) // 閾値
}

RAG: データからの質問への回答

async function answerFromDocs(question: string): Promise<string> {
  // 1. 関連するドキュメントを見つける
  const relevantDocs = await semanticSearch(question, 5)

  if (relevantDocs.length === 0) {
    return 'その質問に答えるための関連情報が見つかりませんでした。'
  }

  // 2. 取得したドキュメントからコンテキストを構築する
  const context = relevantDocs
    .map((doc, i) => `[${i + 1}] ${doc.content}`)
    .join('

')

  // 3. アースティング(根拠)付きコンテキストで Claude に質問する
  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    system: '提供されたコンテキストのみを使って質問に答えてください。コンテキストに答えが含まれていない場合は、それを述べてください。',
    messages: [{
      role: 'user',
      content: `Context:
${context}

Question: ${question}`,
    }],
  })

  return response.content[0].text
}

分割(Chunking)戦略

ドキュメントのチャンク分割は、検索(リトリーバル)の品質に大きく影響します:

function chunkDocument(text: string, chunkSize = 500, overlap = 50): string[] {
  const words = text.split(' ')
  const chunks: string[] = []
for (let i = 0; i < words.length; i += chunkSize - overlap) {
    chunks.push(words.slice(i, i + chunkSize).join(' '))
    if (i + chunkSize >= words.length) break
  }

  return chunks
}

// 各チャンクを個別にインデックスする
for (const chunk of chunkDocument(document)) {
  await indexDocument(chunk, { sourceDocId: document.id })
}

管理オプション

pgvector を自分で管理したくない場合:

  • Pinecone: 完全マネージド、手厚い無料ティア
  • Qdrant: オープンソース、自社ホスティングまたはクラウド
  • Supabase Vector: Supabase 上の pgvector
  • Neon: Neon 上の pgvector(アプリと同じDB)

whoffagents.com の AI SaaS Starter には、pgvector + Prisma、埋め込みパイプライン、セマンティック検索、RAG パターンを事前構築したベクトル検索モジュールが含まれています。$99 の一回払い。

返却形式: {"translated": "翻訳されたHTML"}