Building RAG Systems from Scratch: A Comprehensive Guide

By Anurag Shankar Maurya • Published: 2026-01-19 • Category: Generative AI & LLM Integration • 12 min read
Building RAG Systems from Scratch: A Comprehensive Guide

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:

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: ---

Evaluation and Metrics

A robust evaluation framework is necessary to measure performance regressions as the pipeline evolves. Common evaluation paradigms include: Faithfulness (Groundedness): Evaluates if the generated answer is derived only* from the provided context. ---

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.