<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tech Deep Dives]]></title><description><![CDATA[Tech Deep Dives]]></description><link>https://tech-deep-dives.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/678522a35f05f817899782ef/4becc641-28fb-4f5f-8027-ca6bb7a028f0.webp</url><title>Tech Deep Dives</title><link>https://tech-deep-dives.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 16:34:51 GMT</lastBuildDate><atom:link href="https://tech-deep-dives.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Understanding RAG (Retrieval-Augmented Generation): How AI Looks Things Up Before Answering]]></title><description><![CDATA[Table of Contents

Introduction

The Problem with Pure LLMs

What Is RAG?

How RAG Works: A Deep Dive

Step 1: Document Ingestion and Chunking

Step 2: Embedding Generation

Step 3: Vector Storage

St]]></description><link>https://tech-deep-dives.hashnode.dev/understanding-rag-retrieval-augmented-generation-how-ai-looks-things-up-before-answering</link><guid isPermaLink="true">https://tech-deep-dives.hashnode.dev/understanding-rag-retrieval-augmented-generation-how-ai-looks-things-up-before-answering</guid><category><![CDATA[AI]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[llm]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[nlp]]></category><dc:creator><![CDATA[Ankush Sanyal]]></dc:creator><pubDate>Mon, 20 Apr 2026 17:09:51 GMT</pubDate><content:encoded><![CDATA[<h2>Table of Contents</h2>
<ol>
<li><p><a href="#introduction">Introduction</a></p>
</li>
<li><p><a href="#the-problem-with-pure-llms">The Problem with Pure LLMs</a></p>
</li>
<li><p><a href="#what-is-rag">What Is RAG?</a></p>
</li>
<li><p><a href="#how-rag-works-a-deep-dive">How RAG Works: A Deep Dive</a></p>
<ul>
<li><p><a href="#step-1-document-ingestion-and-chunking">Step 1: Document Ingestion and Chunking</a></p>
</li>
<li><p><a href="#step-2-embedding-generation">Step 2: Embedding Generation</a></p>
</li>
<li><p><a href="#step-3-vector-storage">Step 3: Vector Storage</a></p>
</li>
<li><p><a href="#step-4-query-time-retrieval">Step 4: Query-Time Retrieval</a></p>
</li>
<li><p><a href="#step-5-context-augmentation-and-generation">Step 5: Context Augmentation and Generation</a></p>
</li>
</ul>
</li>
<li><p><a href="#rag-vs-fine-tuning-which-one-do-you-need">RAG vs. Fine-Tuning: Which One Do You Need?</a></p>
</li>
<li><p><a href="#types-of-rag-architectures">Types of RAG Architectures</a></p>
<ul>
<li><p><a href="#naive-rag">Naive RAG</a></p>
</li>
<li><p><a href="#advanced-rag">Advanced RAG</a></p>
</li>
<li><p><a href="#modular-rag">Modular RAG</a></p>
</li>
</ul>
</li>
<li><p><a href="#building-a-rag-pipeline-code-walkthrough">Building a RAG Pipeline: Code Walkthrough</a></p>
</li>
<li><p><a href="#chunking-strategies-that-actually-matter">Chunking Strategies That Actually Matter</a></p>
</li>
<li><p><a href="#embedding-models-choosing-the-right-one">Embedding Models: Choosing the Right One</a></p>
</li>
<li><p><a href="#vector-databases-the-backbone-of-rag">Vector Databases: The Backbone of RAG</a></p>
</li>
<li><p><a href="#retrieval-techniques-beyond-naive-similarity-search">Retrieval Techniques: Beyond Naive Similarity Search</a></p>
</li>
<li><p><a href="#reranking-the-hidden-performance-booster">Reranking: The Hidden Performance Booster</a></p>
</li>
<li><p><a href="#evaluation-how-to-know-if-your-rag-is-working">Evaluation: How to Know If Your RAG is Working</a></p>
</li>
<li><p><a href="#common-pitfalls-and-how-to-avoid-them">Common Pitfalls and How to Avoid Them</a></p>
</li>
<li><p><a href="#real-world-use-cases">Real-World Use Cases</a></p>
</li>
<li><p><a href="#the-future-of-rag">The Future of RAG</a></p>
</li>
<li><p><a href="#conclusion">Conclusion</a></p>
</li>
</ol>
<hr />
<h2>Introduction</h2>
<p>Imagine hiring a brilliant consultant. They are articulate, logical, and incredibly fast. But here is the catch — they have not read a single document from your company. They know a lot about the world in general, but nothing about your internal processes, your proprietary data, your latest quarterly report, or last week's product updates. Every time you ask them something specific to your domain, they either make something up or confess ignorance.</p>
<p>This is, in essence, the situation with a standalone Large Language Model (LLM).</p>
<p>LLMs like GPT-4, Claude, Gemini, or LLaMA are trained on enormous datasets scraped from the internet and various text corpora. They encode a staggering amount of general knowledge into billions of parameters. But they have two fundamental limitations:</p>
<ol>
<li><p><strong>A knowledge cutoff date</strong> — they do not know anything that happened after their training data was collected.</p>
</li>
<li><p><strong>No access to private or specialized knowledge</strong> — they know nothing about your company, your codebase, your customers, or your proprietary documents.</p>
</li>
</ol>
<p><strong>Retrieval-Augmented Generation (RAG)</strong> is the architectural pattern that solves these problems. It gives the AI a way to "look things up" before answering — combining the reasoning power of an LLM with the factual grounding of a searchable knowledge base.</p>
<p>This blog is a comprehensive, ground-up guide to RAG: what it is, why it works, how to build it, and where the field is headed.</p>
<hr />
<h2>The Problem with Pure LLMs</h2>
<p>Before we understand RAG, we need to deeply understand the limitations it addresses.</p>
<h3>1. Hallucination</h3>
<p>LLMs generate text by predicting the most statistically likely next token. When they do not know something, they do not say "I don't know" — they generate a plausible-sounding but often incorrect answer. This phenomenon is called <strong>hallucination</strong>, and it is the single biggest reliability problem with LLMs.</p>
<p>Ask a standard LLM: <em>"What was the revenue of Acme Corp in Q3 2024?"</em> — it will either make up a number or tell you it cannot access financial data. Neither answer is useful.</p>
<h3>2. Knowledge Staleness</h3>
<p>Training an LLM is expensive and time-consuming. As a result, models have a <strong>training cutoff</strong> — a date beyond which they have no knowledge. GPT-4's original cutoff was September 2021. Even modern models lag the present by several months. In fast-moving fields like tech, finance, medicine, or geopolitics, this is a critical problem.</p>
<h3>3. Context Window Limits</h3>
<p>Even if you could somehow cram your entire company knowledge base into a model's context window (the amount of text it can process at once), it would be impractically slow and expensive. Context windows, while growing (128K, 200K, 1M tokens), are not infinite, and processing enormous contexts has quadratic computational cost.</p>
<h3>4. No Private Knowledge</h3>
<p>Your internal Slack messages, engineering documentation, legal contracts, customer support tickets, research notes — none of this is in the LLM. You cannot expect it to answer questions about things it has never seen.</p>
<p>RAG is built specifically to address all four of these problems.</p>
<hr />
<h2>What Is RAG?</h2>
<p><strong>Retrieval-Augmented Generation (RAG)</strong> is an AI architecture that combines two components:</p>
<ol>
<li><p><strong>A retrieval system</strong> — which searches a knowledge base to find relevant documents or passages in response to a user query.</p>
</li>
<li><p><strong>A generative model</strong> — which uses the retrieved documents as context to produce an accurate, grounded response.</p>
</li>
</ol>
<p>The concept was introduced by Facebook AI Research (now Meta AI) in a 2020 paper titled <em>"Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks"</em> by Patrick Lewis et al. It demonstrated that pairing a dense retriever with a sequence-to-sequence generator dramatically improved performance on knowledge-intensive tasks.</p>
<p>The intuition is simple and elegant: instead of forcing the LLM to memorize all facts during training, let it retrieve the relevant facts at inference time and then use its reasoning ability to generate a response based on those facts.</p>
<p>Think of it like the difference between a student who tried to memorize every fact in the textbook (standard LLM) versus a student who is allowed to consult their notes during the exam (RAG). The second student can handle questions about specific, detailed, or recent information far more reliably.</p>
<hr />
<h2>How RAG Works: A Deep Dive</h2>
<p>A RAG system has two distinct phases: <strong>offline indexing</strong> (preparing the knowledge base) and <strong>online inference</strong> (answering queries). Let us walk through each step in detail.</p>
<h3>Step 1: Document Ingestion and Chunking</h3>
<p>The first step is to take your raw documents — PDFs, Word files, web pages, CSVs, markdown files, database records, Slack threads, emails — and ingest them into the system.</p>
<p>Since LLMs have limited context windows and since embedding models work best on short, focused pieces of text, documents are split into <strong>chunks</strong> — smaller units of text. A chunk might be:</p>
<ul>
<li><p>A paragraph</p>
</li>
<li><p>A fixed number of tokens (e.g., 512 tokens)</p>
</li>
<li><p>A semantic section</p>
</li>
<li><p>A sentence or group of sentences</p>
</li>
</ul>
<p>Chunking strategy significantly impacts RAG quality. We will explore this in depth later.</p>
<h3>Step 2: Embedding Generation</h3>
<p>Each chunk is passed through an <strong>embedding model</strong> — a neural network that converts text into a dense numerical vector (a list of floating-point numbers, typically 768 to 3072 dimensions).</p>
<p>The key property of embeddings: <strong>semantically similar text produces numerically similar vectors</strong>. This means that the chunk "myocardial infarction treatment protocols" will produce a vector close to "how to treat a heart attack" in embedding space, even though the words are different.</p>
<p>This is the magic that allows semantic search to work.</p>
<pre><code class="language-python">from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')

