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,
});
}