Supabase + pgvector 向量資料庫配置與 RAG 系統完整教學

Supabase 結合 pgvector 擴充功能,讓您可以直接在 PostgreSQL 中儲存與搜尋向量資料,實現高效能的 RAG(檢索增強生成)系統。本文將逐步帶您完成向量資料庫配置,並展示如何運用語意搜尋提升 AI 應用的精準度。

什麼是 pgvector 與為何選擇 Supabase

pgvector 是 PostgreSQL 的向量搜尋擴充功能,支援向量嵌入(embedding)的儲存、索引建立與相似性搜尋。Supabase 作為開源的 Firebase 替代方案,內建 PostgreSQL 完整功能,可直接啟用 pgvector 擴充,無需額外部署向量資料庫。

使用 Supabase 的優勢包括:統一的資料庫架構、RESTful API 自動生成、Row Level Security 安全性控制,以及與 Edge Functions 的無縫整合。這讓 RAG 系統的開發流程更加簡潔。

步驟一:啟用 pgvector 擴充功能

在 Supabase 控制台或透過 SQL 編輯器執行以下指令啟用 pgvector:

-- 建立 pgvector 擴充功能
CREATE EXTENSION IF NOT EXISTS vector;

-- 驗證版本
SELECT extversion FROM pg_extension WHERE extname = 'vector';

接著建立儲存向量資料的資料表,並指定向量維度(這裡以 OpenAI text-embedding-3-small 的 1536 維為例):

-- 建立文件資料表
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536),
  metadata JSONB,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 建立 HNSW 索引以加速向量搜尋
CREATE INDEX ON documents 
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

步驟二:產生向量嵌入並儲存

您需要將文字內容轉換為向量嵌入。以下是使用 OpenAI API 產生嵌入的範例程式碼:

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

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// 將文字轉為向量嵌入
async function getEmbedding(text) {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: text
  });
  return response.data[0].embedding;
}

// 儲存文件與向量
async function storeDocument(content, metadata = {}) {
  const embedding = await getEmbedding(content);
  
  const { data, error } = await supabase
    .from('documents')
    .insert({
      content,
      embedding,
      metadata
    })
    .select();
  
  if (error) throw error;
  return data;
}

// 範例:儲存技術文件
await storeDocument(
  'Supabase 是開源的 Firebase 替代方案,支援 PostgreSQL 資料庫',
  { source: 'tech-docs', category: 'database' }
);

步驟三:實現語意相似性搜尋

向量搜尋的核心是找出與查詢最相似的文件。pgvector 提供三種距離運算方式:歐氏距離(<#>)、內積(<#>)、餘弦相似度(<=>)。以下範例使用餘弦相似度:

-- 語意搜尋函數
async function semanticSearch(query, topK = 5) {
  const queryEmbedding = await getEmbedding(query);
  
  const { data, error } = await supabase
    .from('documents')
    .select('id, content, metadata')
    .select(
      'embedding',
      { count: 'exact', head: false }
    )
    .order(
      'embedding',
      { ascending: false, queryEmbedding: queryEmbedding }
    )
    .limit(topK);
  
  if (error) throw error;
  return data;
}

// 執行搜尋
const results = await semanticSearch('什麼是開源的資料庫解決方案?');

Supabase 也支援直接使用 SQL 進行向量搜尋:

-- 直接 SQL 搜尋
SELECT id, content, 
  (embedding <=> '[查詢向量]'::vector) AS similarity
FROM documents
ORDER BY similarity ASC
LIMIT 5;

步驟四:整合 RAG 系統實作

完整的 RAG 流程是:接收使用者問題 → 向量搜尋相關文件 → 將文件內容作為上下文回饋給 LLM。以下是完整實作:

async function ragQuery(userQuestion) {
  // 1. 取得問題的向量表示
  const questionEmbedding = await getEmbedding(userQuestion);
  
  // 2. 向量搜尋取得相關文件
  const { data: documents } = await supabase.rpc(
    'match_documents',
    {
      query_embedding: questionEmbedding,
      match_threshold: 0.7,
      match_count: 3
    }
  );
  
  // 3. 組合上下文
  const context = documents
    .map(doc => doc.content)
    .join('\n\n');
  
  // 4. 呼叫 LLM 生成回答
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: '請根據以下參考資料回答問題。參考資料:\n' + context
      },
      {
        role: 'user',
        content: userQuestion
      }
    ]
  });
  
  return response.choices[0].message.content;
}

// 使用 RAG 系統
const answer = await ragQuery('Supabase 有哪些主要功能?');
console.log(answer);

其中 match_documents 是透過 Supabase Edge Function 或 PostgreSQL 函數建立的向量搜尋包裝函數,可參考 Supabase 官方提供的 pgvector 教學進行設定。

總結

透過 Supabase 與 pgvector 的整合,您可以在單一資料庫中同時管理結構化資料與向量資料,大幅簡化 RAG 系統的架構。關鍵步驟包含:啟用 pgvector 擴充、建立向量欄位與索引、產生文字嵌入、實作語意搜尋,最後將搜尋結果傳入 LLM 生成回答。此架構具有高度擴展性,適合各種 AI 應用場景。