chunks = [
    "RAG combines retrieval with generation to ground LLM responses.",
    "Vector databases store embeddings for fast similarity search.",
    "Chunking strategy affects the quality of retrieval in RAG systems."
]

embeddings = model.encode(chunks)
# Each embedding is a 384-dimensional vector
print(embeddings.shape)  # (3, 384)
</code></pre>
<h3>Step 3: Vector Storage</h3>
<p>The embeddings, along with their corresponding text chunks and metadata (source, date, page number, etc.), are stored in a <strong>vector database</strong> (also called a vector store).</p>
<p>Vector databases are optimized for one specific operation: <strong>Approximate Nearest Neighbor (ANN) search</strong> — finding the vectors most similar to a query vector, extremely fast, even across millions of entries.</p>
<p>Popular vector databases include:</p>
<ul>
<li><p><strong>Pinecone</strong> (managed, cloud-native)</p>
</li>
<li><p><strong>Weaviate</strong> (open-source, hybrid search)</p>
</li>
<li><p><strong>Qdrant</strong> (open-source, Rust-based, high performance)</p>
</li>
<li><p><strong>Chroma</strong> (lightweight, great for development)</p>
</li>
<li><p><strong>pgvector</strong> (PostgreSQL extension, great if you already use Postgres)</p>
</li>
<li><p><strong>FAISS</strong> (Facebook's in-memory library, not a full DB but very fast)</p>
</li>
</ul>
<h3>Step 4: Query-Time Retrieval</h3>
<p>When a user asks a question, the system:</p>
<ol>
<li><p><strong>Embeds the query</strong> using the same embedding model used during indexing.</p>
</li>
<li><p><strong>Performs a similarity search</strong> in the vector database to find the top-K chunks whose embeddings are closest to the query embedding.</p>
</li>
<li><p><strong>Returns those chunks</strong> as the retrieval result.</p>
</li>
</ol>
<p>The similarity metric used is typically <strong>cosine similarity</strong> (measuring the angle between two vectors) or <strong>dot product</strong>.</p>
<pre><code class="language-python">import numpy as np

def cosine_similarity(vec_a, vec_b):
    return np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) * np.linalg.norm(vec_b))

