Supabase Vector - PostgreSQLでベクトル検索

2025.12.04

Supabase Vectorとは

Supabase Vectorは、PostgreSQLの拡張機能pgvectorを活用したベクトル検索機能です。AI/MLアプリケーションで必要なセマンティック検索やRAG(検索拡張生成)を、既存のPostgreSQLデータベースで実現できます。

セットアップ

pgvectorの有効化

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

-- ベクトルカラムを持つテーブルを作成
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(1536), -- OpenAI ada-002の次元数
  metadata JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

インデックスの作成

-- IVFFlat インデックス(高速、近似検索)
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- HNSW インデックス(より高精度)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

ベクトルの生成と保存

OpenAI Embeddingsを使用

import { createClient } from '@supabase/supabase-js';
import OpenAI from 'openai';

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
const openai = new OpenAI();

async function addDocument(content: string, metadata: object) {
  // Embeddingを生成
  const embeddingResponse = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input: content
  });

  const embedding = embeddingResponse.data[0].embedding;

  // Supabaseに保存
  const { error } = await supabase
    .from('documents')
    .insert({
      content,
      embedding,
      metadata
    });

  if (error) throw error;
}

ベクトル検索

類似度検索

async function searchSimilar(query: string, limit = 5) {
  // クエリのEmbeddingを生成
  const embeddingResponse = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input: query
  });

  const queryEmbedding = embeddingResponse.data[0].embedding;

  // ベクトル検索
  const { data, error } = await supabase.rpc('match_documents', {
    query_embedding: queryEmbedding,
    match_threshold: 0.7,
    match_count: limit
  });

  return data;
}

検索用SQL関数

CREATE OR REPLACE FUNCTION match_documents(
  query_embedding VECTOR(1536),
  match_threshold FLOAT,
  match_count INT
)
RETURNS TABLE (
  id BIGINT,
  content TEXT,
  metadata JSONB,
  similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
  SELECT
    documents.id,
    documents.content,
    documents.metadata,
    1 - (documents.embedding <=> query_embedding) AS similarity
  FROM documents
  WHERE 1 - (documents.embedding <=> query_embedding) > match_threshold
  ORDER BY documents.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

RAGアプリケーションの構築

async function askQuestion(question: string) {
  // 1. 関連ドキュメントを検索
  const relevantDocs = await searchSimilar(question, 3);

  // 2. コンテキストを構築
  const context = relevantDocs
    .map(doc => doc.content)
    .join('\n\n');

  // 3. LLMで回答を生成
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: `以下のコンテキストを使って質問に回答してください。
        コンテキストに情報がない場合は「わかりません」と答えてください。

        コンテキスト:
        ${context}`
      },
      { role: 'user', content: question }
    ]
  });

  return {
    answer: response.choices[0].message.content,
    sources: relevantDocs
  };
}

ハイブリッド検索

ベクトル検索とフルテキスト検索を組み合わせます。

CREATE OR REPLACE FUNCTION hybrid_search(
  query_text TEXT,
  query_embedding VECTOR(1536),
  match_count INT
)
RETURNS TABLE (
  id BIGINT,
  content TEXT,
  similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
  SELECT
    d.id,
    d.content,
    (
      0.5 * (1 - (d.embedding <=> query_embedding)) +
      0.5 * ts_rank(to_tsvector('japanese', d.content), plainto_tsquery('japanese', query_text))
    ) AS similarity
  FROM documents d
  WHERE to_tsvector('japanese', d.content) @@ plainto_tsquery('japanese', query_text)
  ORDER BY similarity DESC
  LIMIT match_count;
END;
$$;

Edge Functionsとの統合

// supabase/functions/embed-and-search/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const { query } = await req.json();

  // OpenAI APIでEmbedding生成
  const embeddingRes = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'text-embedding-ada-002',
      input: query
    })
  });

  const { data } = await embeddingRes.json();
  const embedding = data[0].embedding;

  // Supabaseで検索
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  );

  const { data: results } = await supabase.rpc('match_documents', {
    query_embedding: embedding,
    match_threshold: 0.7,
    match_count: 5
  });

  return new Response(JSON.stringify(results), {
    headers: { 'Content-Type': 'application/json' }
  });
});

パフォーマンスのヒント

設定推奨値
IVFFlat listssqrt(行数)
HNSW m16-32
HNSW ef_construction64-128

まとめ

Supabase Vectorは、PostgreSQLの強みを活かしながらベクトル検索を実現します。既存のデータベースにAI機能を追加でき、RAGアプリケーションの構築が容易になります。

← 一覧に戻る