RAG and Vector Search
Design, implement, and evaluate retrieval-augmented generation pipelines.
Core principle: Retrieval quality determines answer quality. A great generator with poor retrieval produces confident hallucinations.
When to Use
- Building a question-answering system over documents
- Adding knowledge retrieval to an LLM application
- Implementing semantic search
- Evaluating or debugging an existing RAG pipeline
- Choosing between vector databases
- Optimizing retrieval relevance or reducing hallucination
When NOT to Use
- Fine-tuning a model (RAG and fine-tuning are complementary, not substitutes)
- Simple keyword search (use Elasticsearch/Algolia)
- Real-time data that can't be pre-indexed (use function calling instead)
Pipeline Architecture
INGESTION
Documents → Clean → Chunk → Embed → Store in vector DB
RETRIEVAL
Query → Embed → Similarity Search → Rerank → Select Context
GENERATION
Context + Query → LLM → Answer
Document Chunking
Strategy Selection
| Strategy |
When to Use |
Trade-offs |
| Fixed-size (512-1024 tokens, 10-20% overlap) |
Uniform documents, low latency required |
May break semantic units |
| Semantic (split on topic shifts) |
Long-form content, technical docs |
Expensive, variable sizes |
| Hierarchical (paragraphs → sections → doc) |
Complex documents needing multi-scale retrieval |
Complex implementation |
| Structural (respect headers, code blocks) |
Markdown, HTML, code |
Requires format parsing |
Chunking Rules
- Always include metadata: source, section title, page number, document date
- Never split mid-sentence
- Maintain 10-20% overlap between consecutive chunks
- Test with 3+ strategies and measure retrieval precision before choosing
def chunk_fixed(text: str, size=1024, overlap=100) -> list[str]:
chunks, step = [], size - overlap
for i in range(0, len(text), step):
chunks.append(text[i:i+size])
return chunks
Embedding Model Selection
| Dimension |
Speed |
Quality |
Best For |
| 384 (all-MiniLM-L6-v2) |
Very fast |
Good |
High-volume, latency-sensitive |
| 768 (all-mpnet-base-v2) |
Balanced |
Excellent |
Most production apps |
| 1536 (OpenAI ada-002) |
Moderate |
Very high |
Complex, nuanced domains |
| Domain-specific (BioBERT, CodeBERT) |
Varies |
Best for domain |
Specialized content |
Selection criteria:
- Latency requirement < 100ms → use fast model (MiniLM)
- High precision needed → use larger model (mpnet, ada-002)
- Specialized content → use domain-specific model
- Self-hosted required → use open-source (MiniLM, mpnet)
Vector Database Selection
| Database |
Best For |
Hosting |
Key Feature |
| pgvector |
RDBMS-first, ACID needed, relational joins |
Self-hosted (PostgreSQL) |
Combine with SQL queries |
| Chroma |
Prototyping, development |
Embedded/local |
Zero-config, easy start |
| Qdrant |
High performance, resource-constrained |
Self-hosted or cloud |
Rust speed, payload filtering |
| Weaviate |
Complex use cases, multi-modal |
Self-hosted or cloud |
GraphQL, auto-vectorization |
| Pinecone |
Managed production, no ops burden |
Fully managed |
99.99% SLA, simple API |
Retrieval Strategies
Dense (Semantic) Retrieval
results = vector_db.query(
query_embedding=embed(query),
k=5,
filter={"category": "orders"} # metadata pre-filtering
)
Best for: semantic similarity, paraphrase matching, conceptual queries.
Sparse (Keyword) Retrieval
BM25 or Elasticsearch. Best for: exact terminology, proper nouns, product codes.
Hybrid Search (Recommended for Production)
Combine dense + sparse with Reciprocal Rank Fusion:
dense_results = semantic_search(query, k=10)
sparse_results = bm25_search(query, k=10)
fused = reciprocal_rank_fusion([dense_results, sparse_results])
Reranking (Two-Stage)
- Retrieve k=20 candidates with fast dense search
- Rerank with cross-encoder model (slower but more accurate)
- Return top k=5
Use when: precision matters more than latency. Adds ~100-200ms.
Query Transformation
| Technique |
When |
How |
| HyDE |
Query style ≠ document style |
Generate hypothetical answer, embed that |
| Multi-query |
Ambiguous or complex queries |
Generate 3-5 variations, retrieve for each |
| Step-back |
Specific queries needing broader context |
"What is X?" → "What are things like X?" |
Context Window Optimization
Assembly Strategy
- Rank chunks by relevance score (highest first)
- Remove redundant chunks (>90% semantic overlap)
- Fit within token budget (leave room for prompt + output)
- Order: general context first, specific evidence last
Context Compression
When too many relevant chunks exist:
- Summarize lower-ranked chunks (keep key facts, remove prose)
- Extract only relevant sentences from each chunk
- Set minimum relevance threshold (drop chunks below 0.7 similarity)
RAG Evaluation
Key Metrics
| Metric |
Definition |
Target |
| Context Relevance |
Retrieved chunks relevant to query |
>0.80 |
| Faithfulness |
Answer grounded in context (not hallucinated) |
>0.90 |
| Answer Relevance |
Answer addresses the original question |
>0.85 |
| Precision@K |
% of top-K chunks that are relevant |
>0.70 |
RAGAS Framework
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
scores = evaluate(
dataset=test_dataset, # questions, contexts, answers, ground_truths
metrics=[faithfulness, answer_relevancy, context_precision],
)
print(scores) # faithfulness: 0.92, answer_relevancy: 0.88, ...
Common Failure Modes
| Failure |
Symptoms |
Fix |
| Retrieval miss |
Correct answer exists but not retrieved |
Lower similarity threshold, improve chunking, try hybrid search |
| Hallucination |
Answer not supported by context |
Add faithfulness guardrail, improve retrieval, add "only use provided context" instruction |
| Context overflow |
Context exceeds token limit |
Raise similarity threshold, compress context, reduce chunk size |
| Irrelevant retrieval |
Chunks retrieved are off-topic |
Improve chunking, add metadata filters, use reranking |
| Slow latency |
RAG adds >1s to response |
Cache embeddings, use faster model, reduce k, cache common queries |
Common Mistakes
- Not testing multiple chunking strategies before choosing
- Using generic embedding model for specialized domain without benchmarking
- Single-stage dense retrieval only (missing keyword matches)
- No evaluation framework (can't measure quality improvements)
- No fallback when retrieval returns 0 results
- Caching responses without checking if underlying documents changed
Verification Checklist
1---2name: rag-and-vector-search3description: Use when building retrieval-augmented generation (RAG) pipelines, implementing vector search, choosing embedding models or vector databases, optimizing retrieval strategies, evaluating RAG quality (faithfulness, relevance, groundedness), or debugging RAG failures like hallucination, retrieval miss, or context overflow. Use for document chunking, hybrid search, reranking, context window optimization, and RAG evaluation with RAGAS or similar frameworks.4---56# RAG and Vector Search78Design, implement, and evaluate retrieval-augmented generation pipelines.910**Core principle:** Retrieval quality determines answer quality. A great generator with poor retrieval produces confident hallucinations.1112## When to Use1314- Building a question-answering system over documents15- Adding knowledge retrieval to an LLM application16- Implementing semantic search17- Evaluating or debugging an existing RAG pipeline18- Choosing between vector databases19- Optimizing retrieval relevance or reducing hallucination2021## When NOT to Use2223- Fine-tuning a model (RAG and fine-tuning are complementary, not substitutes)24- Simple keyword search (use Elasticsearch/Algolia)25- Real-time data that can't be pre-indexed (use function calling instead)2627---2829## Pipeline Architecture3031```32INGESTION33 Documents → Clean → Chunk → Embed → Store in vector DB3435RETRIEVAL 36 Query → Embed → Similarity Search → Rerank → Select Context3738GENERATION39 Context + Query → LLM → Answer40```4142---4344## Document Chunking4546### Strategy Selection4748| Strategy | When to Use | Trade-offs |49|---------|-------------|-----------|50| **Fixed-size** (512-1024 tokens, 10-20% overlap) | Uniform documents, low latency required | May break semantic units |51| **Semantic** (split on topic shifts) | Long-form content, technical docs | Expensive, variable sizes |52| **Hierarchical** (paragraphs → sections → doc) | Complex documents needing multi-scale retrieval | Complex implementation |53| **Structural** (respect headers, code blocks) | Markdown, HTML, code | Requires format parsing |5455### Chunking Rules5657- Always include metadata: source, section title, page number, document date58- Never split mid-sentence59- Maintain 10-20% overlap between consecutive chunks60- Test with 3+ strategies and measure retrieval precision before choosing6162```python63def chunk_fixed(text: str, size=1024, overlap=100) -> list[str]:64 chunks, step = [], size - overlap65 for i in range(0, len(text), step):66 chunks.append(text[i:i+size])67 return chunks68```6970---7172## Embedding Model Selection7374| Dimension | Speed | Quality | Best For |75|-----------|-------|---------|----------|76| 384 (all-MiniLM-L6-v2) | Very fast | Good | High-volume, latency-sensitive |77| 768 (all-mpnet-base-v2) | Balanced | Excellent | Most production apps |78| 1536 (OpenAI ada-002) | Moderate | Very high | Complex, nuanced domains |79| Domain-specific (BioBERT, CodeBERT) | Varies | Best for domain | Specialized content |8081**Selection criteria:**821. Latency requirement < 100ms → use fast model (MiniLM)832. High precision needed → use larger model (mpnet, ada-002)843. Specialized content → use domain-specific model854. Self-hosted required → use open-source (MiniLM, mpnet)8687---8889## Vector Database Selection9091| Database | Best For | Hosting | Key Feature |92|----------|----------|---------|-------------|93| **pgvector** | RDBMS-first, ACID needed, relational joins | Self-hosted (PostgreSQL) | Combine with SQL queries |94| **Chroma** | Prototyping, development | Embedded/local | Zero-config, easy start |95| **Qdrant** | High performance, resource-constrained | Self-hosted or cloud | Rust speed, payload filtering |96| **Weaviate** | Complex use cases, multi-modal | Self-hosted or cloud | GraphQL, auto-vectorization |97| **Pinecone** | Managed production, no ops burden | Fully managed | 99.99% SLA, simple API |9899---100101## Retrieval Strategies102103### Dense (Semantic) Retrieval104105```python106results = vector_db.query(107 query_embedding=embed(query),108 k=5,109 filter={"category": "orders"} # metadata pre-filtering110)111```112113Best for: semantic similarity, paraphrase matching, conceptual queries.114115### Sparse (Keyword) Retrieval116117BM25 or Elasticsearch. Best for: exact terminology, proper nouns, product codes.118119### Hybrid Search (Recommended for Production)120121Combine dense + sparse with Reciprocal Rank Fusion:122123```python124dense_results = semantic_search(query, k=10)125sparse_results = bm25_search(query, k=10)126fused = reciprocal_rank_fusion([dense_results, sparse_results])127```128129### Reranking (Two-Stage)1301311. Retrieve k=20 candidates with fast dense search1322. Rerank with cross-encoder model (slower but more accurate)1333. Return top k=5134135Use when: precision matters more than latency. Adds ~100-200ms.136137### Query Transformation138139| Technique | When | How |140|-----------|------|-----|141| **HyDE** | Query style ≠ document style | Generate hypothetical answer, embed that |142| **Multi-query** | Ambiguous or complex queries | Generate 3-5 variations, retrieve for each |143| **Step-back** | Specific queries needing broader context | "What is X?" → "What are things like X?" |144145---146147## Context Window Optimization148149### Assembly Strategy1501511. Rank chunks by relevance score (highest first)1522. Remove redundant chunks (>90% semantic overlap)1533. Fit within token budget (leave room for prompt + output)1544. Order: general context first, specific evidence last155156### Context Compression157158When too many relevant chunks exist:159- Summarize lower-ranked chunks (keep key facts, remove prose)160- Extract only relevant sentences from each chunk161- Set minimum relevance threshold (drop chunks below 0.7 similarity)162163---164165## RAG Evaluation166167### Key Metrics168169| Metric | Definition | Target |170|--------|------------|--------|171| **Context Relevance** | Retrieved chunks relevant to query | >0.80 |172| **Faithfulness** | Answer grounded in context (not hallucinated) | >0.90 |173| **Answer Relevance** | Answer addresses the original question | >0.85 |174| **Precision@K** | % of top-K chunks that are relevant | >0.70 |175176### RAGAS Framework177178```python179from ragas import evaluate180from ragas.metrics import faithfulness, answer_relevancy, context_precision181182scores = evaluate(183 dataset=test_dataset, # questions, contexts, answers, ground_truths184 metrics=[faithfulness, answer_relevancy, context_precision],185)186print(scores) # faithfulness: 0.92, answer_relevancy: 0.88, ...187```188189---190191## Common Failure Modes192193| Failure | Symptoms | Fix |194|---------|---------|-----|195| **Retrieval miss** | Correct answer exists but not retrieved | Lower similarity threshold, improve chunking, try hybrid search |196| **Hallucination** | Answer not supported by context | Add faithfulness guardrail, improve retrieval, add "only use provided context" instruction |197| **Context overflow** | Context exceeds token limit | Raise similarity threshold, compress context, reduce chunk size |198| **Irrelevant retrieval** | Chunks retrieved are off-topic | Improve chunking, add metadata filters, use reranking |199| **Slow latency** | RAG adds >1s to response | Cache embeddings, use faster model, reduce k, cache common queries |200201---202203## Common Mistakes204205- Not testing multiple chunking strategies before choosing206- Using generic embedding model for specialized domain without benchmarking207- Single-stage dense retrieval only (missing keyword matches)208- No evaluation framework (can't measure quality improvements)209- No fallback when retrieval returns 0 results210- Caching responses without checking if underlying documents changed211212## Verification Checklist213214- [ ] Chunking strategy tested and retrieval precision >0.70215- [ ] Embedding model benchmarked for domain relevance216- [ ] Hybrid search implemented (dense + sparse)217- [ ] Reranking applied for high-precision use cases218- [ ] Faithfulness >0.90 on test set (no hallucination)219- [ ] Context relevance >0.80 on test set220- [ ] Token budget respected (context fits in window)221- [ ] Fallback for empty retrieval results222- [ ] Latency <500ms end-to-end (retrieval + generation)223- [ ] Evaluation metrics monitored in production