query = "What is retrieval-augmented generation?"
query_embedding = model.encode([query])[0]

# Retrieve top-3 most similar chunks
similarities = [cosine_similarity(query_embedding, emb) for emb in embeddings]
top_indices = np.argsort(similarities)[::-1][:3]
retrieved_chunks = [chunks[i] for i in top_indices]
</code></pre>
<h3>Step 5: Context Augmentation and Generation</h3>
<p>The retrieved chunks are inserted into a <strong>prompt</strong> alongside the original user query and sent to the LLM:</p>
<pre><code class="language-plaintext">System: You are a helpful assistant. Answer the user's question using ONLY the context provided below. If the answer is not in the context, say so.

Context:
[CHUNK 1]: RAG combines retrieval with generation to ground LLM responses in factual information.
[CHUNK 2]: Vector databases store embeddings for fast approximate nearest-neighbor search.
[CHUNK 3]: Chunking strategy significantly affects retrieval quality in RAG pipelines.

User Question: What is RAG and how does it store information?
</code></pre>
<p>The LLM then generates a response grounded in the retrieved context, dramatically reducing hallucination.</p>
<hr />
<h2>RAG vs. Fine-Tuning: Which One Do You Need?</h2>
<p>This is one of the most common questions teams face when building AI applications. Let us break it down honestly.</p>
<h3>Fine-Tuning</h3>
<p>Fine-tuning involves continuing the training of an LLM on your specific dataset. The model's weights are updated to "internalize" domain-specific knowledge and style.</p>
<p><strong>When fine-tuning makes sense:</strong></p>
<ul>
<li><p>You need the model to adopt a very specific <strong>tone, format, or writing style</strong> consistently.</p>
</li>
<li><p>You have a very large, stable dataset that changes infrequently.</p>
</li>
<li><p>You want the model to learn domain-specific <strong>reasoning patterns</strong> (e.g., medical diagnosis reasoning).</p>
</li>
<li><p>Latency is critical and you want to reduce prompt size.</p>
</li>
</ul>
<p><strong>Limitations of fine-tuning:</strong></p>
<ul>
<li><p>Expensive (compute and time).</p>
</li>
<li><p>Does not easily incorporate new information without re-training.</p>
</li>
<li><p>Still hallucinates when asked about specific facts not seen enough during training.</p>
</li>
<li><p>Catastrophic forgetting: fine-tuning can degrade the model's general capabilities.</p>
</li>
</ul>
<h3>RAG</h3>
<p>RAG keeps the base model frozen and retrieves information dynamically at inference time.</p>
<p><strong>When RAG makes sense:</strong></p>
<ul>
<li><p>Your knowledge base is large, private, or <strong>frequently updated</strong>.</p>
</li>
<li><p>You need <strong>source citations</strong> and traceable responses.</p>
</li>
<li><p>You want to <strong>avoid hallucinations</strong> on specific factual questions.</p>
</li>
<li><p>You want to add knowledge without the cost of retraining.</p>
</li>
<li><p>Different users need access to different knowledge bases.</p>
</li>
</ul>
<p><strong>Limitations of RAG:</strong></p>
<ul>
<li><p>Adds latency (retrieval + LLM call).</p>
</li>
<li><p>Quality depends heavily on retrieval quality — garbage in, garbage out.</p>
</li>
<li><p>Requires maintaining a vector store infrastructure.</p>
</li>
</ul>
<h3>The Verdict</h3>
<p>In most real-world enterprise applications, <strong>RAG is the first tool to reach for</strong>, not fine-tuning. Fine-tuning is complementary — you fine-tune for style and reasoning, and RAG for knowledge. Many production systems use both.</p>
<table>
<thead>
<tr>
<th>Dimension</th>
<th>Fine-Tuning</th>
<th>RAG</th>
</tr>
</thead>
<tbody><tr>
<td>Knowledge freshness</td>
<td>Static (requires re-training)</td>
<td>Dynamic (update index anytime)</td>
</tr>
<tr>
<td>Hallucination reduction</td>
<td>Moderate</td>
<td>High</td>
</tr>
<tr>
<td>Source attribution</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Cost</td>
<td>High (training + inference)</td>
<td>Moderate (index + inference)</td>
</tr>
<tr>
<td>Latency</td>
<td>Low</td>
<td>Higher (retrieval adds latency)</td>
</tr>
<tr>
<td>Private data support</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Ease of iteration</td>
<td>Slow</td>
<td>Fast</td>
</tr>
</tbody></table>
<hr />
<h2>Types of RAG Architectures</h2>
<p>The RAG landscape has evolved rapidly. Researchers typically categorize RAG systems into three generations.</p>
<h3>Naive RAG</h3>
<p>The original, simplest form of RAG:</p>
<ol>
<li><p>Index documents → chunk → embed → store.</p>
</li>
<li><p>At query time: embed query → retrieve top-K → stuff into prompt → generate.</p>
</li>
</ol>
<p>This works surprisingly well for simple use cases, but has clear limitations:</p>
<ul>
<li><p>Retrieval quality degrades with ambiguous or complex queries.</p>
</li>
<li><p>No query rewriting or reformulation.</p>
</li>
<li><p>No handling of multi-hop reasoning (questions that require connecting multiple documents).</p>
</li>
<li><p>Stuffing raw chunks into the prompt can introduce irrelevant noise.</p>
</li>
</ul>
<h3>Advanced RAG</h3>
<p>Advanced RAG introduces pre-retrieval and post-retrieval enhancements:</p>
<p><strong>Pre-retrieval improvements:</strong></p>
<ul>
<li><p><strong>Query rewriting</strong>: Use an LLM to rephrase the user's query into a cleaner search query.</p>
</li>
<li><p><strong>Query decomposition</strong>: Break complex questions into sub-questions.</p>
</li>
<li><p><strong>HyDE (Hypothetical Document Embeddings)</strong>: Generate a hypothetical answer and embed that instead of the query — since answers are semantically closer to relevant chunks than questions.</p>
</li>
</ul>
<p><strong>Post-retrieval improvements:</strong></p>
<ul>
<li><p><strong>Reranking</strong>: Pass retrieved chunks through a cross-encoder to re-score relevance.</p>
</li>
<li><p><strong>Context compression</strong>: Summarize or extract only the most relevant sentences from each retrieved chunk.</p>
</li>
<li><p><strong>Reciprocal Rank Fusion</strong>: Combine results from multiple retrieval strategies.</p>
</li>
</ul>
<h3>Modular RAG</h3>
<p>Modular RAG treats the system as a set of configurable modules that can be swapped, added, or removed:</p>
<ul>
<li><p>Search module (vector search, keyword search, knowledge graph)</p>
</li>
<li><p>Memory module (conversation history)</p>
</li>
<li><p>Routing module (decide which knowledge base to query)</p>
</li>
<li><p>Fusion module (combine multiple retrieval results)</p>
</li>
<li><p>Predict module (multiple generation strategies)</p>
</li>
</ul>
<p>This is the state-of-the-art for production-grade RAG systems and offers maximum flexibility.</p>
<hr />
<h2>Building a RAG Pipeline: Code Walkthrough</h2>
<p>Let us build a complete, functional RAG pipeline from scratch using Python, LangChain, ChromaDB, and the OpenAI API.</p>
<h3>Installation</h3>
<pre><code class="language-bash">pip install langchain langchain-openai langchain-community chromadb sentence-transformers pypdf
</code></pre>
<h3>Document Loading and Chunking</h3>
<pre><code class="language-python">from langchain.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Load documents
loader = DirectoryLoader('./documents', glob="**/*.pdf", loader_cls=PyPDFLoader)
documents = loader.load()

