Open-source RAG chat application with multi-provider LLM support
Quick Start · Features · Architecture · Deployment · Contributing
Vortex is a self-hosted RAG (Retrieval-Augmented Generation) application that lets you chat with your documents using any LLM provider. Upload PDFs, ingest URLs, and get accurate answers grounded in your own knowledge bases — with hybrid search, re-ranking, and source citations.
Works out of the box with free models. No API key required to get started.
- Hybrid Search — Combines vector similarity (pgvector cosine distance) with BM25 keyword matching using Reciprocal Rank Fusion (RRF) for better retrieval than vector-only search.
- Re-ranking — After retrieving candidates, results are re-ranked using exact phrase matching, keyword density, and position scoring to surface the most relevant chunks.
- Source Citations — Responses include
[n]citation notation linking back to specific document sources, displayed as interactive badges in the chat UI. - Semantic Chunking — Documents are split into 1500-character chunks with 300-character overlap using sentence-aware separators for better context preservation.
- 5 LLM Providers — OpenAI, Anthropic, Google, xAI (Grok), and OpenRouter. Switch providers and models from the settings page. Free models available via OpenRouter with zero configuration.
- 4 Embedding Providers — Local embeddings via Xenova/Transformers.js (free, no API key), OpenAI (text-embedding-3-small/large), Google (text-embedding-004), or OpenRouter. Embedding model is locked per knowledge base to prevent dimension mismatches.
- Knowledge Base Management — Create multiple knowledge bases, each with its own embedding model. Dashboard shows document counts, conversation counts, and model badges.
- Document Ingestion — Upload PDFs and text files, or ingest content from any URL. Documents are chunked and embedded automatically with rollback on partial failure.
- Streaming Chat — Real-time streaming responses with conversation persistence and full chat history per knowledge base.
- Settings Page — Configure LLM provider, model, temperature, API keys, and default embedding model. API keys are encrypted at rest with AES-256-GCM.
- Auth Middleware — Supabase SSR cookie-based auth with session refresh on every request. Security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy).
- Rate Limiting — Tiered per-endpoint limits (chat: 20/min, ingest: 30/min, general: 60/min) with 429 responses and Retry-After headers.
- Input Validation — Zod schemas on all API route inputs with type-safe error messages.
- KB Ownership Verification — Every knowledge base operation verifies the requesting user owns the resource.
- Soft Deletes — Documents, knowledge bases, and conversations are soft-deleted with
deleted_attimestamps, preserving data integrity. - SSRF Protection — URL ingestion validates against internal/private IP ranges.
- Error Boundary — React error boundary with fallback UI and retry button wrapping all protected routes.
- Confirmation Dialogs — All destructive actions (delete KB, document, conversation) require confirmation.
- Toast Notifications — User-facing error feedback on all API failures.
- Loading Skeletons — Pulsing skeleton placeholders instead of full-page spinners.
- Responsive Design — Works on desktop and mobile with collapsible sidebar navigation.
- Node.js 18+
- A Supabase account (free tier works)
- An OpenRouter API key (optional — free models work without one)
# Clone and install
git clone https://github.com/ankushchhabra02/vortex.git
cd vortex
npm install
# Configure environment
cp .env.example .env.local
# Edit .env.local with your Supabase and OpenRouter credentials- Create a new Supabase project
- In the SQL Editor, run these migrations in order:
supabase/migrations/001_initial_schema.sql— Core tables, RLS policies, vector searchsupabase/migrations/002_multi_provider.sql— Multi-provider support, flexible embeddingssupabase/migrations/003_soft_deletes.sql— Soft delete columns and indexessupabase/migrations/004_hybrid_search.sql— Full-text search index and hybrid search functionsupabase/migrations/005_fix_search_functions.sql— Optimized search thresholds and scoring
- Copy your project URL, anon key, and service role key into
.env.local
The encryption key is used to encrypt API keys stored in the database:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Add the output as ENCRYPTION_KEY in .env.local.
npm run devOpen http://localhost:3000, create an account, and start building knowledge bases.
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Yes | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Yes | Supabase anonymous key |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Supabase service role key |
OPENROUTER_API_KEY |
No | OpenRouter API key (free models work without it) |
ENCRYPTION_KEY |
Yes | 32-byte hex string for encrypting stored API keys |
NEXT_PUBLIC_APP_URL |
No | App URL (defaults to localhost:3000) |
Browser
│
├── Dashboard (/) ← KB cards, recent conversations
├── Chat (/chat/[kbId]) ← Streaming chat + citations + document sidebar
└── Settings (/settings) ← Provider config, API keys, embedding model
│
▼
Middleware ← Auth session refresh, security headers, route protection
│
▼
Next.js API Routes ← Auth + rate limiting + Zod validation on every route
├── /api/chat ← RAG context retrieval → LLM streaming with citations
├── /api/ingest ← Document chunking + embedding (with rollback)
├── /api/settings ← User preferences + encrypted API keys
├── /api/knowledge-bases ← KB CRUD with ownership verification
└── /api/conversations ← Chat history CRUD (soft deletes)
│
▼
RAG Pipeline
├── Hybrid Search ← Vector (pgvector) + Keyword (tsvector) via RRF
├── Re-ranker ← Phrase match + keyword density + position scoring
└── Context Builder ← Top chunks with document titles → cited system prompt
│
▼
Provider Abstraction Layer
├── LLM Factory ← OpenAI, Anthropic, Google, OpenRouter, xAI
├── Embedding Factory ← Xenova (local), OpenAI, Google, OpenRouter
└── Crypto ← AES-256-GCM key encryption
│
▼
Supabase
├── PostgreSQL + pgvector ← Documents, chunks, embeddings, tsvector index
├── Auth (SSR) ← Cookie-based authentication
└── Row Level Security ← Per-user data isolation
- Ingest — Documents are split into 1500-char chunks with 300-char overlap using sentence-aware separators. Each chunk is embedded using the KB's configured embedding model and stored in pgvector.
- Hybrid Search — The user query is embedded and searched via both vector similarity (cosine distance) and BM25 keyword matching (PostgreSQL tsvector). Results are fused using Reciprocal Rank Fusion (RRF) with 70% vector / 30% keyword weighting.
- Re-rank — Top candidates are re-scored using exact phrase matching, keyword density, and match position to surface the most relevant chunks.
- Generate — The re-ranked chunks (with document titles) are injected into the system prompt with citation instructions. The LLM generates a grounded response with
[n]source references.
| Table | Purpose |
|---|---|
knowledge_bases |
User's KB collections with embedding model config |
documents |
Uploaded/ingested content metadata |
document_chunks |
Text chunks with vector embeddings |
conversations |
Chat threads linked to knowledge bases |
messages |
Individual chat messages |
user_settings |
LLM/embedding preferences per user |
user_providers |
Encrypted API keys per provider per user |
All tables have Row Level Security policies. Users can only access their own data.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| UI | React 19, Tailwind CSS v4 |
| Database | Supabase (PostgreSQL + pgvector + tsvector) |
| Auth | Supabase Auth via @supabase/ssr |
| LLM | LangChain (ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI) |
| Embeddings | Transformers.js (local), OpenAI, Google, OpenRouter |
| Document Loading | LangChain (PDF, Cheerio) |
| Validation | Zod |
| Testing | Vitest (80 tests) |
src/
├── app/
│ ├── (protected)/ # Auth-guarded route group
│ │ ├── page.tsx # Dashboard
│ │ ├── chat/[kbId]/page.tsx # Chat interface
│ │ ├── settings/page.tsx # Settings page
│ │ └── layout.tsx # Auth check
│ ├── api/
│ │ ├── chat/ # LLM streaming + message persistence
│ │ ├── ingest/ # Document ingestion
│ │ ├── settings/ # User settings + API key management
│ │ ├── knowledge-bases/ # KB CRUD
│ │ ├── conversations/ # Chat history + recent conversations
│ │ └── documents/ # Document deletion
│ ├── login/ # Public login page
│ ├── signup/ # Public signup page
│ └── auth/callback/ # Supabase auth callback
├── components/
│ ├── dashboard/ # KB cards, recent chats, create dialog
│ ├── settings/ # Provider selector, API key manager, embedding selector
│ ├── layout/ # Shared sidebar navigation
│ ├── chat-interface.tsx # Chat UI with streaming
│ ├── ingest-panel.tsx # Tabbed sidebar (Chats/Documents)
│ └── toast.tsx # Toast notifications
├── lib/
│ ├── providers/ # Multi-provider abstraction
│ │ ├── types.ts # Provider types + model catalogs
│ │ ├── llm-factory.ts # LLM provider factory
│ │ ├── embedding-factory.ts # Embedding provider factory
│ │ └── crypto.ts # AES-256-GCM encryption
│ ├── supabase/ # Supabase clients + auth helpers + ownership verification
│ ├── validations.ts # Zod schemas for all API inputs
│ ├── rate-limit.ts # Tiered rate limiting
│ ├── embeddings.ts # Xenova/Transformers.js embeddings
│ └── rag-service-supabase.ts # RAG pipeline (chunk, embed, hybrid search, re-rank)
└── middleware.ts # Auth session refresh + security headers
- Push to GitHub
- Import the repository on vercel.com
- Add all environment variables from the table above
- Deploy
The app uses Supabase SSR middleware for auth. All environment variables must be set in Vercel's dashboard.
Any platform that runs Node.js 18+ works. Build with npm run build and start with npm start.
- API keys encrypted at rest (AES-256-GCM with random IV per encryption)
- Auth middleware on every request (Supabase SSR session refresh + route protection)
- Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- Rate limiting on all endpoints (tiered: chat 20/min, ingest 30/min, general 60/min)
- Zod input validation on all API route inputs
- KB ownership verification before any data access
- Row Level Security on all database tables
- URL validation with SSRF protection (blocks internal/private IPs)
- File type and size validation (10MB limit, PDF/TXT/MD/HTML only)
- Soft deletes filtered from all queries (no data leakage from deleted records)
"Unauthorized" errors — Verify your Supabase keys are correct and you're logged in. Check the browser console for auth errors.
Embeddings not generating — Ensure Node.js 18+. Try clearing the cache with rm -rf .next and restarting.
Chat not responding — Check your LLM provider settings. If using a paid model, verify the API key is set and valid in Settings.
Database errors after update — Make sure you've run all 5 SQL migrations in order (001 through 005). Migration 002 drops and recreates the embedding column — existing embeddings will need to be re-ingested.
Generic/irrelevant chat responses — Ensure migration 005 has been applied. It fixes search thresholds and hybrid scoring. Also verify the KB has documents ingested (check the sidebar document list).
Contributions are welcome! Please read our Contributing Guide for details on the development workflow, code guidelines, and how to submit pull requests.
We also ask that all participants follow our Code of Conduct.
If you discover a security vulnerability, please follow our Security Policy. Do not open a public issue for security vulnerabilities.
MIT
Built with Next.js, Supabase, LangChain, and Transformers.js.

