
Building Retrieval-Augmented Generation (RAG) Systems: Architecture and Implementation
Retrieval-Augmented Generation (RAG) is an architectural pattern that integrates external document retrieval systems with generative language models. By grounding large language models (LLMs) in specific, verified data sources, RAG addresses key limitations of standard LLMs, such as knowledge cutoff dates, lack of access to proprietary data, and factual hallucinations.
---
The RAG Workflow
A standard RAG pipeline operates in four sequential phases:
``
[User Query]
│
▼
┌──────────────┐ ┌─────────────────┐
│ Retriever │ ◄─── │ Vector DB / KB │
└──────┬───────┘ └─────────────────┘
│ (Fetched Context)
▼
┌──────────────┐
│ Prompt Synth │ ◄─── (System Instructions)
└──────┬───────┘
│ (Augmented Prompt)
▼
┌──────────────┐
│ Generator │ ───► [Grounded Response]
└──────────────┘
`
1. Ingestion & Indexing: Raw documents are partitioned into smaller text segments (chunks), converted into vector representations (embeddings), and stored in a vector database.
2. Retrieval: The user query is vectorized using the same embedding model. The vector database performs a similarity search to return the most relevant text chunks.
3. Context Injection (Augmentation): The retrieved chunks are formatted alongside the original user query into a structured prompt.
4. Generation: The augmented prompt is sent to the LLM, which synthesizes a response based on the provided context.
---
Core Components
1. Ingestion and Indexing Pipeline
- Document Parsers: Tools that convert formats like PDF, HTML, and DOCX into plain text.
- Text Splitters: Algorithms that divide continuous text into manageable segments (chunks). Common strategies include fixed-character splitting with overlap or recursive, syntax-aware splitting (e.g., by headers or paragraphs).
- Embedding Models: Algorithms that convert text chunks into high-dimensional vectors representing semantic meaning (e.g., models from Hugging Face's Sentence Transformers library or commercial APIs).
2. Vector Database (Storage)
Vector databases index high-dimensional embeddings to enable fast, approximate nearest neighbor (ANN) searches. Common choices include:
- Dedicated Vector Databases: Pinecone, Weaviate, Milvus, Qdrant, Chroma.
- Vector Extensions:
pgvector for PostgreSQL, Elasticsearch, Redis.
3. Retrieval Engine
- Dense Retrieval: Semantic search using vector embeddings to find conceptually related documents.
- Sparse Retrieval: Keyword-based search using algorithms like BM25 to locate precise terms, codes, or product IDs.
- Hybrid Search: Combining dense and sparse retrieval scores to improve overall search precision.
4. Generator (LLM)
The LLM processes the augmented prompt to produce the final response. Options range from proprietary cloud APIs (e.g., OpenAI, Anthropic) to self-hosted, open-weights models (e.g., Llama, Mistral) optimized for low latency and privacy.
---
Implementation: Building a Basic RAG Pipeline in Python
Below is a conceptual implementation of a RAG pipeline using standard Python libraries. This example demonstrates how indexing, cosine-similarity retrieval, and context injection function mathematically.
`python
import numpy as np
Mock Embedding Function (For demonstration purposes; in production, use SentenceTransformers or an API)
def mock_embed(text: str) -> np.ndarray:
# Generates a deterministic pseudo-vector based on word presence
words = ["api", "database", "python", "rag", "django", "model", "query", "vector"]
vector = [1.0 if word in text.lower() else 0.0 for word in words]
# Add minor noise to ensure non-zero vectors
vector = np.array(vector) + 0.05
return vector / np.linalg.norm(vector)
Cosine Similarity Calculation
def cosine_similarity(v1: np.ndarray, v2: np.ndarray) -> float:
return float(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
1. Knowledge Base Documents
documents = [
"Django uses WSGI and ASGI interfaces to manage web requests and server communication.",
"A vector database indexes high-dimensional embeddings for fast semantic similarity search.",
"Prompt engineering involves structuring input text to guide LLM behavior predictably.",
"RAG systems combine vector retrieval with language models to reduce factual errors."
]
2. Indexing: Create Embeddings and Store in Memory
vector_index = []
for idx, doc in enumerate(documents):
embedding = mock_embed(doc)
vector_index.append({"id": idx, "text": doc, "vector": embedding})
3. Retrieval Function
def retrieve(query: str, index: list, top_k: int = 1) -> list:
query_vector = mock_embed(query)
scored_documents = []
for item in index:
score = cosine_similarity(query_vector, item["vector"])
scored_documents.append((score, item["text"]))
# Sort by descending similarity score
scored_documents.sort(key=lambda x: x[0], reverse=True)
return scored_documents[:top_k]
4. Generation Pipeline Simulation
def generate_response(query: str, index: list) -> str:
# Retrieve relevant context
retrieved = retrieve(query, index, top_k=1)
context = retrieved[0][1]
score = retrieved[0][0]
# Format the augmented prompt
augmented_prompt = (
f"System: Answer the question using only the provided context.\n"
f"Context: {context}\n"
f"User Query: {query}\n"
f"Answer:"
)
# In a real pipeline, send this augmented_prompt to your LLM API.
return augmented_prompt, score
Example Execution
query = "Explain how RAG improves accuracy."
prompt, similarity_score = generate_response(query, vector_index)
print(f"Retrieved with similarity score: {similarity_score:.4f}\n")
print(prompt)
`
---
Optimizing RAG Performance
To transition from a proof-of-concept to a production-grade RAG system, consider implementing the following optimizations:
1. Chunking Strategy
- Semantic Chunking: Instead of splitting by arbitrary character limits, split text where semantic changes occur (such as section breaks or paragraphs).
- Chunk Overlap: Ensure adjacent chunks share 10–20% text overlap to preserve context that might otherwise be cut in half by splitting boundaries.
2. Re-Ranking (Two-Stage Retrieval)
Standard vector databases optimize for recall (finding all potentially relevant documents). To improve precision, pass the top 10–25 retrieved chunks through a specialized Cross-Encoder Model (a re-ranker). This model calculates a more accurate relevance score between the query and each chunk, allowing you to select only the top 3–5 most relevant segments for the final prompt.
`
[Query] ──► [Dense Retrieval (Top 25)] ──► [Cross-Encoder Re-ranker] ──► [Top 5 to LLM]
``
3. Metadata Filtering
Before performing vector search, restrict the search space by applying hard filters based on document metadata (e.g., date of creation, user permissions, or document category). This prevents irrelevant documents from polluting similarity search results.
4. Query Transformation
User queries are often short or poorly phrased. Implement preprocessing steps to improve retrieval yields:
- Query Expansion: Instruct an LLM to generate multiple alternative versions of the user query before searching the database.
- Sub-Query Decomposition: Break complex, multi-part queries into several distinct search queries, execute them independently, and consolidate the results.
---
Evaluation and Metrics
A robust evaluation framework is necessary to measure performance regressions as the pipeline evolves. Common evaluation paradigms include:
- Retrieval Metrics:
- Hit Rate (Recall@K): The frequency with which the correct source document appears within the top $K$ retrieved chunks.
- Mean Reciprocal Rank (MRR): Measures where the first relevant document appears in the retrieved results list.
- Generation Metrics (Using LLM-as-a-Judge frameworks like Ragas or TruLens):
Faithfulness (Groundedness): Evaluates if the generated answer is derived only* from the provided context.
- Answer Relevance: Evaluates if the response directly addresses the user's question.
- Context Recall: Verifies if the retriever successfully gathered all necessary information required to construct the answer.
---
Conclusion
RAG is an effective pattern for bridging the gap between static model weights and dynamic, domain-specific knowledge bases. The primary key to a reliable RAG deployment lies not only in the capability of the generative LLM, but in the precision, parsing quality, and indexing strategy of the retrieval engine.