Documentation

Supabase + ModelRiver

Full-stack AI applications with Supabase. Store embeddings via pgvector, query with semantic search, and generate answers through ModelRiver.

Overview

Supabase is an open-source Firebase alternative with built-in pgvector support. Combined with ModelRiver, you can store AI-generated embeddings alongside your regular application data, query them with semantic search, and generate answers: all inside one platform.


Quick start

Install dependencies

Bash
pip install supabase openai

Enable pgvector in Supabase

Run this in the Supabase SQL Editor:

SQL
1-- Enable the pgvector extension
2create extension if not exists vector;
3 
4-- Create documents table with embedding column
5create table documents (
6 id bigserial primary key,
7 content text not null,
8 metadata jsonb default '{}',
9 embedding vector(1536), -- Match your embedding model's dimensions
10 created_at timestamptz default now()
11);
12 
13-- Create an index for fast similarity search
14create index on documents using ivfflat (embedding vector_cosine_ops) with (lists = 100);

Setup clients

PYTHON
1from supabase import create_client
2from openai import OpenAI
3 
4# Supabase
5supabase = create_client(
6 "https://YOUR_PROJECT.supabase.co",
7 "YOUR_SUPABASE_ANON_KEY",
8)
9 
10# ModelRiver
11ai = OpenAI(
12 base_url="https://api.modelriver.com/v1",
13 api_key="mr_live_YOUR_API_KEY",
14)

Store embeddings

PYTHON
1def store_document(content: str, metadata: dict = {}):
2 """Embed content via ModelRiver and store in Supabase."""
3 # Generate embedding
4 response = ai.embeddings.create(
5 model="my-embedding-workflow",
6 input=[content],
7 )
8 embedding = response.data[0].embedding
9 
10 # Insert into Supabase
11 supabase.table("documents").insert({
12 "content": content,
13 "metadata": metadata,
14 "embedding": embedding,
15 }).execute()
16 
17# Store documents
18store_document("ModelRiver routes AI requests across providers.", {"source": "docs"})
19store_document("Workflows configure provider and fallback settings.", {"source": "docs"})
20store_document("Structured outputs guarantee JSON compliance.", {"source": "docs"})

Semantic search + RAG

Create a Supabase RPC function for similarity search:

SQL
1-- Supabase SQL Editor
2create or replace function match_documents(
3 query_embedding vector(1536),
4 match_count int default 5,
5 match_threshold float default 0.7
6)
7returns table (id bigint, content text, metadata jsonb, similarity float)
8language plpgsql
9as $$
10begin
11 return query
12 select
13 documents.id,
14 documents.content,
15 documents.metadata,
16 1 - (documents.embedding <=> query_embedding) as similarity
17 from documents
18 where 1 - (documents.embedding <=> query_embedding) > match_threshold
19 order by documents.embedding <=> query_embedding
20 limit match_count;
21end;
22$$;

Python RAG function

PYTHON
1def ask(question: str, top_k: int = 3) -> str:
2 """Semantic search + AI answer generation."""
3 # Embed the question
4 query_embedding = ai.embeddings.create(
5 model="my-embedding-workflow",
6 input=[question],
7 ).data[0].embedding
8 
9 # Search Supabase
10 results = supabase.rpc("match_documents", {
11 "query_embedding": query_embedding,
12 "match_count": top_k,
13 }).execute()
14 
15 # Build context
16 context = "\n\n".join([r["content"] for r in results.data])
17 
18 # Generate answer
19 response = ai.chat.completions.create(
20 model="my-chat-workflow",
21 messages=[
22 {"role": "system", "content": f"Answer based on context:\n\n{context}"},
23 {"role": "user", "content": question},
24 ],
25 )
26 
27 return response.choices[0].message.content
28 
29print(ask("How does ModelRiver handle failover?"))

JavaScript / Next.js

TYPESCRIPT
1import { createClient } from "@supabase/supabase-js";
2import OpenAI from "openai";
3 
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_ANON_KEY!,
7);
8 
9const ai = new OpenAI({
10 baseURL: "https://api.modelriver.com/v1",
11 apiKey: process.env.MODELRIVER_API_KEY!,
12});
13 
14async function ask(question: string): Promise<string> {
15 const { data: embeddingData } = await ai.embeddings.create({
16 model: "my-embedding-workflow",
17 input: [question],
18 });
19 
20 const { data: docs } = await supabase.rpc("match_documents", {
21 query_embedding: embeddingData[0].embedding,
22 match_count: 3,
23 });
24 
25 const context = docs.map((d: any) => d.content).join("\n\n");
26 
27 const response = await ai.chat.completions.create({
28 model: "my-chat-workflow",
29 messages: [
30 { role: "system", content: `Answer based on context:\n\n${context}` },
31 { role: "user", content: question },
32 ],
33 });
34 
35 return response.choices[0].message.content!;
36}

Best practices

  1. Use IVFFlat or HNSW indexes for large datasets (>10k rows)
  2. Match embedding dimensions: Ensure your table's vector size matches your model
  3. Store original text: Keep content alongside embeddings for retrieval
  4. Use RLS policies: Supabase Row Level Security works with the documents table
  5. Monitor embedding costs in Request Logs

Next steps