Retrieval-Augmented Generation (RAG) has become the go-to pattern for building AI systems that need to answer questions based on your own data. But there's a huge gap between a demo that works on 10 documents and a production system handling thousands of queries per day.
This guide walks you through building a production-ready RAG system that actually scales. We'll cover architecture decisions, chunking strategies, hybrid search, and deployment considerations based on real implementations.
Architecture Overview
A production RAG system has four main components:
- Document Processing Pipeline - Ingests, chunks, and embeds your documents
- Vector Database - Stores embeddings and handles similarity search
- Retrieval Layer - Combines vector and keyword search for best results
- Generation Layer - LLM that uses retrieved context to answer questions
The key is making each component robust, observable, and independently scalable.
Step 1: Document Processing Pipeline
Most RAG tutorials skip this, but document processing is where 80% of production issues occur.
Chunking Strategy
Don't just split on character count. Use semantic chunking:
- Section-aware splitting - Respect document structure (headers, paragraphs)
- Overlap chunks - 10-20% overlap prevents context loss at boundaries
- Metadata preservation - Keep source, page number, section title with each chunk
For technical docs, I use 512 tokens per chunk with 50 token overlap. For conversational data, 256 tokens works better.
Example Pipeline Code
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import PDFLoader
# Load documents
loader = PDFLoader("docs/")
documents = loader.load()
# Smart chunking
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = text_splitter.split_documents(documents)
# Add metadata
for chunk in chunks:
chunk.metadata["source_type"] = "pdf"
chunk.metadata["indexed_at"] = datetime.now()
Production Tip
Always preserve source metadata. You'll need it for citations, debugging, and filtering results.
Step 2: Choosing and Configuring Vector Database
Your vector database choice depends on scale and budget:
Database Options
- Pinecone - Best for production, managed, $70/month minimum
- Weaviate - Self-hosted, open source, hybrid search built-in
- Qdrant - Fast, good for high-throughput, easy Docker deploy
- Chroma - Great for prototyping, not for production scale
For most production systems, I recommend Weaviate or Pinecone. Weaviate if you can self-host, Pinecone for managed simplicity.
Embedding Model Selection
Use domain-specific embeddings when possible:
- OpenAI ada-002 - General purpose, good quality, $0.0001/1K tokens
- Cohere embed-v3 - Multilingual, compression support
- sentence-transformers - Open source, free to run, lower quality
I typically use OpenAI for production unless budget constraints require self-hosting.
Step 3: Implementing Hybrid Search
Pure vector search misses exact matches. Pure keyword search misses semantic relevance. Hybrid search gets both.
How Hybrid Search Works
- Run vector similarity search (get top 20 results)
- Run keyword search with BM25 (get top 20 results)
- Merge and rerank using a reranking model
- Return top 5 final results
Implementation Example
async def hybrid_search(query: str, k: int = 5):
# Vector search
vector_results = await vector_db.similarity_search(
query,
k=20
)
# Keyword search (BM25)
keyword_results = await keyword_index.search(
query,
k=20
)
# Merge results
combined = merge_results(vector_results, keyword_results)
# Rerank with cross-encoder
reranked = reranker.rerank(
query,
combined,
top_k=k
)
return reranked
Hybrid search typically improves relevance by 20-30% compared to vector-only search.
Step 4: Building the Generation Layer
Prompt Engineering for RAG
Your prompt structure matters more than the LLM choice. Use this template:
You are an assistant answering questions based on provided context.
CONTEXT:
{retrieved_chunks}
QUESTION:
{user_question}
INSTRUCTIONS:
- Answer based ONLY on the provided context
- If the context doesn't contain the answer, say so
- Include source citations in your answer
- Be concise and specific
ANSWER:
Citation Tracking
Always include sources in responses:
- Pass chunk metadata to the LLM
- Ask LLM to cite specific sources
- Return sources alongside the answer
- Enable users to verify information
Step 5: Production Considerations
Caching Strategy
Cache at two levels:
- Query cache - Cache identical questions (30% hit rate typical)
- Embedding cache - Cache embeddings for repeated queries
Use Redis with 1-hour TTL. Can reduce costs by 40% and latency by 70%.
Monitoring and Observability
Track these metrics:
- Retrieval latency (target: <200ms)
- Generation latency (target: <2s)
- Retrieval relevance (measure with user feedback)
- Cache hit rate
- Error rate by component
Use LangSmith or LangFuse for RAG-specific observability.
Cost Optimization
For a system handling 10K queries/day:
- Embeddings - $15-30/month (depends on chunk size)
- Vector DB - $70-200/month (depends on scale)
- LLM calls - $300-600/month (GPT-3.5/4)
- Total: ~$400-850/month
Reduce costs by:
- Using smaller embedding models
- Aggressive caching
- GPT-3.5 instead of GPT-4 when possible
- Batch processing embeddings
Scaling Considerations
As you scale past 100K queries/day:
- Separate indexing and querying workloads
- Use read replicas for vector database
- Implement request queuing with Redis/RabbitMQ
- Consider CDN for static chunk embeddings
Common Pitfalls and Solutions
1. Poor Retrieval Quality
Symptom: LLM says "I don't have information about that" even though the data exists.
Fix: Improve chunking, use hybrid search, tune similarity threshold.
2. Slow Response Times
Symptom: Queries take 5+ seconds.
Fix: Implement caching, reduce chunk retrieval count, use streaming responses.
3. Hallucinations Despite RAG
Symptom: LLM makes up information not in context.
Fix: Strengthen prompt instructions, use GPT-4 instead of 3.5, add confidence scoring.
4. High Costs
Symptom: Costs higher than expected.
Fix: Cache aggressively, batch embeddings, use cheaper models, reduce chunk size.
Key Takeaway
Production RAG is 20% choosing the right tools and 80% optimizing chunking, retrieval, and prompts. Start simple, measure everything, and iterate based on user feedback.
Example Production Stack
Here's a battle-tested stack that I've deployed multiple times:
- Documents: S3 for storage, Lambda for processing
- Chunking: LangChain RecursiveCharacterTextSplitter
- Embeddings: OpenAI ada-002
- Vector DB: Pinecone (managed) or Weaviate (self-hosted)
- Search: Hybrid with Cohere reranker
- LLM: GPT-4-turbo for accuracy, GPT-3.5 for speed
- Caching: Redis Cloud
- API: FastAPI on AWS ECS
- Monitoring: LangSmith + CloudWatch
This stack handles 50K+ queries/day with 95th percentile latency under 3 seconds.
Next Steps
Once your basic RAG system is working:
- Add query decomposition for complex questions
- Implement multi-hop reasoning for questions requiring multiple sources
- Add guardrails to prevent prompt injection
- Build feedback loops to continuously improve retrieval
- Consider fine-tuning embeddings on your specific domain
RAG is not a one-time setup. It requires continuous monitoring and iteration based on user behavior and feedback.