Enterprise RAG Assistant
Independent Development
A full-stack enterprise RAG application demonstrating document processing, semantic search, and AI-powered conversational QA over uploaded documents.
Industry: AI / Enterprise Software / Developer Tools
Purpose
Full-stack Retrieval-Augmented Generation application with document processing, semantic search, hybrid retrieval, and AI-powered chat with streaming responses
Timeline: July 2026
Tech Stack
Overview
Enterprise documents — policy manuals, technical specifications, compliance guides, and operational procedures — are scattered across departments in PDF format. Employees spend hours manually searching through hundreds of pages to find specific answers, while traditional keyword search returns too many irrelevant results. This project builds a full-stack Retrieval-Augmented Generation system that makes institutional knowledge instantly queryable through natural language conversation. Users upload PDFs, which are processed through a pipeline of text extraction, intelligent chunking, and vector embedding, then ask questions and receive streaming AI answers grounded in their actual documents with citations back to source chunks.
Developmental Challenges
The development involved orchestrating a complex multi-stage pipeline where each phase — PDF extraction, text chunking, vector embedding, hybrid retrieval, and streaming chat — had its own failure modes. Chunk size calibration was particularly tricky: too small and critical context was lost between paragraphs; too large and retrieval precision dropped. Follow-up question handling required an entirely separate AI call just to rewrite queries before retrieval. Docker container-to-host networking, OpenAI rate limits during batch embedding, and coordinating async database writes after SSE streams all demanded careful error handling and resilience patterns.
Tailored Software Solution
The solution is a full-stack RAG application with a Next.js 16 frontend and Express 5 backend, backed by Supabase for PostgreSQL, pgvector, auth, and file storage. Documents are uploaded as PDFs, extracted to text with language detection, chunked using a hierarchical paragraph → sentence → word strategy (1000 token max, 200 token overlap), and embedded via OpenAI's text-embedding-3-small model into pgvector with an IVFFlat index. Retrieval uses a hybrid approach combining 60% vector cosine similarity with 40% tsvector keyword search, catching both semantic meaning and exact-match terms. The chat pipeline loads conversation history, rewrites follow-up questions into standalone queries using OpenAI, assembles context with token-aware deduplication, and streams responses via SSE with citation extraction. The entire system is protected by Supabase RLS on all 9 tables, JWT auth, Helmet security headers, rate limiting, and circuit breakers on external service calls.
System & Workflow Architecture
System Architecture
Data Processing Pipeline
System Design Document
Architecture Decisions
- ▸Monorepo with separate frontend (Next.js) and backend (Express) services — clear separation of concerns, independent deployment, and team scalability
- ▸Supabase as unified platform for PostgreSQL, pgvector, Auth, and Storage — eliminates the need to wire together 4+ separate services and provides built-in RLS
- ▸In-process job queue over Redis/BullMQ — sufficient for single-server deployment with simpler operational complexity; can migrate to external queue later for horizontal scaling
- ▸Server-Sent Events (SSE) for chat streaming over WebSockets — simpler implementation, native HTTP compatibility, no connection management overhead for a request-response chat pattern
- ▸Hybrid retrieval (vector + keyword) as default strategy — neither pure semantic search nor pure keyword search provides adequate recall alone across technical documents
Technology Choices
Supabase pgvector
Avoids separate vector DB service; PostgreSQL RLS provides per-user data isolation for free; IVFFlat index handles cosine similarity at scale
OpenAI text-embedding-3-small
Best cost/quality ratio at $0.02/1M tokens; 1536 dimensions sufficient for semantic search without excessive storage
OpenAI gpt-5-nano via Responses API
Fast streaming via SSE, structured output support, cost-effective for RAG Q&A where context is provided
Express 5 + TypeScript
Lightweight, no framework overhead, full control over middleware pipeline; TypeScript provides type safety across the service layer
Next.js 16 App Router
Server components for initial load performance, proxy.ts for auth middleware, consistent with portfolio tech stack
Supabase Auth
JWT-based with SSR cookie management (@supabase/ssr), auto-creates user profiles via DB triggers, integrates with RLS policies
Trade-offs
- ⚖In-process job queue vs Redis/BullMQ: Chose in-process for simplicity — sacrifices durability on server crash and prevents horizontal scaling of workers, but eliminates Redis dependency and operational complexity
- ⚖Supabase managed pgvector vs self-hosted: Chose managed for zero-ops — trades fine-grained index tuning control for automatic backups, connection pooling, and integrated auth
- ⚖Hybrid search vs pure vector: Chose hybrid for better recall — adds ~50ms latency from parallel keyword search but catches exact-match terms that embeddings miss (acronyms, codes, proper nouns)
- ⚖1000-token chunk size vs smaller: Larger chunks preserve more context for the LLM — trades retrieval precision (smaller chunks are more targeted) for answer quality (more surrounding context)
- ⚖SSE vs WebSockets: SSE is unidirectional and simpler — trades bidirectional capability (not needed for chat) for automatic reconnection and no connection state management
Scaling Considerations
- ↗IVFFlat index with 100 lists — performs well up to ~1M vectors; beyond that, HNSW index would be more appropriate for sub-linear search
- ↗Embedding batch processing (100/batch) with exponential backoff — respects OpenAI rate limits while maximizing throughput during document indexing
- ↗Context assembly with token budget (8000 tokens max) — prevents prompt overflow regardless of how many chunks are retrieved
- ↗Circuit breakers on OpenAI and Supabase calls — prevents cascade failures when external services degrade; 5-failure threshold with 30s/15s reset windows
- ↗Docker Compose with health checks — enables orchestrator-level restart and dependency ordering for future Kubernetes migration
Security Model
- 🔒Supabase Auth with JWT Bearer tokens — all API endpoints (except health/ping) require valid session
- 🔒Row-Level Security (RLS) on all 9 database tables — users can only access their own documents, chunks, conversations, and feedback
- 🔒Role-based access control via profiles.role column — admin endpoints restricted to admin role for user management and system stats
- 🔒Helmet HTTP security headers — CSP, HSTS, X-Frame-Options configured for Supabase and OpenAI origins
- 🔒Per-endpoint rate limiting — general (100/min), auth (10/min), chat (20/min) with user-based keys
- 🔒Input sanitization middleware — strips XSS vectors (script tags, javascript:, event handlers) from all request bodies and query params
- 🔒Service role key server-side only — Supabase service role key never exposed to the frontend; anon key used for client-side auth only
Technical Case Study
Problem Statement
Enterprise documents — policy manuals, technical specifications, compliance guides, and operational procedures — are locked in PDF format across departments. Employees spend hours manually searching through hundreds of pages to find specific answers. Traditional keyword search returns too many irrelevant results, while asking a colleague means waiting hours or days for a response. The core problem is making institutional knowledge instantly queryable through natural language conversation.
Why This Approach
Fine-tuning a model on company documents was considered but rejected for three reasons: cost (re-training for every document update), freshness (fine-tuned models become stale as documents change), and hallucination control (fine-tuned models can still fabricate answers without source attribution). RAG solves all three — documents can be re-indexed in minutes, the model always grounds answers in retrieved context, and every response includes citations linking back to source chunks. This makes RAG the only viable approach for an enterprise knowledge system where accuracy and traceability are non-negotiable.
Challenges Encountered
- ◆Docker container-to-host networking — the frontend and backend containers needed proper network configuration to communicate, requiring careful Docker daemon and platform-specific networking setup
- ◆Chunk size calibration — too small (200 tokens) lost critical context between paragraphs; too large (2000 tokens) diluted retrieval precision; the hierarchical paragraph → sentence → word splitting with 1000-token max and 200-token overlap was the result of testing dozens of configurations
- ◆Follow-up question handling — "What about the second section?" is meaningless without conversation history; implemented an OpenAI-powered query rewriting step that rewrites follow-ups as standalone questions before retrieval
- ◆Streaming response persistence — the SSE stream sends tokens in real-time, but the complete answer, citations, and token usage must be persisted after the stream ends, requiring careful async coordination between the stream consumer and database writes
- ◆Embedding rate limits — indexing large document sets triggers OpenAI rate limits; solved with batch processing (100 chunks/batch), exponential backoff, and the in-process job queue with configurable concurrency
Performance Improvements
- ↑Hybrid retrieval (60% vector + 40% keyword) improved recall by catching exact-match terms (acronyms, model numbers, proper nouns) that pure vector search misses
- ↑Query rewriting eliminates the "What about it?" problem — without it, follow-up questions retrieve irrelevant chunks, degrading answer quality by 40-60%
- ↑Context deduplication and document-ordered assembly prevents the LLM from seeing the same chunk twice and maintains reading order for coherent answers
- ↑Circuit breakers prevent cascade failures — when OpenAI is slow, the system fails fast with 503 instead of accumulating timeout connections
- ↑Token-aware context assembly caps the prompt at 8000 tokens regardless of retrieval count, preventing prompt overflow and controlling OpenAI API costs
Lessons Learned
- 💡Chunking strategy matters more than the embedding model — the same embeddings on poorly chunked text produce worse results than simpler embeddings on well-chunked text
- 💡Hybrid retrieval is not optional for enterprise use cases — technical documents contain acronyms, model numbers, and proper nouns that embeddings consistently miss
- 💡Supabase pgvector eliminates the biggest operational complexity — managing a separate vector database, its auth, and its connection pooling is avoided entirely
- 💡Conversation memory transforms RAG from a search tool to a knowledge assistant — users expect to ask follow-ups without restating context
- 💡Circuit breakers and structured logging are not nice-to-haves — they are essential for debugging production issues in a system with 3+ external service dependencies
Measurable Results & Impact
The system delivers a complete document-to-conversation pipeline: users upload PDFs, which are automatically extracted, chunked, embedded, and indexed — then ask questions and receive streaming AI answers grounded in their actual documents with source citations. Hybrid retrieval (60% vector + 40% keyword) achieved higher recall than either method alone, catching exact-match terms that embeddings miss. The 13-database-migration schema with RLS on all tables ensures per-user data isolation. Circuit breakers, structured logging, and Prometheus metrics provide production-grade observability. The Dockerized deployment with GitHub Actions CI/CD demonstrates a complete DevOps workflow from development to production readiness.
Key Features Implemented
- ✓PDF document upload with text extraction and multi-language detection (EN/ES/FR/DE/PT)
- ✓Intelligent text chunking with paragraph → sentence → word hierarchy and configurable token limits
- ✓Vector embeddings via OpenAI text-embedding-3-small with batch processing and pgvector storage
- ✓Hybrid retrieval combining cosine similarity vector search with PostgreSQL tsvector full-text keyword search
- ✓Streaming AI chat responses via OpenAI Responses API with Server-Sent Events
- ✓Query rewriting for follow-up question understanding using conversation history
- ✓Conversation memory with persistent session and message storage
- ✓Citation extraction linking AI responses back to source document chunks
- ✓Background job queue with retry logic, concurrency control, and circuit breakers
- ✓Admin dashboard with user management, system stats, and document oversight
- ✓Row-Level Security (RLS) on all database tables with role-based access control
- ✓Prometheus metrics, structured logging, health checks, and rate limiting