Supabase Easy RAG
A production-ready, ultra-fast Hybrid RAG engine built natively on PostgreSQL & Supabase. Combines dense vector search (HNSW) and sparse BM25 full-text search with Reciprocal Rank Fusion (RRF), parent-context expansion, and database-native multi-tenant RLS isolation.
🚀 Quick Start
1. Installation
pip install supabase-easy-rag
2. Apply Database Migrations
Option A: Via Supabase CLI (Recommended)
supabase db push
Option B: Via SQL Editor or CLI Export
easy-rag init-sql --output ./migrations --dimensions 1536
Apply 01_schema.sql and 02_functions.sql in the Supabase SQL Editor.
3. Configure Environment Variables (.env)
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"
SUPABASE_ANON_KEY="your-anon-key"
# Embedding Provider (OpenAI or Azure OpenAI)
OPENAI_API_KEY="sk-..."
4. 30-Second Python Example
from supabase_easy_rag import EasyRagClient
# Pass the end-user JWT for automatic RLS tenant isolation
client = EasyRagClient(user_jwt="eyJhbGci...")
# 1. Sync markdown knowledge base in parallel (SHA-256 incremental)
client.sync_directory("./docs", max_workers=8)
# 2. Search with Hybrid RRF & Parent-Context Expansion
results = client.search_hybrid(
query="PostgreSQL connection pooling guidelines",
match_count=5,
candidate_count=50,
rrf_k=60,
expand_context="section", # "section" or "document"
)
for item in results:
print(f"[{item.final_score:.4f}] {item.document_title} > {item.section_title}")
print(item.effective_text[:200] + "...\n")
📊 Evaluation & Latency Benchmarks
Empirical evaluation against the Google Research TyDi QA multilingual gold-standard benchmark (5,077 natural queries, 4,488 authentic Wikipedia articles across 11 typologically diverse languages) and direct execution on PostgreSQL 16 + pgvector:
1. Retrieval Accuracy & Quality (TyDi QA Global Benchmark)
| Benchmark Metric | Score | Description |
|---|---|---|
| Document Hit Rate @ 1 (Top-1) | 88.46% | Ground-truth document ranked #1 (Strict Document Match) |
| Document Hit Rate @ 3 (Top-3) | 91.61% | Ground-truth document in Top-3 chunks |
| Document Hit Rate @ 5 (Top-5) | 92.34% | Ground-truth document in Top-5 chunks |
| Document Hit Rate @ 10 (Top-10) | 92.75% | Ground-truth document in Top-10 chunks |
| Document MRR (Mean Reciprocal Rank) | 0.9012 | Average reciprocal rank on document retrieval |
| Answer Span Recall @ 5 | 91.90% | Exact fact-answer span contained in Top-5 chunks |
2. Execution Latency & Ingestion Throughput
| Component | Metric | Value | Details |
|---|---|---|---|
| Hybrid SQL Query Latency | Mean | 21.34 ms | |
| p50 | 21.42 ms | ||
| p95 | 22.09 ms | ||
| Ingestion & Verification | Throughput | 651.5 docs/sec | 4,488 documents verified via SHA-256 in 6.89s (16 workers) |
3. Multilingual Breakdown Across 11 Languages
| Language | Queries | Doc Hit @ 1 | Doc Hit @ 5 | Doc MRR | Answer Recall @ 5 |
|---|---|---|---|---|---|
| Russian | 828 | 91.5% | 93.8% | 0.926 | 93.2% |
| Finnish | 920 | 90.0% | 93.0% | 0.913 | 92.9% |
| Arabic | 964 | 89.2% | 92.8% | 0.908 | 92.0% |
| Telugu | 668 | 89.1% | 92.8% | 0.906 | 93.1% |
| Indonesian | 499 | 89.4% | 91.8% | 0.903 | 91.6% |
| Swahili | 475 | 87.4% | 92.6% | 0.895 | 91.6% |
| English | 278 | 87.1% | 89.9% | 0.883 | 91.0% |
| Japanese | 139 | 82.0% | 89.2% | 0.854 | 88.5% |
| Bengali | 111 | 76.6% | 91.9% | 0.834 | 90.1% |
| Korean | 194 | 75.8% | 85.0% | 0.795 | 83.0% |
⚙️ Engineering & Security Highlights
- ☁️ Live Cloud Supabase Tested: Validated against live Supabase Cloud (PostgreSQL 17.6 in
eu-north-1). See CLOUD_EVAL_REPORT.md. - 🐘 PostgreSQL 16 + pgvector CI: GitHub Actions integration tests against live
pgvector/pgvector:pg16containers with real HNSW index scans, GIN full-text index scans, and database triggers. - 🔒 Strict RLS Tenant Isolation: Zero cross-tenant data leakage across vector, FTS, and hybrid retrieval with PostgreSQL
auth.uid(). - 🎯 Filtered ANN Recall Preservation: Candidate oversampling (
candidate_count) and iterative scan (hnsw.iterative_scan = 'relaxed_order') prevent candidate starvation under metadata/tenant filters. - 📖 Parent-Context Expansion: Searches granular 400-token chunks for precision, returns full parent sections for LLM context.
- 🌍 Multilingual FTS & Fallbacks: Native dictionary stemming and fallback support across 11 languages (English, Russian, Arabic, Finnish, and more).
💻 Python SDK Usage
1. Hybrid Search with Parent-Context Expansion
from supabase_easy_rag import EasyRagClient
# Pass the end-user JWT for automatic RLS tenant isolation
client = EasyRagClient(user_jwt="eyJhbGci...")
# Search by dense chunk, expand to full parent section for LLM context
results = client.search_hybrid(
query="PostgreSQL connection pooling guidelines",
match_count=5,
candidate_count=50,
rrf_k=60,
expand_context="section", # "section" or "document"
)
for item in results:
print(f"[{item.final_score:.4f}] {item.document_title} > {item.section_title}")
print(item.effective_text[:200] + "...")
2. High-Throughput Parallel Directory Ingestion
# Sync a directory of Markdown documents with 8 parallel workers
sync_stats = client.sync_directory(
directory_path="./knowledgebase_docs",
batch_size=30,
max_workers=8,
enable_chunking=True,
chunk_size=800,
chunk_overlap=100,
)
print(f"Synced {sync_stats['total_files']} files in parallel.")
3. Asynchronous Client (FastAPI / Async Runtimes)
from supabase_easy_rag import AsyncEasyRagClient
async_client = AsyncEasyRagClient()
results = await async_client.search_hybrid(
query="Distributed consensus election timeouts",
match_count=3,
)
4. Dynamic Vector Dimensions & Partial Index Management
PostgreSQL chunks.embedding uses unconstrained VECTOR with partial HNSW indexes partitioned by (tenant_id, dimension):
# List active vector indexes across tenants and dimensions
indexes = client.indexes.list_vector_indexes()
# Ensure an HNSW index for Qwen3 1024-dim or OpenAI 1536-dim
client.indexes.ensure_vector_index(dimension=1024, m=16, ef_construction=64)
# Drop an obsolete vector index
client.indexes.drop_vector_index(dimension=768)
5. Apple Silicon MLX Local Embedding Provider
Accelerated on-device embeddings with Apple Metal and INT8/BF16 quantization:
from supabase_easy_rag.providers.mlx_provider import MlxQwenEmbeddingProvider
# Run Qwen3-Embedding-0.6B locally on Apple Silicon
mlx_provider = MlxQwenEmbeddingProvider(
model_path_or_repo="Qwen/Qwen3-Embedding-0.6B",
quantize_int8=True,
use_bf16=True,
)
client = EasyRagClient(embedding_provider=mlx_provider)
🧪 Local Docker & CI Testing
Run the full integration test suite (89 tests) against a local PostgreSQL container with pgvector:
# 1. Start local Postgres test container:
docker compose -f docker-compose.test.yml up -d
# 2. Run the complete test suite:
POSTGRES_URL="postgresql://postgres:postgres@localhost:5432/postgres" pytest tests/ -v
🛠️ CLI Commands
# Export migration files
easy-rag init-sql --output ./migrations
# Sync directory of documents
easy-rag sync ./docs --workers 8
# Execute test query with diagnostics & optional model filter
easy-rag query "What is distributed erasure coding?" --mode hybrid --count 5 --model "Qwen/Qwen3-Embedding-0.6B"
# Manage dynamic vector indexes
easy-rag list-indexes [--tenant-id <UUID>]
easy-rag ensure-index 1024 [--m 16] [--ef-construction 64]
easy-rag drop-index 768
# Manage backend API access tokens
easy-rag create-token "Production Ingestion Worker"
easy-rag list-tokens
⚙️ Architecture & Implementation Details
- Dynamic Vector Dimensions:
knowledgebase.chunks.embeddingis stored as unconstrainedVECTOR. Partial HNSW indexes (idx_kb_chunks_hnsw_g_<dim>andidx_kb_chunks_hnsw_t_<tenant>_<dim>) provide maximum query performance without dimension constraint errors. - Two-Stage Hybrid Fusion: Independent candidate pools are retrieved via indexed scans (
HNSWfor vector distance,GINfor full-text match) and fused using Reciprocal Rank Fusion: $$RRF_Score = \frac{w_v}{k + rank_v} + \frac{w_t}{k + rank_t}$$ This avoids arbitrary score normalization issues between cosine similarities and BM25/FTS weights. - Iterative Graph Traversal: Employs
hnsw.iterative_scan = 'relaxed_order'and dynamicp_ef_searchto prevent candidate starvation when applying strict metadata or tenant filters. - Weighted Lexical Search: Uses PostgreSQL native
setweight()indexing where document titles receive WeightA($1.0$), section headings receive WeightB($0.4$), and chunk text receives WeightD($0.1$). - Multi-Tenant Security: Enforces database-level isolation via Supabase Auth (
auth.uid()) using non-recursive Row-Level Security (RLS) policies on documents and chunks.
📄 License
Distributed under the MIT License. See LICENSE for details.
Metadata
Release files for supabase-easy-rag 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| supabase_easy_rag-0.5.0.tar.gz | 225.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| supabase_easy_rag-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 277.3 kB
Release files / supabase_easy_rag-0.5.0.tar.gz
| Download URL | supabase_easy_rag-0.5.0.tar.gz |
|---|---|
| Size | 225.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6e0cb52d820d4bf15d818ab7ef94bc6650a34fc21cbdae2740d86c83e3c95777
|
|
BLAKE2b-256 checksum How to use checksums |
54286357a8b8f5a749a1b2c9950de9b1f04c59fa6cdb3b6ffd523a348b640328
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / supabase_easy_rag-0.5.0-py3-none-any.whl
| Download URL | supabase_easy_rag-0.5.0-py3-none-any.whl |
|---|---|
| Size | 52.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d8e0dcf410a1c2ac15b1aa754b053c193e9c14aa2955cfc01b690b78b2064f8d
|
|
BLAKE2b-256 checksum How to use checksums |
fc185fc0a026efe4ea90f41a6a14c83d1f6874f0c2556eb5216048833a22ef73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|