print(f"Loaded {len(documents)} document pages.")

# Chunk documents
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=64,
    length_function=len,
    separators=["\n\n", "\n", " ", ""]
)

chunks = text_splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks.")
</code></pre>
<h3>Creating the Vector Store</h3>
<pre><code class="language-python">from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

# Initialize embedding model
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Create vector store and index all chunks
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

print("Vector store created and persisted.")
</code></pre>
<h3>Building the Retrieval Chain</h3>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

# Define a custom prompt template
prompt_template = """You are a precise and helpful assistant. Use ONLY the context provided below to answer the question. 
If the answer cannot be found in the context, clearly state that you do not have enough information.
Always cite which part of the context you used.

Context:
{context}

Question: {question}

Detailed Answer:"""

PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# Initialize LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# Build the RAG chain
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
    chain_type_kwargs={"prompt": PROMPT},
    return_source_documents=True
)
</code></pre>
<h3>Running a Query</h3>
<pre><code class="language-python">query = "What are the main components of a RAG system?"

result = qa_chain.invoke({"query": query})

print("Answer:", result["result"])
print("\nSources:")
for doc in result["source_documents"]:
    print(f"  - {doc.metadata.get('source', 'unknown')} (page {doc.metadata.get('page', 'N/A')})")
</code></pre>
<hr />
<h2>Chunking Strategies That Actually Matter</h2>
<p>Chunking is the most underrated decision in RAG system design. Poor chunking = poor retrieval = poor answers. Let us explore your options.</p>
<h3>Fixed-Size Chunking</h3>
<p>Split text into equal-sized chunks by token or character count, with optional overlap.</p>
<pre><code class="language-python">text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=64
)
</code></pre>
<p><strong>Pros:</strong> Simple, fast, consistent.<br /><strong>Cons:</strong> May split sentences or ideas mid-thought.</p>
<h3>Sentence-Level Chunking</h3>
<p>Split at sentence boundaries using NLP tools.</p>
<pre><code class="language-python">import spacy
nlp = spacy.load("en_core_web_sm")

