Dashboard

Semantic search

RAG-style doc search: store OpenAI embeddings in pgvector and query with match_documents.

What you'll build

A documents table with vector(1536), a match_documents RPC, and a framework search handler using OpenAI + voltbase-js.

1. Schema + match function

Run in SQL / Migrations (see also Vectors):

semantic.sql
create extension if not exists vector;

create table documents (
  id uuid primary key default gen_random_uuid(),
  content text not null,
  embedding vector(1536) not null
);

create or replace function match_documents(
  query_embedding vector(1536),
  match_threshold float default 0.7,
  match_count int default 10
)
returns table (id uuid, content text, similarity float)
language sql stable
as $$
  select
    documents.id,
    documents.content,
    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;
$$;

2. HNSW index

index.sql
create index documents_embedding_hnsw
  on documents
  using hnsw (embedding vector_cosine_ops);

3. Seed embeddings

seed.ts
import { createClient } from 'voltbase-js';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const admin = createClient(projectUrl, serviceRoleKey);

const texts = [
  'Reset your password from Account → Security.',
  'Billing invoices are emailed on the 1st of each month.',
];

for (const content of texts) {
  const embed = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: content,
  });
  await admin.from('documents').insert({
    content,
    embedding: embed.data[0]!.embedding,
  });
}

4. Query from your app

Pick your framework:

app/search/actions.ts
'use server';

import { createClient } from 'voltbase-js';
import OpenAI from 'openai';

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

export async function semanticSearch(query: string) {
  const embed = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: query,
  });
  const query_embedding = embed.data[0]!.embedding;

  const voltbase = createClient(
    process.env.NEXT_PUBLIC_VOLTBASE_URL!,
    process.env.NEXT_PUBLIC_VOLTBASE_ANON_KEY!,
  );

  return voltbase.rpc('match_documents', {
    query_embedding,
    match_threshold: 0.7,
    match_count: 8,
  });
}