How to Build a Production-Grade RAG Agent with Hybrid Search in 30 Minutes A developer detailed a production-grade RAG agent architecture that combines hybrid search with Reciprocal Rank Fusion (RRF) and a LangGraph supervisor agent. The approach uses PostgreSQL's pgvector for dense embeddings and full-text search for sparse retrieval, executed in a single SQL query. The developer emphasized that naive vector-only retrieval fails on exact keywords, acronyms, and error codes, and the hybrid method improves accuracy for enterprise knowledge bases. Most Retrieval-Augmented Generation RAG tutorials stop at a naive vector lookup: embed text with OpenAI, store it in Pinecone or Chroma, and perform cosine similarity search. In production environments, this naive approach quickly fails. Vector embeddings excel at semantic similarity, but they consistently stumble on exact keywords, acronyms, product SKUs, UUIDs, and domain-specific error codes. To build an enterprise-grade RAG agent, you need Hybrid Search with Reciprocal Rank Fusion RRF combined with a deterministic multi-agent harness. pgvector Consider a developer searching an enterprise knowledge base for: "Fix CVE-2024-38077 Windows Netlogon RPC buffer overflow" "CVE-2024-38077" , but misses related contextual documentation that uses synonyms like "Netlogon remote elevation vulnerability" . The Solution: Hybrid Search + Reciprocal Rank Fusion RRF Hybrid search executes both retrieval pipelines concurrently and merges the candidate rankings using the mathematical RRF formula: $$\text{RRF Score} d = \sum {m \in M} \frac{1}{60 + r m d }$$ Where: pgvector + tsvector You don't need a separate vector database. PostgreSQL handles dense vector embeddings and sparse full-text search within a single atomic ACID transaction. sql -- Enable vector extension CREATE EXTENSION IF NOT EXISTS vector; -- Create enterprise knowledge documents table CREATE TABLE enterprise documents id UUID PRIMARY KEY DEFAULT gen random uuid , tenant id VARCHAR 64 NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, search vector TSVECTOR GENERATED ALWAYS AS to tsvector 'english', title || ' ' || content STORED, embedding VECTOR 1536 , -- Compatible with text-embedding-3-small metadata JSONB DEFAULT '{}'::jsonb, created at TIMESTAMP WITH TIME ZONE DEFAULT NOW ; -- Fast approximate nearest neighbor index HNSW CREATE INDEX ON enterprise documents USING hnsw embedding vector cosine ops WITH m = 16, ef construction = 64 ; -- High-performance GIN index for sparse full-text search CREATE INDEX ON enterprise documents USING gin search vector ; 3. The SQL Reciprocal Rank Fusion RRF Query Here is the single SQL query that executes both dense and sparse retrieval in parallel and merges them via RRF: sql WITH dense candidates AS SELECT id, content, metadata, rank OVER ORDER BY embedding <= $1 as d rank FROM enterprise documents WHERE tenant id = $3 LIMIT 50 , sparse candidates AS SELECT id, content, metadata, rank OVER ORDER BY ts rank cd search vector, plainto tsquery $2 DESC as s rank FROM enterprise documents WHERE tenant id = $3 AND search vector @@ plainto tsquery $2 LIMIT 50 SELECT COALESCE d.id, s.id as id, COALESCE d.content, s.content as content, COALESCE d.metadata, s.metadata as metadata, COALESCE 1.0 / 60 + d.d rank , 0.0 + COALESCE 1.0 / 60 + s.s rank , 0.0 as fusion score FROM dense candidates d FULL OUTER JOIN sparse candidates s ON d.id = s.id ORDER BY fusion score DESC LIMIT 10; 4. Connecting the Retrieval Engine to LangGraph Now we wrap the hybrid search function inside an autonomous LangGraph Supervisor Agent that can analyze the retrieved context and verify facts before generating an answer. typescript import { StateGraph, END, START } from '@langchain/langgraph'; import { HybridSearchEngine } from './tools/hybridSearch'; export async function createRAGAgent { const searchEngine = new HybridSearchEngine process.env.DATABASE URL ; const workflow = new StateGraph { // Define state channels channels: { query: { value: x, y = y ?? x, default: = '' }, retrievedContext: { value: x, y = y ?? x, default: = }, finalAnswer: { value: x, y = y ?? x, default: = '' }, } } ; // Step 1: Hybrid Retrieval Node workflow.addNode 'retrieve', async state = { const embedding = await generateEmbedding state.query ; const results = await searchEngine.search state.query, embedding, 5 ; return { retrievedContext: results }; } ; // Step 2: Answer Generation Node Claude 3.5 Sonnet workflow.addNode 'synthesize', async state = { const answer = await generateGroundedAnswer state.query, state.retrievedContext ; return { finalAnswer: answer }; } ; workflow.addEdge START, 'retrieve' ; workflow.addEdge 'retrieve', 'synthesize' ; workflow.addEdge 'synthesize', END ; return workflow.compile ; } 5. Architectural Checklist for Production RAG Before rolling this out to production users, enforce these 4 guardrails: Strict Multi-Tenant Row-Level Security RLS : Ensure tenant IDs are parameterized at the DB connection level. Context Window Truncation: Dynamically budget token usage with tiktoken to prevent context overflow. Cross-Encoder Re-ranking: For legal/medical data, add a secondary cross-encoder re-ranking pass cohere.rerank or bge-reranker-large . Hallucination Tripwires: Measure output faithfulness against retrieved