def sentence_chunk(text, max_sentences=5):
    doc = nlp(text)
    sentences = [str(sent) for sent in doc.sents]
    chunks = []
    for i in range(0, len(sentences), max_sentences):
        chunks.append(" ".join(sentences[i:i+max_sentences]))
    return chunks
</code></pre>
<p><strong>Pros:</strong> Preserves semantic units (sentences). Better for factual retrieval.<br /><strong>Cons:</strong> Variable chunk size.</p>
<h3>Semantic Chunking</h3>
<p>Use an embedding model to detect where the semantic meaning of the text shifts. Chunk at those boundaries.</p>
<pre><code class="language-python">from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile"
)
chunks = splitter.split_text(long_text)
</code></pre>
<p><strong>Pros:</strong> Chunks are semantically coherent — each chunk is "about one thing."<br /><strong>Cons:</strong> More expensive (requires embedding at indexing time).</p>
<h3>Hierarchical / Parent-Child Chunking</h3>
<p>Store large "parent" chunks but retrieve small "child" chunks. When a child chunk is retrieved, expand it to the full parent for LLM context.</p>
<pre><code class="language-python">from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)

store = InMemoryStore()
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)
</code></pre>
<p>This gives you <strong>precise retrieval</strong> (small child chunks embed well) with <strong>rich context</strong> (large parent chunks give the LLM more information).</p>
<hr />
<h2>Embedding Models: Choosing the Right One</h2>
<p>Your choice of embedding model directly determines retrieval quality. Here is a practical guide.</p>
<h3>OpenAI Embeddings</h3>
<ul>
<li><p><strong>text-embedding-3-small</strong>: 1536 dimensions, excellent quality, fast, affordable (~$0.02/1M tokens). Good default choice.</p>
</li>
<li><p><strong>text-embedding-3-large</strong>: 3072 dimensions, best OpenAI quality, higher cost.</p>
</li>
</ul>
<h3>Open-Source / Self-Hosted Options</h3>
<table>
<thead>
<tr>
<th>Model</th>
<th>Dimensions</th>
<th>Best For</th>
</tr>
</thead>
<tbody><tr>
<td><code>all-MiniLM-L6-v2</code></td>
<td>384</td>
<td>Fast, lightweight, good general quality</td>
</tr>
<tr>
<td><code>all-mpnet-base-v2</code></td>
<td>768</td>
<td>Better quality, still fast</td>
</tr>
<tr>
<td><code>BAAI/bge-large-en-v1.5</code></td>
<td>1024</td>
<td>State-of-the-art open-source, English</td>
</tr>
<tr>
<td><code>intfloat/e5-mistral-7b-instruct</code></td>
<td>4096</td>
<td>Highest quality, requires GPU</td>
</tr>
<tr>
<td><code>nomic-embed-text</code></td>
<td>768</td>
<td>Long context (8192 tokens), open-source</td>
</tr>
</tbody></table>
<p><strong>Key principle: Use the same model for both indexing and querying.</strong> A vector created by model A is not comparable to a vector created by model B.</p>
<p><strong>Domain matters:</strong> For legal, medical, or scientific documents, consider fine-tuned domain-specific embeddings, or at minimum evaluate a few general models on your specific data before committing.</p>
<hr />
<h2>Vector Databases: The Backbone of RAG</h2>
<p>Vector databases are specialized storage and retrieval systems built around embedding vectors. Here is how to choose between them.</p>
<h3>Chroma</h3>
<p>Best for: Development, prototyping, small-scale projects.</p>
<pre><code class="language-python">from chromadb import Client
client = Client()
collection = client.create_collection("my_rag_collection")
collection.add(
    embeddings=embeddings_list,
    documents=texts,
    ids=["id1", "id2", "id3"]
)
results = collection.query(query_embeddings=[query_emb], n_results=5)
</code></pre>
<h3>Pinecone</h3>
<p>Best for: Production at scale, managed infrastructure, no ops burden.</p>
<pre><code class="language-python">from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_KEY")
index = pc.Index("your-index-name")

