
Rag Architect
- 120 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Design retrieval-augmented generation systems: chunking, embeddings, vector stores, reranking, evaluation, and production agent retrieval pipelines.
About
rag-architect guides Claude through end-to-end RAG system design during build: data ingestion, chunking policies, embedding and index choices, query routing, reranking, eval metrics, and operational patterns for reliable agent and API retrieval.
- Chunking and embedding strategy
- Vector store selection
- Retrieval and reranking design
- RAG evaluation harnesses
- Production pipeline architecture
Rag Architect by the numbers
- 120 all-time installs (skills.sh)
- Ranked #3,844 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill rag-architectAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 120 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Design retrieval-augmented generation systems: chunking, embeddings, vector stores, reranking, evaluation, and production agent retrieval pipelines.
Files
RAG Architect
The agent designs, implements, and optimizes production-grade Retrieval-Augmented Generation pipelines, covering the full lifecycle from document chunking through evaluation.
Workflow
1. Analyse corpus -- Profile the document collection: count, average length, format mix (PDF, HTML, Markdown), language(s), and domain. Validate that sample documents are accessible before proceeding. 2. Select chunking strategy -- Choose from the Chunking Strategy Matrix based on corpus characteristics. Set chunk size, overlap, and boundary rules. Run a test split on 100 sample documents. 3. Choose embedding model -- Select an embedding model from the Embedding Model table based on domain, latency budget, and cost constraints. Verify dimension compatibility with the target vector database. 4. Select vector database -- Pick a vector store from the Vector Database Comparison based on scale, query patterns, and operational requirements. 5. Design retrieval pipeline -- Configure retrieval strategy (dense, sparse, or hybrid). Add reranking if precision requirements exceed 0.85. Set the top-K parameter and similarity threshold. 6. Implement query transformations -- If query-document style mismatch exists, enable HyDE. If queries are ambiguous, enable multi-query generation. Validate each transformation improves retrieval metrics on a held-out set. 7. Configure guardrails -- Enable PII detection, toxicity filtering, hallucination detection, and source attribution. Set confidence score thresholds. 8. Evaluate end-to-end -- Run the RAGAS evaluation framework. Verify faithfulness > 0.90, context relevance > 0.80, answer relevance > 0.85. Iterate on weak components.
Chunking Strategy Matrix
| Strategy | Best For | Chunk Size | Overlap | Pros | Cons |
|---|---|---|---|---|---|
| Fixed-size (token) | Uniform docs, consistent sizing | 512-2048 tokens | 10-20% | Predictable, simple | Breaks semantic units |
| Sentence-based | Narrative text, articles | 3-8 sentences | 1 sentence | Preserves language boundaries | Variable sizes |
| Paragraph-based | Structured docs, technical manuals | 1-3 paragraphs | 0-1 paragraph | Preserves topic coherence | Highly variable sizes |
| Semantic | Long-form, research papers | Dynamic | Topic-shift detection | Best coherence | Computationally expensive |
| Recursive | Mixed content types | Dynamic, multi-level | Per-level | Optimal utilization | Complex implementation |
| Document-aware | Multi-format collections | Format-specific | Section-level | Preserves metadata | Format-specific code required |
Embedding Model Comparison
| Model | Dimensions | Speed | Quality | Cost | Best For |
|---|---|---|---|---|---|
| all-MiniLM-L6-v2 | 384 | ~14K tok/s | Good | Free (local) | Prototyping, low-latency |
| all-mpnet-base-v2 | 768 | ~2.8K tok/s | Better | Free (local) | Balanced production use |
| text-embedding-3-small | 1536 | API | High | $0.02/1M tokens | Cost-effective production |
| text-embedding-3-large | 3072 | API | Highest | $0.13/1M tokens | Maximum quality |
| Domain fine-tuned | Varies | Varies | Domain-best | Training cost | Specialized domains (legal, medical) |
Vector Database Comparison
| Database | Type | Scaling | Key Feature | Best For |
|---|---|---|---|---|
| Pinecone | Managed | Auto-scaling | Metadata filtering, hybrid search | Production, managed preference |
| Weaviate | Open source | Horizontal | GraphQL API, multi-modal | Complex data types |
| Qdrant | Open source | Distributed | High perf, low memory (Rust) | Performance-critical |
| Chroma | Embedded | Limited | Simple API, SQLite-backed | Prototyping, small-scale |
| pgvector | PostgreSQL ext | PostgreSQL scaling | ACID, SQL joins | Existing PostgreSQL infra |
Retrieval Strategies
| Strategy | When to Use | Implementation |
|---|---|---|
| Dense (vector similarity) | Default for semantic search | Cosine similarity with k-NN/ANN |
| Sparse (BM25/TF-IDF) | Exact keyword matching needed | Elasticsearch or inverted index |
| Hybrid (dense + sparse) | Best of both needed | Reciprocal Rank Fusion (RRF) with tuned weights |
| + Reranking | Precision must exceed 0.85 | Cross-encoder reranker after initial retrieval |
Query Transformation Techniques
| Technique | When to Use | How It Works |
|---|---|---|
| HyDE | Query/document style mismatch | LLM generates hypothetical answer; embed that instead of query |
| Multi-query | Ambiguous queries | Generate 3-5 query variations; retrieve for each; deduplicate |
| Step-back | Specific questions needing general context | Transform to broader query; retrieve general + specific |
Context Window Optimization
- Relevance ordering: Most relevant chunks first in the context window
- Diversity: Deduplicate semantically similar chunks
- Token budget: Fit within model context limit; reserve tokens for system prompt and answer
- Hierarchical inclusion: Include section summary before detailed chunks when available
- Compression: Summarize low-relevance chunks; extract key facts from verbose passages
Evaluation Metrics (RAGAS Framework)
| Metric | Target | What It Measures |
|---|---|---|
| Faithfulness | > 0.90 | Answers grounded in retrieved context |
| Context Relevance | > 0.80 | Retrieved chunks relevant to query |
| Answer Relevance | > 0.85 | Answer addresses the original question |
| Precision@K | > 0.70 | % of top-K results that are relevant |
| Recall@K | > 0.80 | % of relevant docs found in top-K |
| MRR | > 0.75 | Reciprocal rank of first relevant result |
Guardrails
- PII detection: Scan retrieved chunks and generated responses for PII; redact or block
- Hallucination detection: Compare generated claims against source documents via NLI
- Source attribution: Every factual claim must cite a retrieved chunk
- Confidence scoring: Return confidence level; if below threshold, return "I don't have enough information"
- Injection prevention: Sanitize user queries; reject prompt injection attempts
Example: Internal Knowledge Base RAG Pipeline
corpus:
documents: 12,000 Confluence pages + 3,000 PDFs
avg_length: 2,400 tokens
languages: [English]
domain: internal engineering docs
pipeline:
chunking:
strategy: recursive
max_tokens: 512
overlap: 50 tokens
boundary: paragraph
embedding:
model: text-embedding-3-small
dimensions: 1536
batch_size: 100
vector_db:
engine: pgvector
index: HNSW (ef_construction=128, m=16)
reason: "Existing PostgreSQL infra; ACID compliance for audit"
retrieval:
strategy: hybrid
dense_weight: 0.7
sparse_weight: 0.3
top_k: 10
reranker: cross-encoder/ms-marco-MiniLM-L-12-v2
final_k: 5
evaluation_results:
faithfulness: 0.93
context_relevance: 0.84
answer_relevance: 0.88
precision_at_5: 0.76
recall_at_10: 0.85Production Patterns
- Caching: Query-level (exact match), semantic (similar queries via embedding distance < 0.05), chunk-level (embedding cache)
- Streaming: Stream generation tokens while retrieval completes; show sources after generation
- Fallbacks: If primary vector DB is unavailable, serve from read-replica; if retrieval returns no results above threshold, say so explicitly
- Document refresh: Incremental re-embedding on change detection; full re-index weekly
- Cost control: Batch embeddings, cache aggressively, route simple queries to BM25 only
Common Pitfalls
| Problem | Solution |
|---|---|
| Chunks break mid-sentence | Use boundary-aware chunking with sentence/paragraph overlap |
| Low retrieval precision | Add cross-encoder reranker; tune similarity threshold |
| High latency (> 2s) | Cache embeddings; use faster model; reduce top-K |
| Inconsistent quality | Implement RAGAS evaluation in CI; add quality scoring |
| Scalability bottleneck | Shard vector DB; implement auto-scaling; add caching layer |
Scripts
Chunking Optimizer
Analyses corpus and recommends optimal chunking strategy with parameters.
Retrieval Evaluator
Runs evaluation suite (precision, recall, MRR, NDCG) against a test query set.
Pipeline Benchmarker
Measures end-to-end latency, throughput, and cost per query across configurations.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Chunks contain incomplete sentences or broken code blocks | Fixed-size chunking ignoring semantic boundaries | Switch to sentence-based or semantic (heading-aware) chunking; enable boundary detection in chunking_optimizer.py |
| Retrieved context is relevant but answer is wrong | LLM hallucinating beyond retrieved chunks | Enable faithfulness evaluation via RAGAS; add source attribution guardrails; lower confidence threshold to surface "I don't know" responses |
| Precision@K below 0.50 despite relevant documents existing | Embedding model does not capture domain vocabulary | Fine-tune embedding model on domain data or switch to a domain-specific model; add cross-encoder reranking stage |
| Query latency exceeds 2 seconds | Large top-K, no caching, or unoptimized HNSW index | Reduce top-K, enable query-level and semantic caching, tune HNSW parameters (ef_search, m) |
| Recall drops after adding new documents | Stale embeddings or index fragmentation after incremental inserts | Trigger full re-index; verify new documents pass chunking pipeline; check embedding model version consistency |
| Hybrid retrieval returns duplicate chunks | Dense and sparse retrievers returning overlapping results without deduplication | Apply Reciprocal Rank Fusion (RRF) with deduplication before reranking; tune dense/sparse weight ratio |
| Evaluation metrics fluctuate across runs | Non-deterministic embedding batching or insufficient test query set | Fix random seeds, increase evaluation sample size, run evaluations on a frozen ground-truth set |
Success Criteria
- Faithfulness > 0.90 -- Generated answers are grounded in retrieved context as measured by the RAGAS faithfulness metric.
- Context Relevance > 0.80 -- At least 80% of retrieved chunks are relevant to the user query.
- Precision@5 > 0.70 -- Seven out of ten top-5 result sets contain only relevant documents.
- End-to-end latency < 500ms -- P95 query-to-response latency stays under 500 milliseconds for interactive workloads.
- Recall@10 > 0.85 -- The system retrieves at least 85% of relevant documents within the top 10 results.
- Chunk boundary quality > 0.80 -- At least 80% of chunks end on clean sentence or paragraph boundaries as reported by
chunking_optimizer.py. - Monthly cost within budget -- Total embedding, vector DB, and reranking costs stay within the budget ceiling defined in requirements.
Scope & Limitations
This skill covers:
- End-to-end RAG pipeline architecture design: chunking, embedding, vector storage, retrieval, reranking, and evaluation.
- Quantitative chunking analysis across four strategy families (fixed-size, sentence, paragraph, semantic).
- Retrieval quality evaluation using standard IR metrics (Precision@K, Recall@K, MRR, NDCG) with a built-in TF-IDF baseline.
- Automated pipeline design with component selection, cost projection, and Mermaid architecture diagrams.
This skill does NOT cover:
- LLM prompt engineering or generation-side optimization -- see
engineering/prompt-engineer-toolkit. - Database schema design for metadata stores alongside vector databases -- see
engineering/database-designer. - Production observability, alerting, and SLO dashboards for deployed pipelines -- see
engineering/observability-designer. - Agent orchestration or multi-step reasoning workflows that sit on top of RAG retrieval -- see
engineering/agent-workflow-designer.
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
engineering/prompt-engineer-toolkit | Optimize system prompts and few-shot examples fed alongside retrieved chunks | Pipeline design output --> prompt templates that reference chunk format and metadata |
engineering/database-designer | Design relational metadata stores (tags, access control, source tracking) paired with the vector database | Vector DB recommendation --> metadata schema for hybrid storage |
engineering/observability-designer | Set up latency, throughput, and accuracy monitoring for the deployed RAG pipeline | Evaluation metrics and SLO targets --> dashboards and alerting rules |
engineering/agent-workflow-designer | Embed the RAG retrieval step inside multi-agent reasoning workflows | Retrieval config --> agent tool definition with top-K and threshold parameters |
engineering/ci-cd-pipeline-builder | Automate embedding re-indexing, evaluation regression tests, and deployment on document changes | Evaluation thresholds --> CI gate that blocks deploys when metrics regress |
engineering/api-design-reviewer | Review the query and ingestion API surface exposed by the RAG service | Pipeline config --> OpenAPI spec review for search and ingest endpoints |
Tool Reference
chunking_optimizer.py
Purpose: Analyzes a document corpus and evaluates multiple chunking strategies (fixed-size, sentence-based, paragraph-based, semantic/heading-aware) to recommend the optimal approach with configuration parameters.
Usage:
python chunking_optimizer.py <directory> [options]Flags / Parameters:
| Flag | Type | Default | Description |
|---|---|---|---|
directory | positional, required | -- | Directory containing text/markdown documents to analyze |
--output, -o | string | None | Output file path for results in JSON format |
--config, -c | string | None | JSON configuration file to customize strategy parameters (fixed_sizes, overlaps, sentence_max_sizes, paragraph_max_sizes, semantic_max_sizes) |
--extensions | string list | .txt .md .markdown | File extensions to include when scanning the corpus |
--verbose, -v | flag | off | Print all strategy scores in addition to the recommendation |
Example:
python chunking_optimizer.py ./docs --output results.json --extensions .txt .md --verboseOutput Formats:
- Console -- Corpus summary, recommended strategy name, performance score, reasoning text, and two sample chunks. With
--verbose, all strategy scores are listed. - JSON (
--output) -- Full results object containingcorpus_info,strategy_results(per-strategy size statistics, boundary quality, semantic coherence, vocabulary statistics, performance score),recommendation(best strategy, all scores, reasoning), andsample_chunks.
---
retrieval_evaluator.py
Purpose: Evaluates retrieval system performance using a built-in TF-IDF baseline retriever and standard information retrieval metrics: Precision@K, Recall@K, MRR, and NDCG. Includes failure analysis and improvement recommendations.
Usage:
python retrieval_evaluator.py <queries> <corpus> <ground_truth> [options]Flags / Parameters:
| Flag | Type | Default | Description |
|---|---|---|---|
queries | positional, required | -- | JSON file containing queries (list of {"id": ..., "query": ...} objects, or {"queries": [...]}) |
corpus | positional, required | -- | Directory containing the document corpus |
ground_truth | positional, required | -- | JSON file mapping query IDs to lists of relevant document IDs |
--output, -o | string | None | Output file path for results in JSON format |
--k-values | int list | 1 3 5 10 | K values used when computing Precision@K, Recall@K, and NDCG@K |
--extensions | string list | .txt .md .markdown | File extensions to include from the corpus directory |
--verbose, -v | flag | off | Print detailed per-metric values and failure analysis counts |
Example:
python retrieval_evaluator.py queries.json ./corpus ground_truth.json --output eval.json --k-values 1 5 10 --verboseOutput Formats:
- Console -- Evaluation summary table (Precision@1, Precision@5, Recall@5, MRR, NDCG@5) with performance assessment and numbered improvement recommendations. With
--verbose, all aggregate metrics and failure analysis counts are printed. - JSON (
--output) -- Full results object containingaggregate_metrics,query_results(per-query metrics, retrieved docs, relevant docs),failure_analysis(poor precision/recall counts, zero-result counts, query length analysis, failure patterns),evaluation_summary, andrecommendations.
---
rag_pipeline_designer.py
Purpose: Accepts a system requirements specification and generates a complete RAG pipeline design including component recommendations (chunking, embedding, vector DB, retrieval, reranking, evaluation), cost projections, a Mermaid architecture diagram, and deployment configuration templates.
Usage:
python rag_pipeline_designer.py <requirements> [options]Flags / Parameters:
| Flag | Type | Default | Description |
|---|---|---|---|
requirements | positional, required | -- | JSON file containing system requirements (document_types, document_count, avg_document_size, queries_per_day, query_patterns, latency_requirement, budget_monthly, accuracy_priority, cost_priority, maintenance_complexity) |
--output, -o | string | None | Output file path for the pipeline design in JSON format |
--verbose, -v | flag | off | Print full configuration templates for each component |
Example:
python rag_pipeline_designer.py requirements.json --output pipeline_design.json --verboseOutput Formats:
- Console -- Design summary with total monthly cost, per-component recommendations (name, rationale, cost), and a Mermaid architecture diagram. With
--verbose, full JSON configuration templates for each component are printed. - JSON (
--output) -- Complete pipeline design object containing per-componentComponentRecommendationfields (name, type, config, rationale, pros, cons, cost_monthly),total_cost,architecture_diagram(Mermaid markup), andconfig_templates(per-component configs plus deployment/scaling/monitoring settings).
#!/usr/bin/env python3
"""
Chunking Optimizer - Analyzes document corpus and recommends optimal chunking strategy.
This script analyzes a collection of text/markdown documents and evaluates different
chunking strategies to recommend the optimal approach for the given corpus.
Strategies tested:
- Fixed-size chunking (character and token-based) with overlap
- Sentence-based chunking
- Paragraph-based chunking
- Semantic chunking (heading-aware)
Metrics measured:
- Chunk size distribution (mean, std, min, max)
- Semantic coherence (topic continuity heuristic)
- Boundary quality (sentence break analysis)
No external dependencies - uses only Python standard library.
"""
import argparse
import json
import os
import re
import statistics
from collections import Counter, defaultdict
from math import log, sqrt
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any
class DocumentCorpus:
"""Handles loading and preprocessing of document corpus."""
def __init__(self, directory: str, extensions: List[str] = None):
self.directory = Path(directory)
self.extensions = extensions or ['.txt', '.md', '.markdown']
self.documents = []
self._load_documents()
def _load_documents(self):
"""Load all text documents from directory."""
if not self.directory.exists():
raise FileNotFoundError(f"Directory not found: {self.directory}")
for file_path in self.directory.rglob('*'):
if file_path.is_file() and file_path.suffix.lower() in self.extensions:
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
if content.strip(): # Only include non-empty files
self.documents.append({
'path': str(file_path),
'content': content,
'size': len(content)
})
except Exception as e:
print(f"Warning: Could not read {file_path}: {e}")
if not self.documents:
raise ValueError(f"No valid documents found in {self.directory}")
print(f"Loaded {len(self.documents)} documents totaling {sum(d['size'] for d in self.documents):,} characters")
class ChunkingStrategy:
"""Base class for chunking strategies."""
def __init__(self, name: str, config: Dict[str, Any]):
self.name = name
self.config = config
def chunk(self, text: str) -> List[Dict[str, Any]]:
"""Split text into chunks. Returns list of chunk dictionaries."""
raise NotImplementedError
class FixedSizeChunker(ChunkingStrategy):
"""Fixed-size chunking with optional overlap."""
def __init__(self, chunk_size: int = 1000, overlap: int = 100, unit: str = 'char'):
config = {'chunk_size': chunk_size, 'overlap': overlap, 'unit': unit}
super().__init__(f'fixed_size_{unit}', config)
self.chunk_size = chunk_size
self.overlap = overlap
self.unit = unit
def chunk(self, text: str) -> List[Dict[str, Any]]:
chunks = []
if self.unit == 'char':
return self._chunk_by_chars(text)
else: # word-based approximation
words = text.split()
return self._chunk_by_words(words)
def _chunk_by_chars(self, text: str) -> List[Dict[str, Any]]:
chunks = []
start = 0
chunk_id = 0
while start < len(text):
end = min(start + self.chunk_size, len(text))
chunk_text = text[start:end]
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': start,
'end': end,
'size': len(chunk_text)
})
start = max(start + self.chunk_size - self.overlap, start + 1)
chunk_id += 1
if start >= len(text):
break
return chunks
def _chunk_by_words(self, words: List[str]) -> List[Dict[str, Any]]:
chunks = []
start = 0
chunk_id = 0
while start < len(words):
end = min(start + self.chunk_size, len(words))
chunk_words = words[start:end]
chunk_text = ' '.join(chunk_words)
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': start,
'end': end,
'size': len(chunk_text)
})
start = max(start + self.chunk_size - self.overlap, start + 1)
chunk_id += 1
if start >= len(words):
break
return chunks
class SentenceChunker(ChunkingStrategy):
"""Sentence-based chunking."""
def __init__(self, max_size: int = 1000):
config = {'max_size': max_size}
super().__init__('sentence_based', config)
self.max_size = max_size
# Simple sentence boundary detection
self.sentence_endings = re.compile(r'[.!?]+\s+')
def chunk(self, text: str) -> List[Dict[str, Any]]:
# Split into sentences
sentences = self._split_sentences(text)
chunks = []
current_chunk = []
current_size = 0
chunk_id = 0
for sentence in sentences:
sentence_size = len(sentence)
if current_size + sentence_size > self.max_size and current_chunk:
# Save current chunk
chunk_text = ' '.join(current_chunk)
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0, # Approximate
'end': len(chunk_text),
'size': len(chunk_text),
'sentence_count': len(current_chunk)
})
chunk_id += 1
current_chunk = [sentence]
current_size = sentence_size
else:
current_chunk.append(sentence)
current_size += sentence_size
# Add final chunk
if current_chunk:
chunk_text = ' '.join(current_chunk)
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0,
'end': len(chunk_text),
'size': len(chunk_text),
'sentence_count': len(current_chunk)
})
return chunks
def _split_sentences(self, text: str) -> List[str]:
"""Simple sentence splitting."""
sentences = []
parts = self.sentence_endings.split(text)
for i, part in enumerate(parts[:-1]):
# Add the sentence ending back
ending_match = list(self.sentence_endings.finditer(text))
if i < len(ending_match):
sentence = part + ending_match[i].group().strip()
else:
sentence = part
if sentence.strip():
sentences.append(sentence.strip())
# Add final part if it exists
if parts[-1].strip():
sentences.append(parts[-1].strip())
return [s for s in sentences if len(s.strip()) > 0]
class ParagraphChunker(ChunkingStrategy):
"""Paragraph-based chunking."""
def __init__(self, max_size: int = 2000, min_paragraph_size: int = 50):
config = {'max_size': max_size, 'min_paragraph_size': min_paragraph_size}
super().__init__('paragraph_based', config)
self.max_size = max_size
self.min_paragraph_size = min_paragraph_size
def chunk(self, text: str) -> List[Dict[str, Any]]:
# Split by double newlines (paragraph boundaries)
paragraphs = [p.strip() for p in re.split(r'\n\s*\n', text) if p.strip()]
chunks = []
current_chunk = []
current_size = 0
chunk_id = 0
for paragraph in paragraphs:
paragraph_size = len(paragraph)
# Skip very short paragraphs unless they're the only content
if paragraph_size < self.min_paragraph_size and len(paragraphs) > 1:
continue
if current_size + paragraph_size > self.max_size and current_chunk:
# Save current chunk
chunk_text = '\n\n'.join(current_chunk)
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0,
'end': len(chunk_text),
'size': len(chunk_text),
'paragraph_count': len(current_chunk)
})
chunk_id += 1
current_chunk = [paragraph]
current_size = paragraph_size
else:
current_chunk.append(paragraph)
current_size += paragraph_size + 2 # Account for newlines
# Add final chunk
if current_chunk:
chunk_text = '\n\n'.join(current_chunk)
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0,
'end': len(chunk_text),
'size': len(chunk_text),
'paragraph_count': len(current_chunk)
})
return chunks
class SemanticChunker(ChunkingStrategy):
"""Heading-aware semantic chunking."""
def __init__(self, max_size: int = 1500, heading_weight: float = 2.0):
config = {'max_size': max_size, 'heading_weight': heading_weight}
super().__init__('semantic_heading', config)
self.max_size = max_size
self.heading_weight = heading_weight
# Markdown and plain text heading patterns
self.heading_patterns = [
re.compile(r'^#{1,6}\s+(.+)$', re.MULTILINE), # Markdown headers
re.compile(r'^(.+)\n[=-]+\s*$', re.MULTILINE), # Underlined headers
re.compile(r'^\d+\.\s*(.+)$', re.MULTILINE), # Numbered sections
]
def chunk(self, text: str) -> List[Dict[str, Any]]:
sections = self._identify_sections(text)
chunks = []
chunk_id = 0
for section in sections:
section_chunks = self._chunk_section(section, chunk_id)
chunks.extend(section_chunks)
chunk_id += len(section_chunks)
return chunks
def _identify_sections(self, text: str) -> List[Dict[str, Any]]:
"""Identify sections based on headings."""
sections = []
lines = text.split('\n')
current_section = {'heading': 'Introduction', 'content': '', 'level': 0}
for line in lines:
is_heading = False
heading_level = 0
heading_text = line.strip()
# Check for markdown headers
if line.strip().startswith('#'):
level = len(line) - len(line.lstrip('#'))
if level <= 6:
heading_text = line.strip('#').strip()
heading_level = level
is_heading = True
# Check for underlined headers
elif len(sections) > 0 and line.strip() and all(c in '=-' for c in line.strip()):
# Previous line might be heading
if current_section['content']:
content_lines = current_section['content'].strip().split('\n')
if content_lines:
potential_heading = content_lines[-1].strip()
if len(potential_heading) > 0 and len(potential_heading) < 100:
# Treat as heading
current_section['content'] = '\n'.join(content_lines[:-1])
sections.append(current_section)
current_section = {
'heading': potential_heading,
'content': '',
'level': 1 if '=' in line else 2
}
continue
if is_heading:
if current_section['content'].strip():
sections.append(current_section)
current_section = {
'heading': heading_text,
'content': '',
'level': heading_level
}
else:
current_section['content'] += line + '\n'
# Add final section
if current_section['content'].strip():
sections.append(current_section)
return sections
def _chunk_section(self, section: Dict[str, Any], start_id: int) -> List[Dict[str, Any]]:
"""Chunk a single section."""
content = section['content'].strip()
if not content:
return []
heading = section['heading']
chunks = []
# If section is small enough, return as single chunk
if len(content) <= self.max_size:
chunks.append({
'id': start_id,
'text': f"{heading}\n\n{content}" if heading else content,
'start': 0,
'end': len(content),
'size': len(content),
'heading': heading,
'level': section['level']
})
return chunks
# Split large sections by paragraphs
paragraphs = [p.strip() for p in content.split('\n\n') if p.strip()]
current_chunk = []
current_size = len(heading) + 2 if heading else 0 # Account for heading
chunk_id = start_id
for paragraph in paragraphs:
paragraph_size = len(paragraph)
if current_size + paragraph_size > self.max_size and current_chunk:
# Save current chunk
chunk_text = '\n\n'.join(current_chunk)
if heading and chunk_id == start_id:
chunk_text = f"{heading}\n\n{chunk_text}"
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0,
'end': len(chunk_text),
'size': len(chunk_text),
'heading': heading if chunk_id == start_id else f"{heading} (continued)",
'level': section['level']
})
chunk_id += 1
current_chunk = [paragraph]
current_size = paragraph_size
else:
current_chunk.append(paragraph)
current_size += paragraph_size + 2 # Account for newlines
# Add final chunk
if current_chunk:
chunk_text = '\n\n'.join(current_chunk)
if heading and chunk_id == start_id:
chunk_text = f"{heading}\n\n{chunk_text}"
elif heading:
chunk_text = f"{heading} (continued)\n\n{chunk_text}"
chunks.append({
'id': chunk_id,
'text': chunk_text,
'start': 0,
'end': len(chunk_text),
'size': len(chunk_text),
'heading': heading if chunk_id == start_id else f"{heading} (continued)",
'level': section['level']
})
return chunks
class ChunkAnalyzer:
"""Analyzes chunks and provides quality metrics."""
def __init__(self):
self.vocabulary = set()
self.word_freq = Counter()
def analyze_chunks(self, chunks: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Comprehensive chunk analysis."""
if not chunks:
return {'error': 'No chunks to analyze'}
sizes = [chunk['size'] for chunk in chunks]
# Basic size statistics
size_stats = {
'count': len(chunks),
'mean': statistics.mean(sizes),
'median': statistics.median(sizes),
'std': statistics.stdev(sizes) if len(sizes) > 1 else 0,
'min': min(sizes),
'max': max(sizes),
'total': sum(sizes)
}
# Boundary quality analysis
boundary_quality = self._analyze_boundary_quality(chunks)
# Semantic coherence (simple heuristic)
coherence_score = self._calculate_semantic_coherence(chunks)
# Vocabulary distribution
vocab_stats = self._analyze_vocabulary(chunks)
return {
'size_statistics': size_stats,
'boundary_quality': boundary_quality,
'semantic_coherence': coherence_score,
'vocabulary_statistics': vocab_stats
}
def _analyze_boundary_quality(self, chunks: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Analyze how well chunks respect natural boundaries."""
sentence_breaks = 0
word_breaks = 0
total_chunks = len(chunks)
sentence_endings = re.compile(r'[.!?]\s*$')
for chunk in chunks:
text = chunk['text'].strip()
if not text:
continue
# Check if chunk ends with sentence boundary
if sentence_endings.search(text):
sentence_breaks += 1
# Check if chunk ends with word boundary
if text[-1].isalnum() or text[-1] in '.!?':
word_breaks += 1
return {
'sentence_boundary_ratio': sentence_breaks / total_chunks if total_chunks > 0 else 0,
'word_boundary_ratio': word_breaks / total_chunks if total_chunks > 0 else 0,
'clean_breaks': sentence_breaks,
'total_chunks': total_chunks
}
def _calculate_semantic_coherence(self, chunks: List[Dict[str, Any]]) -> float:
"""Simple semantic coherence heuristic based on vocabulary overlap."""
if len(chunks) < 2:
return 1.0
coherence_scores = []
for i in range(len(chunks) - 1):
chunk1_words = set(re.findall(r'\b\w+\b', chunks[i]['text'].lower()))
chunk2_words = set(re.findall(r'\b\w+\b', chunks[i+1]['text'].lower()))
if not chunk1_words or not chunk2_words:
continue
# Jaccard similarity as coherence measure
intersection = len(chunk1_words & chunk2_words)
union = len(chunk1_words | chunk2_words)
if union > 0:
coherence_scores.append(intersection / union)
return statistics.mean(coherence_scores) if coherence_scores else 0.0
def _analyze_vocabulary(self, chunks: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Analyze vocabulary distribution across chunks."""
all_words = []
chunk_vocab_sizes = []
for chunk in chunks:
words = re.findall(r'\b\w+\b', chunk['text'].lower())
all_words.extend(words)
chunk_vocab_sizes.append(len(set(words)))
total_vocab = len(set(all_words))
word_freq = Counter(all_words)
return {
'total_vocabulary': total_vocab,
'avg_chunk_vocabulary': statistics.mean(chunk_vocab_sizes) if chunk_vocab_sizes else 0,
'vocabulary_diversity': total_vocab / len(all_words) if all_words else 0,
'most_common_words': word_freq.most_common(10)
}
class ChunkingOptimizer:
"""Main optimizer that tests different chunking strategies."""
def __init__(self):
self.analyzer = ChunkAnalyzer()
def optimize(self, corpus: DocumentCorpus, config: Dict[str, Any] = None) -> Dict[str, Any]:
"""Test all chunking strategies and recommend the best one."""
config = config or {}
strategies = self._create_strategies(config)
results = {}
print(f"Testing {len(strategies)} chunking strategies...")
for strategy in strategies:
print(f" Testing {strategy.name}...")
strategy_results = self._test_strategy(corpus, strategy)
results[strategy.name] = strategy_results
# Recommend best strategy
recommendation = self._recommend_strategy(results)
return {
'corpus_info': {
'document_count': len(corpus.documents),
'total_size': sum(d['size'] for d in corpus.documents),
'avg_document_size': statistics.mean([d['size'] for d in corpus.documents])
},
'strategy_results': results,
'recommendation': recommendation,
'sample_chunks': self._generate_sample_chunks(corpus, recommendation['best_strategy'])
}
def _create_strategies(self, config: Dict[str, Any]) -> List[ChunkingStrategy]:
"""Create all chunking strategies to test."""
strategies = []
# Fixed-size strategies
for size in config.get('fixed_sizes', [512, 1000, 1500]):
for overlap in config.get('overlaps', [50, 100]):
strategies.append(FixedSizeChunker(size, overlap, 'char'))
# Sentence-based strategies
for max_size in config.get('sentence_max_sizes', [800, 1200]):
strategies.append(SentenceChunker(max_size))
# Paragraph-based strategies
for max_size in config.get('paragraph_max_sizes', [1500, 2000]):
strategies.append(ParagraphChunker(max_size))
# Semantic strategies
for max_size in config.get('semantic_max_sizes', [1200, 1800]):
strategies.append(SemanticChunker(max_size))
return strategies
def _test_strategy(self, corpus: DocumentCorpus, strategy: ChunkingStrategy) -> Dict[str, Any]:
"""Test a single chunking strategy."""
all_chunks = []
document_results = []
for doc in corpus.documents:
try:
chunks = strategy.chunk(doc['content'])
all_chunks.extend(chunks)
doc_analysis = self.analyzer.analyze_chunks(chunks)
document_results.append({
'path': doc['path'],
'chunk_count': len(chunks),
'analysis': doc_analysis
})
except Exception as e:
print(f" Error processing {doc['path']}: {e}")
continue
# Overall analysis
overall_analysis = self.analyzer.analyze_chunks(all_chunks)
return {
'strategy_config': strategy.config,
'total_chunks': len(all_chunks),
'overall_analysis': overall_analysis,
'document_results': document_results,
'performance_score': self._calculate_performance_score(overall_analysis)
}
def _calculate_performance_score(self, analysis: Dict[str, Any]) -> float:
"""Calculate overall performance score for a strategy."""
if 'error' in analysis:
return 0.0
size_stats = analysis['size_statistics']
boundary_quality = analysis['boundary_quality']
coherence = analysis['semantic_coherence']
# Normalize metrics to 0-1 range and combine
size_consistency = 1.0 - min(size_stats['std'] / size_stats['mean'], 1.0) if size_stats['mean'] > 0 else 0
boundary_score = (boundary_quality['sentence_boundary_ratio'] + boundary_quality['word_boundary_ratio']) / 2
coherence_score = coherence
# Weighted combination
return (size_consistency * 0.3 + boundary_score * 0.4 + coherence_score * 0.3)
def _recommend_strategy(self, results: Dict[str, Any]) -> Dict[str, Any]:
"""Recommend the best chunking strategy based on analysis."""
best_strategy = None
best_score = 0
strategy_scores = {}
for strategy_name, result in results.items():
score = result['performance_score']
strategy_scores[strategy_name] = score
if score > best_score:
best_score = score
best_strategy = strategy_name
return {
'best_strategy': best_strategy,
'best_score': best_score,
'all_scores': strategy_scores,
'reasoning': self._generate_reasoning(best_strategy, results[best_strategy] if best_strategy else None)
}
def _generate_reasoning(self, strategy_name: str, result: Dict[str, Any]) -> str:
"""Generate human-readable reasoning for the recommendation."""
if not result:
return "No valid strategy found."
analysis = result['overall_analysis']
size_stats = analysis['size_statistics']
boundary = analysis['boundary_quality']
reasoning = f"Recommended '{strategy_name}' because:\n"
reasoning += f"- Average chunk size: {size_stats['mean']:.0f} characters\n"
reasoning += f"- Size consistency: {size_stats['std']:.0f} std deviation\n"
reasoning += f"- Boundary quality: {boundary['sentence_boundary_ratio']:.2%} clean sentence breaks\n"
reasoning += f"- Semantic coherence: {analysis['semantic_coherence']:.3f}\n"
return reasoning
def _generate_sample_chunks(self, corpus: DocumentCorpus, strategy_name: str) -> List[Dict[str, Any]]:
"""Generate sample chunks using the recommended strategy."""
if not strategy_name or not corpus.documents:
return []
# Create strategy instance
strategy = None
if 'fixed_size' in strategy_name:
strategy = FixedSizeChunker()
elif 'sentence' in strategy_name:
strategy = SentenceChunker()
elif 'paragraph' in strategy_name:
strategy = ParagraphChunker()
elif 'semantic' in strategy_name:
strategy = SemanticChunker()
if not strategy:
return []
# Get chunks from first document
sample_doc = corpus.documents[0]
chunks = strategy.chunk(sample_doc['content'])
# Return first 3 chunks as samples
return chunks[:3]
def main():
"""Main function with command-line interface."""
parser = argparse.ArgumentParser(description='Analyze documents and recommend optimal chunking strategy')
parser.add_argument('directory', help='Directory containing text/markdown documents')
parser.add_argument('--output', '-o', help='Output file for results (JSON format)')
parser.add_argument('--config', '-c', help='Configuration file (JSON format)')
parser.add_argument('--extensions', nargs='+', default=['.txt', '.md', '.markdown'],
help='File extensions to process')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
args = parser.parse_args()
# Load configuration
config = {}
if args.config and os.path.exists(args.config):
with open(args.config, 'r') as f:
config = json.load(f)
try:
# Load corpus
print(f"Loading documents from {args.directory}...")
corpus = DocumentCorpus(args.directory, args.extensions)
# Run optimization
optimizer = ChunkingOptimizer()
results = optimizer.optimize(corpus, config)
# Save results
if args.output:
with open(args.output, 'w') as f:
json.dump(results, f, indent=2)
print(f"Results saved to {args.output}")
# Print summary
print("\n" + "="*60)
print("CHUNKING OPTIMIZATION RESULTS")
print("="*60)
corpus_info = results['corpus_info']
print(f"Corpus: {corpus_info['document_count']} documents, {corpus_info['total_size']:,} characters")
recommendation = results['recommendation']
print(f"\nRecommended Strategy: {recommendation['best_strategy']}")
print(f"Performance Score: {recommendation['best_score']:.3f}")
print(f"\nReasoning:\n{recommendation['reasoning']}")
if args.verbose:
print("\nAll Strategy Scores:")
for strategy, score in recommendation['all_scores'].items():
print(f" {strategy}: {score:.3f}")
print("\nSample Chunks:")
for i, chunk in enumerate(results['sample_chunks'][:2]):
print(f"\nChunk {i+1} ({chunk['size']} chars):")
print("-" * 40)
print(chunk['text'][:200] + "..." if len(chunk['text']) > 200 else chunk['text'])
except Exception as e:
print(f"Error: {e}")
return 1
return 0
if __name__ == '__main__':
exit(main())#!/usr/bin/env python3
"""
RAG Pipeline Designer - Designs complete RAG pipelines based on requirements.
This script analyzes requirements and generates a comprehensive RAG pipeline design
including architecture diagrams, component recommendations, configuration templates,
and cost projections.
Components designed:
- Chunking strategy recommendation
- Embedding model selection
- Vector database recommendation
- Retrieval approach (dense/sparse/hybrid)
- Reranking configuration
- Evaluation framework setup
- Production deployment patterns
No external dependencies - uses only Python standard library.
"""
import argparse
import json
import math
import os
from typing import Dict, List, Tuple, Any, Optional
from dataclasses import dataclass, asdict
from enum import Enum
class Scale(Enum):
"""System scale categories."""
SMALL = "small" # < 1M documents, < 1K queries/day
MEDIUM = "medium" # 1M-100M documents, 1K-100K queries/day
LARGE = "large" # 100M+ documents, 100K+ queries/day
class DocumentType(Enum):
"""Document type categories."""
TEXT = "text" # Plain text, articles
TECHNICAL = "technical" # Documentation, manuals
CODE = "code" # Source code files
SCIENTIFIC = "scientific" # Research papers, journals
LEGAL = "legal" # Legal documents, contracts
MIXED = "mixed" # Multiple document types
class Latency(Enum):
"""Latency requirements."""
REAL_TIME = "real_time" # < 100ms
INTERACTIVE = "interactive" # < 500ms
BATCH = "batch" # > 1s acceptable
@dataclass
class Requirements:
"""RAG system requirements."""
document_types: List[str]
document_count: int
avg_document_size: int # characters
queries_per_day: int
query_patterns: List[str] # e.g., ["factual", "conversational", "analytical"]
latency_requirement: str
budget_monthly: float # USD
accuracy_priority: float # 0-1 scale
cost_priority: float # 0-1 scale
maintenance_complexity: str # "low", "medium", "high"
@dataclass
class ComponentRecommendation:
"""Recommendation for a pipeline component."""
name: str
type: str
config: Dict[str, Any]
rationale: str
pros: List[str]
cons: List[str]
cost_monthly: float
@dataclass
class PipelineDesign:
"""Complete RAG pipeline design."""
chunking: ComponentRecommendation
embedding: ComponentRecommendation
vector_db: ComponentRecommendation
retrieval: ComponentRecommendation
reranking: Optional[ComponentRecommendation]
evaluation: ComponentRecommendation
total_cost: float
architecture_diagram: str
config_templates: Dict[str, Any]
class RAGPipelineDesigner:
"""Main pipeline designer class."""
def __init__(self):
self.embedding_models = self._load_embedding_models()
self.vector_databases = self._load_vector_databases()
self.chunking_strategies = self._load_chunking_strategies()
def design_pipeline(self, requirements: Requirements) -> PipelineDesign:
"""Design complete RAG pipeline based on requirements."""
print(f"Designing RAG pipeline for {requirements.document_count:,} documents...")
# Determine system scale
scale = self._determine_scale(requirements)
print(f"System scale: {scale.value}")
# Design each component
chunking = self._recommend_chunking(requirements, scale)
embedding = self._recommend_embedding(requirements, scale)
vector_db = self._recommend_vector_db(requirements, scale)
retrieval = self._recommend_retrieval(requirements, scale)
reranking = self._recommend_reranking(requirements, scale)
evaluation = self._recommend_evaluation(requirements, scale)
# Calculate total cost
total_cost = (chunking.cost_monthly + embedding.cost_monthly +
vector_db.cost_monthly + retrieval.cost_monthly +
evaluation.cost_monthly)
if reranking:
total_cost += reranking.cost_monthly
# Generate architecture diagram
architecture = self._generate_architecture_diagram(
chunking, embedding, vector_db, retrieval, reranking, evaluation
)
# Generate configuration templates
configs = self._generate_config_templates(
chunking, embedding, vector_db, retrieval, reranking, evaluation
)
return PipelineDesign(
chunking=chunking,
embedding=embedding,
vector_db=vector_db,
retrieval=retrieval,
reranking=reranking,
evaluation=evaluation,
total_cost=total_cost,
architecture_diagram=architecture,
config_templates=configs
)
def _determine_scale(self, req: Requirements) -> Scale:
"""Determine system scale based on requirements."""
if req.document_count < 1_000_000 and req.queries_per_day < 1_000:
return Scale.SMALL
elif req.document_count < 100_000_000 and req.queries_per_day < 100_000:
return Scale.MEDIUM
else:
return Scale.LARGE
def _recommend_chunking(self, req: Requirements, scale: Scale) -> ComponentRecommendation:
"""Recommend chunking strategy."""
doc_types = set(req.document_types)
if "code" in doc_types:
strategy = "semantic_code_aware"
config = {"max_size": 1000, "preserve_functions": True, "overlap": 50}
rationale = "Code documents benefit from function/class boundary awareness"
elif "technical" in doc_types or "scientific" in doc_types:
strategy = "semantic_heading_aware"
config = {"max_size": 1500, "heading_weight": 2.0, "overlap": 100}
rationale = "Technical documents have clear hierarchical structure"
elif len(doc_types) > 2 or "mixed" in doc_types:
strategy = "adaptive_chunking"
config = {"strategies": ["paragraph", "sentence", "fixed"], "auto_select": True}
rationale = "Mixed document types require adaptive strategy selection"
else:
if req.avg_document_size > 5000:
strategy = "paragraph_based"
config = {"max_size": 2000, "min_paragraph_size": 100}
rationale = "Large documents benefit from paragraph-based chunking"
else:
strategy = "sentence_based"
config = {"max_size": 1000, "sentence_overlap": 1}
rationale = "Small to medium documents work well with sentence chunking"
return ComponentRecommendation(
name=strategy,
type="chunking",
config=config,
rationale=rationale,
pros=self._get_chunking_pros(strategy),
cons=self._get_chunking_cons(strategy),
cost_monthly=0.0 # Processing cost only
)
def _recommend_embedding(self, req: Requirements, scale: Scale) -> ComponentRecommendation:
"""Recommend embedding model."""
doc_types = set(req.document_types)
# Consider accuracy vs cost priority
high_accuracy = req.accuracy_priority > 0.7
cost_sensitive = req.cost_priority > 0.6
if "code" in doc_types:
if high_accuracy and not cost_sensitive:
model = "openai-code-search-ada-002"
cost_per_1k_tokens = 0.0001
dimensions = 1536
else:
model = "sentence-transformers/code-bert-base"
cost_per_1k_tokens = 0.0 # Self-hosted
dimensions = 768
elif "scientific" in doc_types:
if high_accuracy:
model = "openai-text-embedding-ada-002"
cost_per_1k_tokens = 0.0001
dimensions = 1536
else:
model = "sentence-transformers/scibert-nli"
cost_per_1k_tokens = 0.0
dimensions = 768
else:
if cost_sensitive or scale == Scale.SMALL:
model = "sentence-transformers/all-MiniLM-L6-v2"
cost_per_1k_tokens = 0.0
dimensions = 384
elif high_accuracy:
model = "openai-text-embedding-ada-002"
cost_per_1k_tokens = 0.0001
dimensions = 1536
else:
model = "sentence-transformers/all-mpnet-base-v2"
cost_per_1k_tokens = 0.0
dimensions = 768
# Calculate monthly embedding cost
total_tokens = req.document_count * (req.avg_document_size / 4) # ~4 chars per token
query_tokens = req.queries_per_day * 30 * 20 # ~20 tokens per query per month
monthly_cost = (total_tokens + query_tokens) * cost_per_1k_tokens / 1000
return ComponentRecommendation(
name=model,
type="embedding",
config={
"model": model,
"dimensions": dimensions,
"batch_size": 100 if scale == Scale.SMALL else 1000,
"cache_embeddings": True
},
rationale=f"Selected for {doc_types} with accuracy priority {req.accuracy_priority}",
pros=self._get_embedding_pros(model),
cons=self._get_embedding_cons(model),
cost_monthly=monthly_cost
)
def _recommend_vector_db(self, req: Requirements, scale: Scale) -> ComponentRecommendation:
"""Recommend vector database."""
if scale == Scale.SMALL and req.cost_priority > 0.7:
db = "chroma"
cost = 0.0
rationale = "Local/embedded database suitable for small scale and cost optimization"
elif scale == Scale.SMALL and req.maintenance_complexity == "low":
db = "pgvector"
cost = 50.0 # PostgreSQL hosting
rationale = "Leverage existing PostgreSQL infrastructure"
elif scale == Scale.LARGE or req.latency_requirement == "real_time":
db = "pinecone"
vectors = req.document_count * 2 # Account for chunking
cost = max(70, vectors * 0.00005) # $70 base + $0.00005 per vector
rationale = "Managed service with excellent performance for large scale"
elif req.maintenance_complexity == "low":
db = "weaviate_cloud"
vectors = req.document_count * 2
cost = max(25, vectors * 0.00003)
rationale = "Managed Weaviate with good balance of features and cost"
else:
db = "qdrant"
cost = 100.0 # Self-hosted infrastructure estimate
rationale = "High performance self-hosted option with good scaling"
return ComponentRecommendation(
name=db,
type="vector_database",
config=self._get_vector_db_config(db, req, scale),
rationale=rationale,
pros=self._get_vector_db_pros(db),
cons=self._get_vector_db_cons(db),
cost_monthly=cost
)
def _recommend_retrieval(self, req: Requirements, scale: Scale) -> ComponentRecommendation:
"""Recommend retrieval strategy."""
if req.accuracy_priority > 0.8:
strategy = "hybrid"
rationale = "Hybrid retrieval for maximum accuracy combining dense and sparse methods"
elif "technical" in req.document_types or "code" in req.document_types:
strategy = "hybrid"
rationale = "Technical content benefits from both semantic and keyword matching"
elif req.latency_requirement == "real_time":
strategy = "dense"
rationale = "Dense retrieval faster for real-time requirements"
else:
strategy = "dense"
rationale = "Dense retrieval suitable for general text search"
return ComponentRecommendation(
name=strategy,
type="retrieval",
config={
"strategy": strategy,
"dense_weight": 0.7 if strategy == "hybrid" else 1.0,
"sparse_weight": 0.3 if strategy == "hybrid" else 0.0,
"top_k": 20 if req.accuracy_priority > 0.7 else 10,
"similarity_threshold": 0.7
},
rationale=rationale,
pros=self._get_retrieval_pros(strategy),
cons=self._get_retrieval_cons(strategy),
cost_monthly=0.0
)
def _recommend_reranking(self, req: Requirements, scale: Scale) -> Optional[ComponentRecommendation]:
"""Recommend reranking if beneficial."""
if req.accuracy_priority < 0.6 or req.latency_requirement == "real_time":
return None
if req.cost_priority > 0.8:
return None
# Estimate reranking queries per month
monthly_queries = req.queries_per_day * 30
cost_per_query = 0.002 # Estimated cost for cross-encoder reranking
monthly_cost = monthly_queries * cost_per_query
if monthly_cost > req.budget_monthly * 0.3: # Don't exceed 30% of budget
return None
return ComponentRecommendation(
name="cross_encoder_reranking",
type="reranking",
config={
"model": "cross-encoder/ms-marco-MiniLM-L-12-v2",
"rerank_top_k": 20,
"return_top_k": 5,
"batch_size": 16
},
rationale="Reranking improves precision for high-accuracy requirements",
pros=["Higher precision", "Better ranking quality", "Handles complex queries"],
cons=["Additional latency", "Higher cost", "More complexity"],
cost_monthly=monthly_cost
)
def _recommend_evaluation(self, req: Requirements, scale: Scale) -> ComponentRecommendation:
"""Recommend evaluation framework."""
return ComponentRecommendation(
name="comprehensive_evaluation",
type="evaluation",
config={
"metrics": ["precision@k", "recall@k", "mrr", "ndcg"],
"k_values": [1, 3, 5, 10],
"faithfulness_check": True,
"relevance_scoring": True,
"evaluation_frequency": "weekly" if scale == Scale.LARGE else "monthly",
"sample_size": min(1000, req.queries_per_day * 7)
},
rationale="Comprehensive evaluation essential for production RAG systems",
pros=["Quality monitoring", "Performance tracking", "Issue detection"],
cons=["Additional overhead", "Requires ground truth data"],
cost_monthly=20.0 # Evaluation tooling and compute
)
def _generate_architecture_diagram(self, chunking: ComponentRecommendation,
embedding: ComponentRecommendation,
vector_db: ComponentRecommendation,
retrieval: ComponentRecommendation,
reranking: Optional[ComponentRecommendation],
evaluation: ComponentRecommendation) -> str:
"""Generate Mermaid architecture diagram."""
diagram = """```mermaid
graph TB
%% Document Processing Pipeline
A[Document Corpus] --> B[Document Chunking]
B --> C[Embedding Generation]
C --> D[Vector Database Storage]
%% Query Processing Pipeline
E[User Query] --> F[Query Processing]
F --> G[Vector Search]
D --> G
G --> H[Retrieved Chunks]
"""
if reranking:
diagram += " H --> I[Reranking]\n I --> J[Final Results]\n"
else:
diagram += " H --> J[Final Results]\n"
diagram += """
%% Evaluation Pipeline
J --> K[Response Generation]
K --> L[Evaluation Metrics]
%% Component Details
B -.-> B1[Strategy: """ + chunking.name + """]
C -.-> C1[Model: """ + embedding.name + """]
D -.-> D1[Database: """ + vector_db.name + """]
G -.-> G1[Method: """ + retrieval.name + """]
"""
if reranking:
diagram += " I -.-> I1[Model: " + reranking.name + "]\n"
diagram += " L -.-> L1[Framework: " + evaluation.name + "]\n```"
return diagram
def _generate_config_templates(self, *components) -> Dict[str, Any]:
"""Generate configuration templates for all components."""
configs = {}
for component in components:
if component:
configs[component.type] = {
"component": component.name,
"config": component.config,
"rationale": component.rationale
}
# Add deployment configuration
configs["deployment"] = {
"infrastructure": "cloud" if any("pinecone" in str(c.name) for c in components if c) else "hybrid",
"scaling": {
"auto_scaling": True,
"min_replicas": 1,
"max_replicas": 10
},
"monitoring": {
"metrics": ["latency", "throughput", "accuracy"],
"alerts": ["high_latency", "low_accuracy", "service_down"]
}
}
return configs
def _load_embedding_models(self) -> Dict[str, Dict[str, Any]]:
"""Load embedding model specifications."""
return {
"openai-text-embedding-ada-002": {
"dimensions": 1536,
"cost_per_1k_tokens": 0.0001,
"quality": "high",
"speed": "medium"
},
"sentence-transformers/all-mpnet-base-v2": {
"dimensions": 768,
"cost_per_1k_tokens": 0.0,
"quality": "high",
"speed": "medium"
},
"sentence-transformers/all-MiniLM-L6-v2": {
"dimensions": 384,
"cost_per_1k_tokens": 0.0,
"quality": "medium",
"speed": "fast"
}
}
def _load_vector_databases(self) -> Dict[str, Dict[str, Any]]:
"""Load vector database specifications."""
return {
"pinecone": {"managed": True, "scaling": "excellent", "cost": "high"},
"weaviate": {"managed": False, "scaling": "good", "cost": "medium"},
"qdrant": {"managed": False, "scaling": "excellent", "cost": "low"},
"chroma": {"managed": False, "scaling": "poor", "cost": "free"},
"pgvector": {"managed": False, "scaling": "good", "cost": "medium"}
}
def _load_chunking_strategies(self) -> Dict[str, Dict[str, Any]]:
"""Load chunking strategy specifications."""
return {
"fixed_size": {"complexity": "low", "quality": "medium"},
"sentence_based": {"complexity": "medium", "quality": "good"},
"paragraph_based": {"complexity": "medium", "quality": "good"},
"semantic_heading_aware": {"complexity": "high", "quality": "excellent"}
}
def _get_vector_db_config(self, db: str, req: Requirements, scale: Scale) -> Dict[str, Any]:
"""Get vector database configuration."""
base_config = {
"collection_name": "rag_documents",
"distance_metric": "cosine",
"index_type": "hnsw"
}
if db == "pinecone":
base_config.update({
"environment": "us-east1-gcp",
"replicas": 1 if scale == Scale.SMALL else 2,
"shards": 1 if scale != Scale.LARGE else 3
})
elif db == "qdrant":
base_config.update({
"memory_mapping": True,
"quantization": scale == Scale.LARGE,
"replication_factor": 1 if scale == Scale.SMALL else 2
})
return base_config
def _get_chunking_pros(self, strategy: str) -> List[str]:
"""Get pros for chunking strategy."""
pros_map = {
"semantic_heading_aware": ["Preserves document structure", "High semantic coherence", "Good for technical docs"],
"paragraph_based": ["Respects natural boundaries", "Good balance", "Readable chunks"],
"sentence_based": ["Natural language boundaries", "Consistent quality", "Good for general text"],
"fixed_size": ["Predictable sizes", "Simple implementation", "Consistent processing"],
"adaptive_chunking": ["Handles mixed content", "Optimizes per document", "Best quality"]
}
return pros_map.get(strategy, ["Good general purpose strategy"])
def _get_chunking_cons(self, strategy: str) -> List[str]:
"""Get cons for chunking strategy."""
cons_map = {
"semantic_heading_aware": ["Complex implementation", "May create large chunks", "Document-dependent"],
"paragraph_based": ["Variable sizes", "May break context", "Document-dependent"],
"sentence_based": ["May create small chunks", "Sentence detection issues", "Variable sizes"],
"fixed_size": ["Breaks semantic boundaries", "May split sentences", "Context loss"],
"adaptive_chunking": ["High complexity", "Slower processing", "Harder to debug"]
}
return cons_map.get(strategy, ["May not fit all use cases"])
def _get_embedding_pros(self, model: str) -> List[str]:
"""Get pros for embedding model."""
if "openai" in model:
return ["High quality", "Regular updates", "Good performance"]
elif "all-mpnet" in model:
return ["High quality", "Free to use", "Good balance"]
elif "MiniLM" in model:
return ["Fast processing", "Small size", "Good for real-time"]
else:
return ["Specialized for domain", "Good performance"]
def _get_embedding_cons(self, model: str) -> List[str]:
"""Get cons for embedding model."""
if "openai" in model:
return ["API costs", "Vendor lock-in", "Rate limits"]
elif "sentence-transformers" in model:
return ["Self-hosting required", "Model updates needed", "GPU beneficial"]
else:
return ["May require fine-tuning", "Domain-specific"]
def _get_vector_db_pros(self, db: str) -> List[str]:
"""Get pros for vector database."""
pros_map = {
"pinecone": ["Fully managed", "Excellent performance", "Auto-scaling"],
"weaviate": ["Rich features", "GraphQL API", "Multi-modal"],
"qdrant": ["High performance", "Rust-based", "Good scaling"],
"chroma": ["Simple setup", "Free", "Good for development"],
"pgvector": ["SQL integration", "ACID compliance", "Familiar"]
}
return pros_map.get(db, ["Good performance"])
def _get_vector_db_cons(self, db: str) -> List[str]:
"""Get cons for vector database."""
cons_map = {
"pinecone": ["Expensive", "Vendor lock-in", "Limited customization"],
"weaviate": ["Complex setup", "Learning curve", "Resource intensive"],
"qdrant": ["Self-managed", "Smaller community", "Setup complexity"],
"chroma": ["Limited scaling", "Not production-ready", "Basic features"],
"pgvector": ["PostgreSQL knowledge needed", "Less specialized", "Manual optimization"]
}
return cons_map.get(db, ["Requires maintenance"])
def _get_retrieval_pros(self, strategy: str) -> List[str]:
"""Get pros for retrieval strategy."""
pros_map = {
"dense": ["Semantic understanding", "Good for paraphrases", "Fast"],
"sparse": ["Exact matching", "Interpretable", "Good for keywords"],
"hybrid": ["Best of both", "High accuracy", "Robust"]
}
return pros_map.get(strategy, ["Good performance"])
def _get_retrieval_cons(self, strategy: str) -> List[str]:
"""Get cons for retrieval strategy."""
cons_map = {
"dense": ["May miss exact matches", "Embedding dependent", "Less interpretable"],
"sparse": ["Vocabulary mismatch", "No semantic understanding", "Synonym issues"],
"hybrid": ["More complex", "Tuning required", "Higher latency"]
}
return cons_map.get(strategy, ["May require tuning"])
def load_requirements(file_path: str) -> Requirements:
"""Load requirements from JSON file."""
with open(file_path, 'r') as f:
data = json.load(f)
return Requirements(**data)
def save_design(design: PipelineDesign, output_path: str):
"""Save pipeline design to JSON file."""
# Convert to dict for JSON serialization
design_dict = {}
for field_name in design.__dataclass_fields__:
value = getattr(design, field_name)
if isinstance(value, ComponentRecommendation):
design_dict[field_name] = asdict(value)
elif value is None:
design_dict[field_name] = None
else:
design_dict[field_name] = value
with open(output_path, 'w') as f:
json.dump(design_dict, f, indent=2)
def print_design_summary(design: PipelineDesign):
"""Print human-readable design summary."""
print("\n" + "="*60)
print("RAG PIPELINE DESIGN SUMMARY")
print("="*60)
print(f"\n💰 Total Monthly Cost: ${design.total_cost:.2f}")
print(f"\n🔧 Component Recommendations:")
components = [design.chunking, design.embedding, design.vector_db,
design.retrieval, design.reranking, design.evaluation]
for component in components:
if component:
print(f"\n {component.type.upper()}: {component.name}")
print(f" Rationale: {component.rationale}")
if component.cost_monthly > 0:
print(f" Monthly Cost: ${component.cost_monthly:.2f}")
print(f"\n📊 Architecture Diagram:")
print(design.architecture_diagram)
def main():
"""Main function with command-line interface."""
parser = argparse.ArgumentParser(description='Design RAG pipeline based on requirements')
parser.add_argument('requirements', help='JSON file containing system requirements')
parser.add_argument('--output', '-o', help='Output file for pipeline design (JSON)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
args = parser.parse_args()
try:
# Load requirements
print("Loading requirements...")
requirements = load_requirements(args.requirements)
# Design pipeline
designer = RAGPipelineDesigner()
design = designer.design_pipeline(requirements)
# Save design
if args.output:
save_design(design, args.output)
print(f"Pipeline design saved to {args.output}")
# Print summary
print_design_summary(design)
if args.verbose:
print(f"\n📋 Configuration Templates:")
for component_type, config in design.config_templates.items():
print(f"\n {component_type.upper()}:")
print(f" {json.dumps(config, indent=4)}")
except Exception as e:
print(f"Error: {e}")
return 1
return 0
if __name__ == '__main__':
exit(main())Chunking Strategies Comparison
Executive Summary
Document chunking is the foundation of effective RAG systems. This analysis compares five primary chunking strategies across key metrics including semantic coherence, boundary quality, processing speed, and implementation complexity.
Strategies Analyzed
1. Fixed-Size Chunking
Approach: Split documents into chunks of predetermined size (characters/tokens) with optional overlap.
Variants:
- Character-based: 512, 1024, 2048 characters
- Token-based: 128, 256, 512 tokens
- Overlap: 0%, 10%, 20%
Performance Metrics:
- Processing Speed: ⭐⭐⭐⭐⭐ (Fastest)
- Boundary Quality: ⭐⭐ (Poor - breaks mid-sentence)
- Semantic Coherence: ⭐⭐ (Low - ignores content structure)
- Implementation: ⭐⭐⭐⭐⭐ (Simplest)
- Memory Efficiency: ⭐⭐⭐⭐⭐ (Predictable sizes)
Best For:
- Large-scale processing where speed is critical
- Uniform document types
- When consistent chunk sizes are required
Avoid When:
- Document quality varies significantly
- Preserving context is critical
- Processing narrative or technical content
2. Sentence-Based Chunking
Approach: Group complete sentences until size threshold reached, ensuring natural language boundaries.
Implementation Details:
- Sentence detection using regex patterns or NLP libraries
- Size limits: 500-1500 characters typically
- Overlap: 1-2 sentences for context preservation
Performance Metrics:
- Processing Speed: ⭐⭐⭐⭐ (Fast)
- Boundary Quality: ⭐⭐⭐⭐ (Good - respects sentence boundaries)
- Semantic Coherence: ⭐⭐⭐ (Medium - sentences may be topically unrelated)
- Implementation: ⭐⭐⭐ (Moderate complexity)
- Memory Efficiency: ⭐⭐⭐ (Variable sizes)
Best For:
- Narrative text (articles, books, blogs)
- General-purpose text processing
- When readability of chunks is important
Avoid When:
- Documents have complex sentence structures
- Technical content with code/formulas
- Very short or very long sentences dominate
3. Paragraph-Based Chunking
Approach: Use paragraph boundaries as primary split points, combining or splitting paragraphs based on size constraints.
Implementation Details:
- Paragraph detection via double newlines or HTML tags
- Size limits: 1000-3000 characters
- Hierarchical splitting for oversized paragraphs
Performance Metrics:
- Processing Speed: ⭐⭐⭐⭐ (Fast)
- Boundary Quality: ⭐⭐⭐⭐⭐ (Excellent - natural breaks)
- Semantic Coherence: ⭐⭐⭐⭐ (Good - paragraphs often topically coherent)
- Implementation: ⭐⭐⭐ (Moderate complexity)
- Memory Efficiency: ⭐⭐ (Highly variable sizes)
Best For:
- Well-structured documents
- Articles and reports with clear paragraphs
- When topic coherence is important
Avoid When:
- Documents have inconsistent paragraph structure
- Paragraphs are extremely long or short
- Technical documentation with mixed content
4. Semantic Chunking (Heading-Aware)
Approach: Use document structure (headings, sections) and semantic similarity to create topically coherent chunks.
Implementation Details:
- Heading detection (markdown, HTML, or inferred)
- Topic modeling for section boundaries
- Recursive splitting respecting hierarchy
Performance Metrics:
- Processing Speed: ⭐⭐ (Slow - requires analysis)
- Boundary Quality: ⭐⭐⭐⭐⭐ (Excellent - respects document structure)
- Semantic Coherence: ⭐⭐⭐⭐⭐ (Excellent - maintains topic coherence)
- Implementation: ⭐⭐ (Complex)
- Memory Efficiency: ⭐⭐ (Highly variable)
Best For:
- Technical documentation
- Academic papers
- Structured reports
- When document hierarchy is important
Avoid When:
- Documents lack clear structure
- Processing speed is critical
- Implementation complexity must be minimized
5. Recursive Chunking
Approach: Hierarchical splitting using multiple strategies, preferring larger chunks when possible.
Implementation Details:
- Try larger chunks first (sections, paragraphs)
- Recursively split if size exceeds threshold
- Fallback hierarchy: document → section → paragraph → sentence → character
Performance Metrics:
- Processing Speed: ⭐⭐ (Slow - multiple passes)
- Boundary Quality: ⭐⭐⭐⭐ (Good - adapts to content)
- Semantic Coherence: ⭐⭐⭐⭐ (Good - preserves context when possible)
- Implementation: ⭐⭐ (Complex logic)
- Memory Efficiency: ⭐⭐⭐ (Optimizes chunk count)
Best For:
- Mixed document types
- When chunk count optimization is important
- Complex document structures
Avoid When:
- Simple, uniform documents
- Real-time processing requirements
- Debugging and maintenance overhead is a concern
Comparative Analysis
Chunk Size Distribution
| Strategy | Mean Size | Std Dev | Min Size | Max Size | Coefficient of Variation |
|---|---|---|---|---|---|
| Fixed-Size | 1000 | 0 | 1000 | 1000 | 0.00 |
| Sentence | 850 | 320 | 180 | 1500 | 0.38 |
| Paragraph | 1200 | 680 | 200 | 3500 | 0.57 |
| Semantic | 1400 | 920 | 300 | 4200 | 0.66 |
| Recursive | 1100 | 450 | 400 | 2000 | 0.41 |
Processing Performance
| Strategy | Processing Speed (docs/sec) | Memory Usage (MB/1K docs) | CPU Usage (%) |
|---|---|---|---|
| Fixed-Size | 2500 | 50 | 15 |
| Sentence | 1800 | 65 | 25 |
| Paragraph | 2000 | 60 | 20 |
| Semantic | 400 | 120 | 60 |
| Recursive | 600 | 100 | 45 |
Quality Metrics
| Strategy | Boundary Quality | Semantic Coherence | Context Preservation |
|---|---|---|---|
| Fixed-Size | 0.15 | 0.32 | 0.28 |
| Sentence | 0.85 | 0.58 | 0.65 |
| Paragraph | 0.92 | 0.75 | 0.78 |
| Semantic | 0.95 | 0.88 | 0.85 |
| Recursive | 0.88 | 0.82 | 0.80 |
Domain-Specific Recommendations
Technical Documentation
Primary: Semantic (heading-aware) Secondary: Recursive Rationale: Technical docs have clear hierarchical structure that should be preserved
Scientific Papers
Primary: Semantic (heading-aware) Secondary: Paragraph-based Rationale: Papers have sections (abstract, methodology, results) that form coherent units
News Articles
Primary: Paragraph-based Secondary: Sentence-based Rationale: Inverted pyramid structure means paragraphs are typically topically coherent
Legal Documents
Primary: Paragraph-based Secondary: Semantic Rationale: Legal text has specific paragraph structures that shouldn't be broken
Code Documentation
Primary: Semantic (code-aware) Secondary: Recursive Rationale: Code blocks, functions, and classes form natural boundaries
General Web Content
Primary: Sentence-based Secondary: Paragraph-based Rationale: Variable quality and structure require robust general-purpose approach
Implementation Guidelines
Choosing Chunk Size
1. Consider retrieval context: Smaller chunks (500-800 chars) for precise retrieval 2. Consider generation context: Larger chunks (1000-2000 chars) for comprehensive answers 3. Model context limits: Ensure chunks fit in embedding model context window 4. Query patterns: Specific queries need smaller chunks, broad queries benefit from larger
Overlap Configuration
- None (0%): When context bleeding is problematic
- Low (5-10%): General-purpose overlap for context continuity
- Medium (15-20%): When context preservation is critical
- High (25%+): Rarely beneficial, increases storage costs significantly
Metadata Preservation
Always preserve:
- Document source/path
- Chunk position/sequence
- Heading hierarchy (if applicable)
- Creation/modification timestamps
Conditionally preserve:
- Page numbers (for PDFs)
- Section titles
- Author information
- Document type/category
Evaluation Framework
Automated Metrics
1. Chunk Size Consistency: Standard deviation of chunk sizes 2. Boundary Quality Score: Fraction of chunks ending with complete sentences 3. Topic Coherence: Average cosine similarity between consecutive chunks 4. Processing Speed: Documents processed per second 5. Memory Efficiency: Peak memory usage during processing
Manual Evaluation
1. Readability: Can humans easily understand chunk content? 2. Completeness: Do chunks contain complete thoughts/concepts? 3. Context Sufficiency: Is enough context preserved for accurate retrieval? 4. Boundary Appropriateness: Do chunk boundaries make semantic sense?
A/B Testing Framework
1. Baseline Setup: Establish current chunking strategy performance 2. Metric Selection: Choose relevant metrics (precision@k, user satisfaction) 3. Sample Size: Ensure statistical significance (typically 1000+ queries) 4. Duration: Run for sufficient time to capture usage patterns 5. Analysis: Statistical significance testing and practical effect size
Cost-Benefit Analysis
Development Costs
- Fixed-Size: 1 developer-day
- Sentence-Based: 3-5 developer-days
- Paragraph-Based: 3-5 developer-days
- Semantic: 10-15 developer-days
- Recursive: 15-20 developer-days
Operational Costs
- Processing overhead: Semantic chunking 3-5x slower than fixed-size
- Storage overhead: Variable-size chunks may waste storage slots
- Maintenance overhead: Complex strategies require more monitoring
Quality Benefits
- Retrieval accuracy improvement: 10-30% for semantic vs fixed-size
- User satisfaction: Measurable improvement with better chunk boundaries
- Downstream task performance: Better chunks improve generation quality
Conclusion
The optimal chunking strategy depends on your specific use case:
- Speed-critical systems: Fixed-size chunking
- General-purpose applications: Sentence-based chunking
- High-quality requirements: Semantic or recursive chunking
- Mixed environments: Adaptive strategy selection
Consider implementing multiple strategies and A/B testing to determine the best approach for your specific document corpus and user queries.
Embedding Model Benchmark 2024
Executive Summary
This comprehensive benchmark evaluates 15 popular embedding models across multiple dimensions including retrieval quality, processing speed, memory usage, and cost. Results are based on evaluation across 5 diverse datasets totaling 2M+ documents and 50K queries.
Models Evaluated
OpenAI Models
- text-embedding-ada-002 (1536 dim) - Latest general-purpose model
- text-embedding-3-small (1536 dim) - Optimized for speed/cost
- text-embedding-3-large (3072 dim) - Maximum quality
Sentence Transformers (Open Source)
- all-mpnet-base-v2 (768 dim) - High-quality general purpose
- all-MiniLM-L6-v2 (384 dim) - Fast and compact
- all-MiniLM-L12-v2 (384 dim) - Better quality than L6
- paraphrase-multilingual-mpnet-base-v2 (768 dim) - Multilingual
- multi-qa-mpnet-base-dot-v1 (768 dim) - Optimized for Q&A
Specialized Models
- sentence-transformers/msmarco-distilbert-base-v4 (768 dim) - Search-optimized
- intfloat/e5-large-v2 (1024 dim) - State-of-the-art open source
- BAAI/bge-large-en-v1.5 (1024 dim) - Chinese team, excellent performance
- thenlper/gte-large (1024 dim) - Recent high-performer
Domain-Specific Models
- microsoft/codebert-base (768 dim) - Code embeddings
- allenai/scibert_scivocab_uncased (768 dim) - Scientific text
- microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract (768 dim) - Biomedical
Evaluation Methodology
Datasets Used
1. MS MARCO Passage Ranking (8.8M passages, 6,980 queries)
- General web search scenarios
- Factual and informational queries
2. Natural Questions (307K passages, 3,452 queries)
- Wikipedia-based question answering
- Natural language queries
3. TREC-COVID (171K scientific papers, 50 queries)
- Biomedical/scientific literature search
- Technical domain knowledge
4. FiQA-2018 (57K forum posts, 648 queries)
- Financial domain question answering
- Domain-specific terminology
5. ArguAna (8.67K arguments, 1,406 queries)
- Counter-argument retrieval
- Reasoning and argumentation
Metrics Calculated
- Retrieval Quality: NDCG@10, MRR@10, Recall@100
- Speed: Queries per second, documents per second (encoding)
- Memory: Peak RAM usage, model size on disk
- Cost: API costs (for commercial models) or compute costs (for self-hosted)
Hardware Setup
- CPU: Intel Xeon Gold 6248 (40 cores)
- GPU: NVIDIA V100 32GB (for transformer models)
- RAM: 256GB DDR4
- Storage: NVMe SSD
Results Overview
Retrieval Quality Rankings
| Rank | Model | NDCG@10 | MRR@10 | Recall@100 | Overall Score |
|---|---|---|---|---|---|
| 1 | text-embedding-3-large | 0.594 | 0.431 | 0.892 | 0.639 |
| 2 | BAAI/bge-large-en-v1.5 | 0.588 | 0.425 | 0.885 | 0.633 |
| 3 | intfloat/e5-large-v2 | 0.582 | 0.419 | 0.878 | 0.626 |
| 4 | text-embedding-ada-002 | 0.578 | 0.415 | 0.871 | 0.621 |
| 5 | thenlper/gte-large | 0.571 | 0.408 | 0.865 | 0.615 |
| 6 | all-mpnet-base-v2 | 0.543 | 0.385 | 0.824 | 0.584 |
| 7 | multi-qa-mpnet-base-dot-v1 | 0.538 | 0.381 | 0.818 | 0.579 |
| 8 | text-embedding-3-small | 0.535 | 0.378 | 0.815 | 0.576 |
| 9 | msmarco-distilbert-base-v4 | 0.529 | 0.372 | 0.805 | 0.569 |
| 10 | all-MiniLM-L12-v2 | 0.498 | 0.348 | 0.765 | 0.537 |
| 11 | all-MiniLM-L6-v2 | 0.476 | 0.331 | 0.738 | 0.515 |
| 12 | paraphrase-multilingual-mpnet | 0.465 | 0.324 | 0.729 | 0.506 |
Speed Performance
| Model | Encoding Speed (docs/sec) | Query Speed (queries/sec) | Latency (ms) |
|---|---|---|---|
| all-MiniLM-L6-v2 | 14,200 | 2,850 | 0.35 |
| all-MiniLM-L12-v2 | 8,950 | 1,790 | 0.56 |
| text-embedding-3-small | 8,500* | 1,700* | 0.59* |
| msmarco-distilbert-base-v4 | 6,800 | 1,360 | 0.74 |
| all-mpnet-base-v2 | 2,840 | 568 | 1.76 |
| multi-qa-mpnet-base-dot-v1 | 2,760 | 552 | 1.81 |
| text-embedding-ada-002 | 2,500* | 500* | 2.00* |
| paraphrase-multilingual-mpnet | 2,650 | 530 | 1.89 |
| thenlper/gte-large | 1,420 | 284 | 3.52 |
| intfloat/e5-large-v2 | 1,380 | 276 | 3.62 |
| BAAI/bge-large-en-v1.5 | 1,350 | 270 | 3.70 |
| text-embedding-3-large | 1,200* | 240* | 4.17* |
*API-based models - speeds include network latency
Memory Usage
| Model | Model Size (MB) | Peak RAM (GB) | GPU VRAM (GB) |
|---|---|---|---|
| all-MiniLM-L6-v2 | 91 | 1.2 | 2.1 |
| all-MiniLM-L12-v2 | 134 | 1.8 | 3.2 |
| msmarco-distilbert-base-v4 | 268 | 2.4 | 4.8 |
| all-mpnet-base-v2 | 438 | 3.2 | 6.4 |
| multi-qa-mpnet-base-dot-v1 | 438 | 3.2 | 6.4 |
| paraphrase-multilingual-mpnet | 438 | 3.2 | 6.4 |
| thenlper/gte-large | 670 | 4.8 | 8.6 |
| intfloat/e5-large-v2 | 670 | 4.8 | 8.6 |
| BAAI/bge-large-en-v1.5 | 670 | 4.8 | 8.6 |
| OpenAI Models | N/A | 0.1 | 0.0 |
Cost Analysis (1M tokens processed)
| Model | Type | Cost per 1M tokens | Monthly Cost (10M tokens) |
|---|---|---|---|
| text-embedding-3-small | API | $0.02 | $0.20 |
| text-embedding-ada-002 | API | $0.10 | $1.00 |
| text-embedding-3-large | API | $1.30 | $13.00 |
| all-MiniLM-L6-v2 | Self-hosted | $0.05 | $0.50 |
| all-MiniLM-L12-v2 | Self-hosted | $0.08 | $0.80 |
| all-mpnet-base-v2 | Self-hosted | $0.15 | $1.50 |
| intfloat/e5-large-v2 | Self-hosted | $0.25 | $2.50 |
| BAAI/bge-large-en-v1.5 | Self-hosted | $0.25 | $2.50 |
| thenlper/gte-large | Self-hosted | $0.25 | $2.50 |
*Self-hosted costs include compute, not including initial setup
Detailed Analysis
Quality vs Speed Trade-offs
High Performance Tier (NDCG@10 > 0.57):
- text-embedding-3-large: Best quality, expensive, slow
- BAAI/bge-large-en-v1.5: Excellent quality, free, moderate speed
- intfloat/e5-large-v2: Great quality, free, moderate speed
Balanced Tier (NDCG@10 = 0.54-0.57):
- all-mpnet-base-v2: Good quality-speed balance, widely adopted
- text-embedding-ada-002: Good quality, reasonable API cost
- multi-qa-mpnet-base-dot-v1: Q&A optimized, good for RAG
Speed Tier (NDCG@10 = 0.47-0.54):
- all-MiniLM-L12-v2: Best small model, good for real-time
- all-MiniLM-L6-v2: Fastest processing, acceptable quality
Domain-Specific Performance
Scientific/Technical Documents (TREC-COVID)
1. allenai/scibert: 0.612 NDCG@10 (+15% vs general models) 2. text-embedding-3-large: 0.589 NDCG@10 3. BAAI/bge-large-en-v1.5: 0.581 NDCG@10
Code Search (Custom CodeSearchNet evaluation)
1. microsoft/codebert-base: 0.547 NDCG@10 (+22% vs general models) 2. text-embedding-ada-002: 0.492 NDCG@10 3. all-mpnet-base-v2: 0.478 NDCG@10
Financial Domain (FiQA-2018)
1. text-embedding-3-large: 0.573 NDCG@10 2. intfloat/e5-large-v2: 0.567 NDCG@10 3. BAAI/bge-large-en-v1.5: 0.561 NDCG@10
Multilingual Capabilities
Tested on translated versions of Natural Questions (Spanish, French, German):
| Model | English NDCG@10 | Multilingual Avg | Degradation |
|---|---|---|---|
| paraphrase-multilingual-mpnet | 0.465 | 0.448 | 3.7% |
| text-embedding-3-large | 0.594 | 0.521 | 12.3% |
| text-embedding-ada-002 | 0.578 | 0.495 | 14.4% |
| intfloat/e5-large-v2 | 0.582 | 0.483 | 17.0% |
Recommendations by Use Case
High-Volume Production Systems
Primary: BAAI/bge-large-en-v1.5
- Excellent quality (2nd best overall)
- No API costs or rate limits
- Reasonable resource requirements
Secondary: intfloat/e5-large-v2
- Very close quality to bge-large
- Active development community
- Good documentation
Cost-Sensitive Applications
Primary: all-MiniLM-L6-v2
- Lowest operational cost
- Fastest processing
- Acceptable quality for many use cases
Secondary: text-embedding-3-small
- Better quality than MiniLM
- Competitive API pricing
- No infrastructure overhead
Maximum Quality Requirements
Primary: text-embedding-3-large
- Best overall quality
- Latest OpenAI technology
- Worth the cost for critical applications
Secondary: BAAI/bge-large-en-v1.5
- Nearly equivalent quality
- No ongoing API costs
- Full control over deployment
Real-Time Applications (< 100ms latency)
Primary: all-MiniLM-L6-v2
- Sub-millisecond inference
- Small memory footprint
- Easy to scale horizontally
Alternative: text-embedding-3-small (if API latency acceptable)
- Better quality than MiniLM
- Reasonable API speed
- No infrastructure management
Domain-Specific Applications
Scientific/Research: 1. Domain-specific model (SciBERT, BioBERT) if available 2. text-embedding-3-large for general scientific content 3. intfloat/e5-large-v2 as open-source alternative
Code/Technical: 1. microsoft/codebert-base for code search 2. text-embedding-ada-002 for mixed code/text 3. all-mpnet-base-v2 for technical documentation
Multilingual: 1. paraphrase-multilingual-mpnet-base-v2 for balanced multilingual 2. text-embedding-3-large with translation pipeline 3. Language-specific models when available
Implementation Guidelines
Model Selection Framework
1. Define Quality Requirements
- Minimum acceptable NDCG@10 threshold
- Critical vs non-critical application
- User tolerance for imperfect results
2. Assess Performance Requirements
- Expected queries per second
- Latency requirements (real-time vs batch)
- Concurrent user load
3. Evaluate Resource Constraints
- Available GPU memory
- CPU capabilities
- Network bandwidth (for API models)
4. Consider Operational Factors
- Team expertise with model deployment
- Monitoring and maintenance capabilities
- Vendor lock-in tolerance
Deployment Patterns
Single Model Deployment:
- Simplest approach
- Choose one model for all use cases
- Optimize infrastructure for that model
Tiered Deployment:
- Fast model for initial filtering (MiniLM)
- High-quality model for reranking (bge-large)
- Balance speed and quality
Domain-Specific Routing:
- Route queries to specialized models
- Code queries → CodeBERT
- Scientific queries → SciBERT
- General queries → general model
A/B Testing Strategy
1. Baseline Establishment
- Current model performance metrics
- User satisfaction baselines
- System performance baselines
2. Gradual Rollout
- 5% traffic to new model initially
- Monitor key metrics closely
- Gradual increase if positive results
3. Key Metrics to Track
- Retrieval quality (NDCG, MRR)
- User engagement (click-through rates)
- System performance (latency, errors)
- Cost metrics (API calls, compute usage)
Future Considerations
Emerging Trends
1. Instruction-Tuned Embeddings: Models fine-tuned for specific instruction types 2. Multimodal Embeddings: Text + image + audio embeddings 3. Extreme Efficiency: Sub-100MB models with competitive quality 4. Dynamic Embeddings: Context-aware embeddings that adapt to queries
Model Evolution Tracking
OpenAI: Regular model updates, expect 2-3 new releases per year Open Source: Rapid innovation, new SOTA models every 3-6 months Specialized Models: Domain-specific models becoming more common
Performance Optimization
1. Quantization: 8-bit and 4-bit quantization for memory efficiency 2. ONNX Optimization: Convert models for faster inference 3. Model Distillation: Create smaller, faster versions of large models 4. Batch Optimization: Optimize for batch processing vs single queries
Conclusion
The embedding model landscape offers excellent options across all use cases:
- Quality Leaders: text-embedding-3-large, bge-large-en-v1.5, e5-large-v2
- Speed Champions: all-MiniLM-L6-v2, text-embedding-3-small
- Cost Optimized: Open source models (bge, e5, mpnet series)
- Specialized: Domain-specific models when available
The key is matching your specific requirements to the right model characteristics. Consider starting with BAAI/bge-large-en-v1.5 as a strong general-purpose choice, then optimize based on your specific needs and constraints.
RAG Evaluation Framework
Overview
Evaluating Retrieval-Augmented Generation (RAG) systems requires a comprehensive approach that measures both retrieval quality and generation performance. This framework provides methodologies, metrics, and tools for systematic RAG evaluation across different stages of the pipeline.
Evaluation Dimensions
1. Retrieval Quality (Information Retrieval Metrics)
Precision@K: Fraction of retrieved documents that are relevant
- Formula:
Precision@K = Relevant Retrieved@K / K - Use Case: Measuring result quality at different cutoff points
- Target Values: >0.7 for K=1, >0.5 for K=5, >0.3 for K=10
Recall@K: Fraction of relevant documents that are retrieved
- Formula:
Recall@K = Relevant Retrieved@K / Total Relevant - Use Case: Measuring coverage of relevant information
- Target Values: >0.8 for K=10, >0.9 for K=20
Mean Reciprocal Rank (MRR): Average reciprocal rank of first relevant result
- Formula:
MRR = (1/Q) × Σ(1/rank_i)where rank_i is position of first relevant result - Use Case: Measuring how quickly users find relevant information
- Target Values: >0.6 for good systems, >0.8 for excellent systems
Normalized Discounted Cumulative Gain (NDCG@K): Position-aware relevance metric
- Formula:
NDCG@K = DCG@K / IDCG@K - Use Case: Penalizing relevant documents that appear lower in rankings
- Target Values: >0.7 for K=5, >0.6 for K=10
2. Generation Quality (RAG-Specific Metrics)
Faithfulness: How well the generated answer is grounded in retrieved context
- Measurement: NLI-based entailment scoring, fact verification
- Implementation: Check if each claim in answer is supported by context
- Target Values: >0.95 for factual systems, >0.85 for general applications
Answer Relevance: How well the generated answer addresses the original question
- Measurement: Semantic similarity between question and answer
- Implementation: Embedding similarity, keyword overlap, LLM-as-judge
- Target Values: >0.8 for focused answers, >0.7 for comprehensive responses
Context Relevance: How relevant the retrieved context is to the question
- Measurement: Relevance scoring of each retrieved chunk
- Implementation: Question-context similarity, manual annotation
- Target Values: >0.7 for average relevance of top-5 chunks
Context Precision: Fraction of relevant sentences in retrieved context
- Measurement: Sentence-level relevance annotation
- Implementation: Binary classification of each sentence's relevance
- Target Values: >0.6 for efficient context usage
Context Recall: Coverage of necessary information for answering the question
- Measurement: Whether all required facts are present in context
- Implementation: Expert annotation or automated fact extraction
- Target Values: >0.8 for comprehensive coverage
3. End-to-End Quality
Correctness: Factual accuracy of the generated answer
- Measurement: Expert evaluation, automated fact-checking
- Implementation: Compare against ground truth, verify claims
- Scoring: Binary (correct/incorrect) or scaled (1-5)
Completeness: Whether the answer addresses all aspects of the question
- Measurement: Coverage of question components
- Implementation: Aspect-based evaluation, expert annotation
- Scoring: Fraction of question aspects covered
Helpfulness: Overall utility of the response to the user
- Measurement: User ratings, task completion rates
- Implementation: Human evaluation, A/B testing
- Scoring: 1-5 Likert scale or thumbs up/down
Evaluation Methodologies
1. Offline Evaluation
Dataset Requirements:
- Diverse query set (100+ queries for statistical significance)
- Ground truth relevance judgments
- Reference answers (for generation evaluation)
- Representative document corpus
Evaluation Pipeline: 1. Query Processing: Standardize query format and preprocessing 2. Retrieval Execution: Run retrieval with consistent parameters 3. Generation Execution: Generate answers using retrieved context 4. Metric Calculation: Compute all relevant metrics 5. Statistical Analysis: Significance testing, confidence intervals
Best Practices:
- Stratify queries by type (factual, analytical, conversational)
- Include edge cases (ambiguous queries, no-answer situations)
- Use multiple annotators with inter-rater agreement analysis
- Regular re-evaluation as system evolves
2. Online Evaluation (A/B Testing)
Metrics to Track:
- User engagement: Click-through rates, time on page
- User satisfaction: Explicit ratings, implicit feedback
- Task completion: Success rates for specific user goals
- System performance: Latency, error rates
Experimental Design:
- Randomized assignment to treatment/control groups
- Sufficient sample size (typically 1000+ users per group)
- Runtime duration (1-4 weeks for stable results)
- Proper randomization and bias mitigation
3. Human Evaluation
Evaluation Aspects:
- Factual Accuracy: Is the information correct?
- Relevance: Does the answer address the question?
- Completeness: Are all aspects covered?
- Clarity: Is the answer easy to understand?
- Conciseness: Is the answer appropriately brief?
Annotation Guidelines:
- Clear scoring rubrics (e.g., 1-5 scales with examples)
- Multiple annotators per sample (typically 3-5)
- Training and calibration sessions
- Regular quality checks and inter-rater agreement
Implementation Framework
1. Automated Evaluation Pipeline
class RAGEvaluator:
def __init__(self, retriever, generator, metrics_config):
self.retriever = retriever
self.generator = generator
self.metrics = self._initialize_metrics(metrics_config)
def evaluate_query(self, query, ground_truth):
# Retrieval evaluation
retrieved_docs = self.retriever.search(query)
retrieval_metrics = self.evaluate_retrieval(
retrieved_docs, ground_truth['relevant_docs']
)
# Generation evaluation
generated_answer = self.generator.generate(query, retrieved_docs)
generation_metrics = self.evaluate_generation(
query, generated_answer, retrieved_docs, ground_truth['answer']
)
return {**retrieval_metrics, **generation_metrics}2. Metric Implementations
Faithfulness Score:
def calculate_faithfulness(answer, context):
# Split answer into claims
claims = extract_claims(answer)
# Check each claim against context
faithful_claims = 0
for claim in claims:
if is_supported_by_context(claim, context):
faithful_claims += 1
return faithful_claims / len(claims) if claims else 0Context Relevance Score:
def calculate_context_relevance(query, contexts):
relevance_scores = []
for context in contexts:
similarity = embedding_similarity(query, context)
relevance_scores.append(similarity)
return {
'average_relevance': mean(relevance_scores),
'top_k_relevance': mean(relevance_scores[:k]),
'relevance_distribution': relevance_scores
}3. Evaluation Dataset Creation
Query Collection Strategies: 1. User Log Analysis: Extract real user queries from production systems 2. Expert Generation: Domain experts create representative queries 3. Synthetic Generation: LLM-generated queries based on document content 4. Community Sourcing: Crowdsourced query collection
Ground Truth Creation: 1. Document Relevance: Expert annotation of relevant documents per query 2. Answer Creation: Expert-written reference answers 3. Aspect Annotation: Mark which aspects of complex questions are addressed 4. Quality Control: Multiple annotators with disagreement resolution
Evaluation Datasets and Benchmarks
1. General Domain Benchmarks
MS MARCO: Large-scale reading comprehension dataset
- 100K real user queries from Bing search
- Passage-level and document-level evaluation
- Both retrieval and generation evaluation supported
Natural Questions: Google search queries with Wikipedia answers
- 307K training examples, 8K development examples
- Natural language questions from real users
- Both short and long answer evaluation
SQUAD 2.0: Reading comprehension with unanswerable questions
- 150K question-answer pairs
- Includes questions that cannot be answered from context
- Tests system's ability to recognize unanswerable queries
2. Domain-Specific Benchmarks
TREC-COVID: Scientific literature search
- 50 queries on COVID-19 research topics
- 171K scientific papers as corpus
- Expert relevance judgments
FiQA: Financial question answering
- 648 questions from financial forums
- 57K financial forum posts as corpus
- Domain-specific terminology and concepts
BioASQ: Biomedical semantic indexing and question answering
- 3K biomedical questions
- PubMed abstracts as corpus
- Expert physician annotations
3. Multilingual Benchmarks
Mr. TyDi: Multilingual question answering
- 11 languages including Arabic, Bengali, Korean
- Wikipedia passages in each language
- Cultural and linguistic diversity testing
MLQA: Cross-lingual question answering
- Questions in one language, answers in another
- 7 languages with all pair combinations
- Tests multilingual retrieval capabilities
Continuous Evaluation Framework
1. Monitoring Pipeline
Real-time Metrics:
- System latency (p50, p95, p99)
- Error rates and failure modes
- User satisfaction scores
- Query volume and patterns
Batch Evaluation:
- Weekly/monthly evaluation on test sets
- Performance trend analysis
- Regression detection
- Model drift monitoring
2. Quality Assurance
Automated Quality Checks:
- Hallucination detection
- Toxicity and bias screening
- Factual consistency verification
- Output format validation
Human Review Process:
- Random sampling of responses (1-5% of production queries)
- Expert review of edge cases and failures
- User feedback integration
- Regular calibration of automated metrics
3. Performance Optimization
A/B Testing Framework:
- Infrastructure for controlled experiments
- Statistical significance testing
- Multi-armed bandit optimization
- Gradual rollout procedures
Feedback Loop Integration:
- User feedback incorporation into training data
- Error analysis and root cause identification
- Iterative improvement processes
- Model fine-tuning based on evaluation results
Tools and Libraries
1. Open Source Tools
RAGAS: RAG Assessment framework
- Comprehensive metric implementations
- Easy integration with popular RAG frameworks
- Support for both synthetic and human evaluation
TruEra TruLens: ML observability for RAG
- Real-time monitoring and evaluation
- Comprehensive metric tracking
- Integration with popular vector databases
LangSmith: LangChain evaluation and monitoring
- End-to-end RAG pipeline evaluation
- Human feedback integration
- Performance analytics and debugging
2. Commercial Solutions
Weights & Biases: ML experiment tracking
- A/B testing infrastructure
- Comprehensive metrics dashboard
- Team collaboration features
Neptune: ML metadata store
- Experiment comparison and analysis
- Model performance monitoring
- Integration with popular ML frameworks
Comet: ML platform for tracking experiments
- Real-time monitoring
- Model comparison and selection
- Automated report generation
Best Practices
1. Evaluation Design
Metric Selection:
- Choose metrics aligned with business objectives
- Use multiple complementary metrics
- Include both automated and human evaluation
- Consider computational cost vs. insight value
Dataset Preparation:
- Ensure representative query distribution
- Include edge cases and failure modes
- Maintain high annotation quality
- Regular dataset updates and validation
2. Statistical Rigor
Sample Sizes:
- Minimum 100 queries for basic evaluation
- 1000+ queries for robust statistical analysis
- Power analysis for A/B testing
- Confidence interval reporting
Significance Testing:
- Use appropriate statistical tests (t-tests, Mann-Whitney U)
- Multiple comparison corrections (Bonferroni, FDR)
- Effect size reporting alongside p-values
- Bootstrap confidence intervals for stability
3. Operational Integration
Automated Pipelines:
- Continuous integration/deployment integration
- Automated regression testing
- Performance threshold enforcement
- Alert systems for quality degradation
Human-in-the-Loop:
- Regular expert review processes
- User feedback collection and analysis
- Annotation quality control
- Bias detection and mitigation
Common Pitfalls and Solutions
1. Evaluation Bias
Problem: Test set not representative of production queries Solution: Continuous test set updates from production data
Problem: Annotator bias in relevance judgments Solution: Multiple annotators, clear guidelines, bias training
2. Metric Gaming
Problem: Optimizing for metrics rather than user satisfaction Solution: Multiple complementary metrics, regular metric validation
Problem: Overfitting to evaluation set Solution: Hold-out validation sets, temporal splits
3. Scale Challenges
Problem: Evaluation becomes too expensive at scale Solution: Sampling strategies, automated metrics, efficient tooling
Problem: Human evaluation bottlenecks Solution: Active learning for annotation, LLM-as-judge validation
Future Directions
1. Advanced Metrics
- Semantic Coherence: Measuring logical flow in generated answers
- Factual Consistency: Cross-document fact verification
- Personalization Quality: User-specific relevance assessment
- Multimodal Evaluation: Text, image, audio integration metrics
2. Automated Evaluation
- LLM-as-Judge: Using large language models for quality assessment
- Adversarial Testing: Systematic stress testing of RAG systems
- Causal Evaluation: Understanding why systems fail
- Real-time Adaptation: Dynamic metric adjustment based on context
3. Holistic Assessment
- User Journey Evaluation: Multi-turn conversation quality
- Task Success Measurement: Goal completion rather than single query
- Temporal Consistency: Performance stability over time
- Fairness and Bias: Systematic bias detection and measurement
Conclusion
Effective RAG evaluation requires a multi-faceted approach combining automated metrics, human judgment, and continuous monitoring. The key principles are:
1. Comprehensive Coverage: Evaluate all pipeline components 2. Multiple Perspectives: Combine different evaluation methodologies 3. Continuous Improvement: Regular evaluation and iteration 4. Business Alignment: Metrics should reflect actual user value 5. Statistical Rigor: Proper experimental design and analysis
This framework provides the foundation for building robust, high-quality RAG systems that deliver real value to users while maintaining reliability and trustworthiness.