Pulsar
docs
← back to home

AI & vector search

Every Pulsar Deploy Postgres instance is AI-ready out of the box. The three things an AI app needs live in one backend: pgvector for embeddings and semantic search, edge functions that call any model, and storage for the documents you index. No separate vector database, no extra services to wire up.

What's built in

pgvector        enabled on every Postgres instance (vector column type + <=> distance)
edge functions  run server-side code that calls any embedding/LLM API with your key
storage         S3-compatible buckets for the source documents you embed
REST + RLS      serve results to your app, gated by row-level security

You bring your own model key (OpenAI, Anthropic, Cohere, a local model — anything with an HTTP API). Store it as an instance secret; your edge functions read it from the environment. Pulsar never sees your prompts or data.

1. Create a table with a vector column

pgvector is already installed — no CREATE EXTENSION needed. Pick the dimension your embedding model outputs (1536 for OpenAI text-embedding-3-small, 1024 for many others):

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

2. Generate embeddings — built in

The fastest path is Pulsar's managed embeddings endpoint: one call, no provider SDK, service-key only (keep it server-side). It returns one vector per input string.

POST /v1/<instance-id>/ai/embed
  apikey: <service_role key>
  { "input": "cats are great pets" }

-> { "model": "text-embedding-3-small", "dimensions": 1536,
     "embeddings": [[0.0123, -0.0456, ...]] }

Prefer your own provider account? Call it directly from an edge function instead — same result, your key, close to the database:

// functions/embed  — POST { content }
const key = process.env.OPENAI_API_KEY;           // set as an instance secret
const { content } = JSON.parse(process.env.PULSAR_REQUEST_BODY || '{}');

const res = await fetch('https://api.openai.com/v1/embeddings', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + key },
  body: JSON.stringify({ model: 'text-embedding-3-small', input: content }),
});
const { data } = await res.json();
const embedding = '[' + data[0].embedding.join(',') + ']';

// store it (service_role key — server-side only)
await fetch(PULSAR_URL + '/rest/documents', {
  method: 'POST',
  headers: { apikey: SERVICE_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ content, embedding }),
});
console.log(JSON.stringify({ ok: true }));

3. Semantic search by distance

Embed the user's query the same way, then order by vector distance. <=> is cosine distance (smaller = more similar); <-> is L2 and <#> is inner product:

select id, content
from documents
order by embedding <=> $1     -- $1 = the query embedding
limit 5;

This exact pattern is verified on Pulsar's managed Postgres — a query embedding for “pets” ranks pet documents first and unrelated ones last, by cosine distance.

4. Retrieval-augmented generation (RAG)

Put the pieces together in one edge function: embed the question, pull the nearest documents as context, and ask your LLM to answer from that context.

// 1. embed the question  -> queryEmbedding
// 2. fetch top matches as context
const rows = await sql`
  select content from documents
  order by embedding <=> ${queryEmbedding} limit 5`;
const context = rows.map(r => r.content).join('\n---\n');

// 3. answer from context with any chat model
const answer = await callYourLLM({
  system: 'Answer only from the provided context.',
  user: 'Context:\n' + context + '\n\nQuestion: ' + question,
});
console.log(JSON.stringify({ answer }));

5. Add an index for scale

Exact search is fine to thousands of rows. Past that, add an approximate index — HNSW gives the best recall/speed trade-off:

create index on documents
using hnsw (embedding vector_cosine_ops);

AI copilots in the dashboard

The SQL assistantis live: open the SQL editor, describe a query in plain English (“top 10 users by signup date”), and it writes the Postgres SQL against your actual schema. It needs the platform to have an OpenAI key configured; without one it stays disabled rather than guessing. More copilots — a build-error fixer and a log chat — are on the roadmap.