# Upsert vectors
index.upsert(vectors=[
    {"id": "chunk-1", "values": embedding_1, "metadata": {"text": "...", "source": "doc.pdf"}},
])

# Query
results = index.query(vector=query_embedding, top_k=5, include_metadata=True)
</code></pre>
<h3>Qdrant</h3>
<p>Best for: High performance self-hosted deployments, rich filtering capabilities.</p>
<pre><code class="language-python">from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

client = QdrantClient("localhost", port=6333)
client.create_collection(
    collection_name="rag_docs",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)
</code></pre>
<h3>pgvector</h3>
<p>Best for: Teams already using PostgreSQL who want to avoid new infrastructure.</p>
<pre><code class="language-sql">-- Enable extension
CREATE EXTENSION vector;

-- Create table with vector column
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(1536)
);

-- Create index for fast search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

-- Query nearest neighbors
SELECT content, 1 - (embedding &lt;=&gt; '[0.1, 0.2, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding &lt;=&gt; '[0.1, 0.2, ...]'::vector
LIMIT 5;
</code></pre>
<hr />
<h2>Retrieval Techniques: Beyond Naive Similarity Search</h2>
<p>Vanilla vector search is a great start, but production systems often need more sophisticated retrieval.</p>
<h3>Hybrid Search (Vector + BM25)</h3>
<p>Combine dense vector search (semantic) with sparse keyword search (BM25/TF-IDF). This handles both conceptual similarity and exact keyword matching.</p>
<pre><code class="language-python">from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# BM25 (keyword) retriever
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 5

# Vector (semantic) retriever
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# Ensemble: 40% BM25, 60% vector
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]
)
</code></pre>
<p>Hybrid search is particularly effective when users use exact technical terms, product names, or codes.</p>
<h3>Multi-Query Retrieval</h3>
<p>Generate multiple variations of the user's question and retrieve chunks for all of them.</p>
<pre><code class="language-python">from langchain.retrievers.multi_query import MultiQueryRetriever

retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=llm
)
# Internally generates 3-5 reformulations of the query and unions the results
docs = retriever.get_relevant_documents("What is RAG?")
</code></pre>
<h3>HyDE (Hypothetical Document Embeddings)</h3>
<p>Instead of embedding the query, prompt the LLM to generate a hypothetical answer and embed that.</p>
<pre><code class="language-python">from langchain.chains import HypotheticalDocumentEmbedder

hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
    llm=llm,
    base_embeddings=embeddings,
    custom_prompt="Write a detailed paragraph answering this question: {question}"
)
hyde_vectorstore = Chroma.from_documents(chunks, hyde_embeddings)
</code></pre>
<p>The intuition: answers are semantically more similar to relevant documents than questions are. Embedding a hypothetical answer rather than the question often produces better retrieval.</p>
<hr />
<h2>Reranking: The Hidden Performance Booster</h2>
<p>After initial retrieval, retrieved chunks can be reranked using a <strong>cross-encoder</strong> — a model that takes (query, chunk) pairs and outputs a fine-grained relevance score.</p>
<p>Cross-encoders are slower than bi-encoders (can't pre-compute) but dramatically more accurate at ranking.</p>
<pre><code class="language-python">from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

query = "How does RAG reduce hallucination?"
retrieved_docs = retriever.get_relevant_documents(query)

# Score each retrieved document against the query
pairs = [(query, doc.page_content) for doc in retrieved_docs]
scores = reranker.predict(pairs)

# Sort by score (descending) and take top-3
ranked = sorted(zip(scores, retrieved_docs), key=lambda x: x[0], reverse=True)
top_docs = [doc for _, doc in ranked[:3]]
</code></pre>
<p><strong>Typical workflow:</strong> Retrieve top-20 with fast vector search → rerank to top-5 with cross-encoder → pass to LLM.</p>
<p>Cohere's Rerank API is a popular managed option for this step.</p>
<hr />
<h2>Evaluation: How to Know If Your RAG is Working</h2>
<p>RAG systems are notoriously hard to evaluate because quality is multidimensional. The main framework for RAG evaluation is <strong>RAGAS</strong> (RAG Assessment).</p>
<p>RAGAS measures four metrics:</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Definition</th>
<th>What It Measures</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Faithfulness</strong></td>
<td>Is the answer supported by the retrieved context?</td>
<td>Hallucination</td>
</tr>
<tr>
<td><strong>Answer Relevancy</strong></td>
<td>Does the answer address the question asked?</td>
<td>Response quality</td>
</tr>
<tr>
<td><strong>Context Precision</strong></td>
<td>Are the retrieved chunks actually relevant?</td>
<td>Retrieval precision</td>
</tr>
<tr>
<td><strong>Context Recall</strong></td>
<td>Were all relevant chunks retrieved?</td>
<td>Retrieval coverage</td>
</tr>
</tbody></table>
<pre><code class="language-python">from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset

# Prepare evaluation data
eval_data = {
    "question": ["What is RAG?", "How does chunking affect RAG?"],
    "answer": [generated_answer_1, generated_answer_2],
    "contexts": [retrieved_chunks_1, retrieved_chunks_2],
    "ground_truth": ["RAG combines retrieval...", "Chunking affects..."]
}

dataset = Dataset.from_dict(eval_data)
results = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
print(results)
</code></pre>
<h3>Human Evaluation Checklist</h3>
<p>Beyond automated metrics, consider rating your system on:</p>
<ul>
<li><p><strong>Correctness</strong>: Is the factual content accurate?</p>
</li>
<li><p><strong>Completeness</strong>: Does the answer cover all aspects of the question?</p>
</li>
<li><p><strong>Coherence</strong>: Is the answer well-structured and readable?</p>
</li>
<li><p><strong>Attribution</strong>: Are sources correctly cited?</p>
</li>
<li><p><strong>Refusal accuracy</strong>: Does the system correctly decline to answer when the context doesn't contain the information?</p>
</li>
</ul>
<hr />
<h2>Common Pitfalls and How to Avoid Them</h2>
<h3>Pitfall 1: Too-Large Chunks</h3>
<p>Large chunks (&gt;1000 tokens) embed to vectors that represent many different topics. This hurts precision — the retrieved chunk will contain lots of irrelevant content along with the relevant part.</p>
<p><strong>Fix:</strong> Use smaller chunks (200-500 tokens) with overlap. Use parent-child retrieval to get rich context despite small chunks.</p>
<h3>Pitfall 2: Too-Small Chunks</h3>
<p>Chunks of 50-100 tokens lack enough context for the embedding to be meaningful. A single sentence may be ambiguous without surrounding context.</p>
<p><strong>Fix:</strong> Aim for chunks that represent a complete idea — a paragraph or a logical unit.</p>
<h3>Pitfall 3: Inconsistent Embedding Models</h3>
<p>Using different models for indexing and querying produces garbage results — the vector spaces are incompatible.</p>
<p><strong>Fix:</strong> Lock down your embedding model. Version it. Never change it without re-indexing everything.</p>
<h3>Pitfall 4: Ignoring Metadata Filtering</h3>
<p>Users often ask questions with implicit filters: "What did we discuss in Q3?" or "Show me policies from the HR department." Without metadata filtering, your retriever searches everything.</p>
<p><strong>Fix:</strong> Store metadata (date, source, author, department, document type) alongside embeddings and filter at retrieval time.</p>
<h3>Pitfall 5: Not Handling "I Don't Know"</h3>
<p>If the retrieved context does not contain the answer, the LLM may still hallucinate. Your prompt must explicitly instruct the model to say when it does not know.</p>
<p><strong>Fix:</strong> Add explicit instructions in your prompt: "If the answer is not in the context provided, say 'I don't have enough information to answer this question.'"</p>
<h3>Pitfall 6: Skipping Evaluation</h3>
<p>Many teams ship RAG systems without any systematic evaluation, discovering failures only through user complaints.</p>
<p><strong>Fix:</strong> Build a golden dataset of question-answer pairs from your domain. Run RAGAS or similar before every significant change.</p>
<hr />
<h2>Real-World Use Cases</h2>
<h3>Enterprise Knowledge Management</h3>
<p>Companies with thousands of internal documents — policies, procedures, product specs, past projects — build RAG systems that let employees ask natural language questions and get accurate, cited answers. Law firms use it for case research. Consulting firms for institutional knowledge retrieval.</p>
<h3>Customer Support Automation</h3>
<p>Train RAG on your product documentation, FAQ, and past support tickets. The system retrieves relevant help articles and generates a precise, contextual answer. Escalation rates drop. Resolution times shrink.</p>
<h3>Medical Information Retrieval</h3>
<p>Hospitals and pharma companies build RAG systems over clinical guidelines, drug interaction databases, and research papers. Clinicians can query in plain language: "What are the contraindications for metformin in patients with renal impairment?"</p>
<h3>Code Documentation Q&amp;A</h3>
<p>Large codebases are indexed: source files, README files, API docs, commit messages. Engineers ask: "How does the authentication middleware work?" and get answers with file paths and line references.</p>
<h3>Financial Analysis</h3>
<p>Investment firms index earnings reports, regulatory filings, analyst reports, and news. Analysts query: "What risks did management cite in Q2 2024 earnings calls?"</p>
<h3>Education and E-Learning</h3>
<p>Educational platforms index textbooks and lecture notes. Students ask questions and get answers with source references, enabling active learning and reducing dependence on search engines.</p>
<hr />
<h2>The Future of RAG</h2>
<h3>GraphRAG</h3>
<p>Microsoft Research introduced GraphRAG — building a knowledge graph from documents and retrieving from the graph rather than flat chunks. This dramatically improves performance on questions requiring multi-hop reasoning ("What is the relationship between X and Y?") and global questions ("What are the main themes across all these documents?").</p>
<h3>Agentic RAG</h3>
<p>Instead of a single retrieval step, agentic RAG systems use an LLM agent that iteratively decides what to retrieve, evaluates whether it has enough information, and retrieves again if needed. This handles complex, multi-step queries.</p>
<pre><code class="language-plaintext">User: Compare the revenue growth of Apple and Microsoft over the past 5 years
Agent → retrieve Apple financials → retrieve Microsoft financials → synthesize comparison → answer
</code></pre>
<h3>Multimodal RAG</h3>
<p>Extending RAG to handle images, audio, and video alongside text. A medical RAG system might retrieve both text descriptions and actual scan images. A legal RAG system might retrieve both document text and signatures or diagrams.</p>
<h3>Long-Context RAG</h3>
<p>As context windows grow (Gemini 1.5 Pro's 1M token window), some are questioning whether RAG will become unnecessary. But retrieval remains valuable even with large context windows — for cost efficiency, focus, and latency. The two approaches are converging rather than one replacing the other.</p>
<h3>RAG with Structured Data</h3>
<p>Combining vector search with SQL or graph queries for systems that need both unstructured (documents) and structured (databases) knowledge. Natural language queries get parsed into SQL + vector queries and results are fused.</p>
<hr />
<h2>Conclusion</h2>
<p>Retrieval-Augmented Generation is not just a technique — it is a fundamental shift in how we think about AI systems and knowledge.</p>
<p>Pure LLMs are like brilliant but amnesiac experts: they reason beautifully but cannot look anything up. RAG gives them a library card. It grounds their eloquence in fact, their confidence in evidence, and their answers in source-traceable information.</p>
<p>The key ideas to take away:</p>
<ul>
<li><p><strong>RAG = Retrieval + Generation</strong>: A knowledge base is searched at query time, and retrieved chunks are fed as context to an LLM.</p>
</li>
<li><p><strong>Chunking strategy matters more than most teams realize.</strong> Invest time here.</p>
</li>
<li><p><strong>Your embedding model is a foundational decision.</strong> Evaluate before committing.</p>
</li>
<li><p><strong>Hybrid search (vector + BM25) outperforms pure vector search in most production scenarios.</strong></p>
</li>
<li><p><strong>Reranking is a high-leverage, low-effort improvement.</strong> Add it before adding more complex architecture.</p>
</li>
<li><p><strong>Evaluate systematically.</strong> Use RAGAS or a custom golden dataset.</p>
</li>
<li><p><strong>The field is evolving rapidly</strong> — GraphRAG, agentic RAG, and multimodal RAG are reshaping what is possible.</p>
</li>
</ul>
<p>If you are building anything knowledge-intensive with AI — chatbots, search systems, document QA, internal tools — RAG is your most reliable foundation. Start simple, measure relentlessly, and layer in complexity only where the metrics justify it.</p>
<hr />
<p><em>This blog was written as a comprehensive technical reference. The field evolves rapidly; always check the latest papers, benchmarks, and library documentation for the most current best practices.</em></p>
]]></content:encoded></item></channel></rss>