<?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[Parikshit | AI Systems]]></title><description><![CDATA[Production-Grade RAG System | LLM, Retrieval & AI Engineering | MLOps ]]></description><link>https://parikshitiiitb.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69e26902fd22b8ad6257df91/906071df-9e83-4709-8c71-9ccfdc772666.png</url><title>Parikshit | AI Systems</title><link>https://parikshitiiitb.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 09:16:15 GMT</lastBuildDate><atom:link href="https://parikshitiiitb.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[RAG Chunking Strategies: What Actually Works in Production (And What Quietly Destroys Retrieval Quality)]]></title><description><![CDATA[Learn RAG chunking strategies for production LLM systems. Improve retrieval quality, reduce hallucinations, and optimize semantic search pipelines]]></description><link>https://parikshitiiitb.hashnode.dev/rag-chunking-strategies-what-actually-works-in-production-and-what-quietly-destroys-retrieval-quality</link><guid isPermaLink="true">https://parikshitiiitb.hashnode.dev/rag-chunking-strategies-what-actually-works-in-production-and-what-quietly-destroys-retrieval-quality</guid><category><![CDATA[AI]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[mlops]]></category><category><![CDATA[ML]]></category><category><![CDATA[chunking]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[vector database]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Parikshit Sharma]]></dc:creator><pubDate>Wed, 22 Apr 2026 05:52:30 GMT</pubDate><content:encoded><![CDATA[<p><em>Every RAG tutorial shows you fixed-size chunking in five lines of code. Nobody shows you what happens six months later when your retrieval quality has collapsed and you can't figure out why. This article is about that. And more importantly how to avoid it before it happens.</em></p>
<hr />
<h3>TL;DR</h3>
<ul>
<li><p><strong>Chunking determines retrieval quality</strong> more than your choice of embeddings.</p>
</li>
<li><p><strong>Fixed-size chunking fails silently</strong> in production by severing context.</p>
</li>
<li><p><strong>Structure-aware chunking</strong> is the default for technical documentation.</p>
</li>
<li><p><strong>Evaluate retrieval at the chunk level</strong> (Context Recall/Precision), not just the final answer.</p>
</li>
<li><p><strong>Use MMR (Maximal Marginal Relevance)</strong> instead of manual deduplication to handle overlaps.</p>
</li>
</ul>
<hr />
<blockquote>
<p><strong>Most RAG systems don’t fail because of the model. They fail because of how the data is chunked.</strong></p>
</blockquote>
<hr />
<h3>The Failure That Started This</h3>
<p>In one system I worked on where Precision looked fine in evals, but engineers started complaining that answers were wrong.</p>
<p>The root cause: a Python function definition—signature, docstring, and implementation was split across two chunks at a 512-token boundary. The retriever found the signature chunk, but the logic needed to answer the query was in the second chunk, which was too far away in embedding space to be retrieved. This led to the model hallucinating the missing context with complete confidence.</p>
<p>Chunking failures are insidious because they don't throw errors the system keeps running while answers quietly degrade.</p>
<hr />
<h3>Why Chunking Is the Highest-Leverage Decision in a RAG Pipeline</h3>
<p>Most teams optimize in the wrong order, swapping embedding models before fixing chunking. Chunking determines the units of meaning your retrieval system works with. Every downstream component (retrievers, rerankers, and the LLM) is bounded by the quality of your chunks.</p>
<p>There are two critical failure modes:</p>
<ul>
<li><p><strong>Precision failure:</strong> Chunks are too large and contain multiple independent ideas, leading to noise.</p>
</li>
<li><p><strong>Context integrity failure:</strong> Chunks are too small, causing a single coherent thought to be severed.</p>
</li>
</ul>
<hr />
<h3>Strategy 1: Fixed-Size Chunking</h3>
<h4>What it is</h4>
<p>Splitting text every \(N\) tokens with \(M\) tokens of overlap.</p>
<pre><code class="language-python">from langchain.text_splitter import RecursiveCharacterTextSplitter
import tiktoken

def token_length(text: str, model: str = "gpt-4") -&gt; int:
    enc = tiktoken.encoding_for_model(model)
    return len(enc.encode(text))

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=51,
    length_function=token_length, 
    separators=["\n\n", "\n", ". ", " ", ""]
)
</code></pre>
<p>💡 <strong>Fixed-size chunking optimizes for simplicity, not meaning.</strong></p>
<h4>When it works</h4>
<ul>
<li><p>Narrative prose where meaning flows continuously.</p>
</li>
<li><p>Building an initial baseline for reproducibility.</p>
</li>
</ul>
<h4>When it quietly fails</h4>
<ul>
<li><p><strong>Split code signatures:</strong> Cutting straight through function names.</p>
</li>
<li><p><strong>Table destruction:</strong> Splitting Markdown/HTML tables makes them semantically meaningless.</p>
</li>
</ul>
<hr />
<h3>Strategy 2: Semantic Chunking</h3>
<h4>What it is</h4>
<p>Embedding every sentence and splitting where the embedding distance between adjacent sentences "spikes."</p>
<p>💡 <strong>You are trading compute at indexing time for retrieval quality at query time.</strong></p>
<h4>When it works</h4>
<ul>
<li><p>Technical documentation where topic shifts are semantically clear.</p>
</li>
<li><p>Multi-topic documents where fixed-size chunking would straddle unrelated concepts.</p>
</li>
</ul>
<h4>When it quietly fails</h4>
<ul>
<li><p><strong>Indexing Cost:</strong> High latency due to embedding every sentence before indexing.</p>
</li>
<li><p><strong>Size Inconsistency:</strong> Chunks can range from one sentence to several paragraphs.</p>
</li>
</ul>
<hr />
<h3>Strategy 3: Document-Structure-Aware Chunking</h3>
<h4>What it is</h4>
<p>Parsing structural elements like headings, code blocks, and tables to define boundaries.</p>
<pre><code class="language-python">from langchain.text_splitter import MarkdownHeaderTextSplitter

headers_to_split_on = [("#", "h1"), ("##", "h2"), ("###", "h3")]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
chunks = splitter.split_text(markdown_doc)
</code></pre>
<p>💡 <strong>This is the default choice for production systems handling technical content.</strong></p>
<h4>When it works</h4>
<ul>
<li><p>Corpora with predictable structure (READMEs, API docs).</p>
</li>
<li><p><strong>Metadata Power-Up:</strong> Hierarchy metadata (e.g., <code>{"h1": "Auth"}</code>) allows for metadata filtering, reducing search space by 40-60%.</p>
</li>
</ul>
<hr />
<h3>Strategy 4: Late Chunking</h3>
<h4>What it is</h4>
<p>Instead of splitting first and embedding later, you embed the entire document first and then chunk the embeddings. This ensures each individual chunk retains the full document's context.</p>
<h4>When it works</h4>
<ul>
<li>Long technical documents with dense cross-references and pronouns.</li>
</ul>
<h4>When it quietly fails</h4>
<ul>
<li><p><strong>Infrastructure Lock-in:</strong> Requires specific models (like Hugging Face TEI) that expose hidden states.</p>
</li>
<li><p><strong>Context Limits:</strong> Limited by the embedding model's maximum input window (e.g., 8k tokens).</p>
</li>
</ul>
<hr />
<h3>Strategy 5: Proposition-Based (Atomic Fact) Chunking</h3>
<h4>What it is</h4>
<p>Using an LLM to decompose text into atomic, self-contained factual statements.</p>
<h4>When it works</h4>
<ul>
<li>High-precision QA systems where a wrong answer is unacceptable.</li>
</ul>
<h4>When it quietly fails</h4>
<ul>
<li><p><strong>LLM Cost:</strong> Decomposing large corpora can be significantly more expensive than standard indexing.</p>
</li>
<li><p><strong>Loss of Sequence:</strong> Procedural or step-by-step content loses ordering and context.</p>
</li>
</ul>
<hr />
<h3>The System Design Decision Matrix</h3>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>Cost</th>
<th>Precision</th>
<th>Consistency</th>
<th>Best For</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Fixed-Size</strong></td>
<td>Low</td>
<td>Low</td>
<td>High</td>
<td>General narrative prose</td>
</tr>
<tr>
<td><strong>Semantic</strong></td>
<td>Medium</td>
<td>High</td>
<td>Low</td>
<td>Multi-topic guides</td>
</tr>
<tr>
<td><strong>Structure-Aware</strong></td>
<td>Low</td>
<td>Very High</td>
<td>Medium</td>
<td><strong>The Production Default</strong></td>
</tr>
<tr>
<td><strong>Late Chunking</strong></td>
<td>High</td>
<td>Ultra High</td>
<td>Medium</td>
<td>Dense cross-references</td>
</tr>
<tr>
<td><strong>Propositional</strong></td>
<td>Very High</td>
<td>Maximum</td>
<td>Low</td>
<td>Factual QA (Legal/Medical)</td>
</tr>
</tbody></table>
<hr />
<h3>The Failure You Haven't Thought About Yet: Eval Debt</h3>
<p>If you’re not measuring context recall and precision, you are debugging blind. End-to-end evals mask chunking failures. You need <strong>chunk-level metrics</strong> via frameworks like <strong>Ragas</strong>:</p>
<ul>
<li><p><strong>Low Context Recall:</strong> You are missing relevant chunks (context integrity failure).</p>
</li>
<li><p><strong>Low Context Precision:</strong> Relevant info is buried in noise (chunks are too large).</p>
</li>
</ul>
<h4>The Overlap Trap</h4>
<p>Overlap causes redundant retrieval. Use <strong>Maximal Marginal Relevance (MMR)</strong> in your vector database to maximize both relevance and diversity in the results.</p>
<hr />
<h3>Conclusion</h3>
<p>Chunking is infrastructure, not configuration.</p>
<p>The choice you make at indexing time defines the ceiling of your entire RAG system. Get it wrong, and no amount of model tuning will save you. Get it right, and everything downstream becomes easier.</p>
<p>Get it wrong, and your retriever will lie to you quietly.</p>
<hr />
<h3>About the Author</h3>
<p><em>Parikshit is a Senior AI/ML Engineer specializing in Generative AI, LLM systems, and MLOps. He focuses on building scalable API architectures and optimizing high-throughput inference pipelines.</em> <a href="https://github.com/parikshitiiitb"><em>GitHub</em></a> <em>·</em> <a href="https://linkedin.com/in/parikshitiiitb"><em>LinkedIn</em></a></p>
]]></content:encoded></item><item><title><![CDATA[Production RAG System Design: Retrieval Quality, Hallucination, and Latency in LLMs]]></title><description><![CDATA[Retrieval-Augmented Generation (RAG) systems are widely used to improve the accuracy of Large Language Models (LLMs) by grounding responses in external…]]></description><link>https://parikshitiiitb.hashnode.dev/production-rag-system-design-retrieval-quality-hallucination-and-latency-in-llms</link><guid isPermaLink="true">https://parikshitiiitb.hashnode.dev/production-rag-system-design-retrieval-quality-hallucination-and-latency-in-llms</guid><category><![CDATA[RAG ]]></category><category><![CDATA[llm]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Parikshit Sharma]]></dc:creator><pubDate>Mon, 20 Apr 2026 19:25:50 GMT</pubDate><content:encoded><![CDATA[<p>Retrieval-Augmented Generation (RAG) systems are widely used to improve the accuracy of Large Language Models (LLMs) by grounding responses in external data. While most tutorials demonstrate simple implementations, production-grade RAG systems require careful engineering across retrieval, evaluation, latency, and hallucination control.</p>
<p>In this post, we explore the real challenges of building scalable and reliable RAG systems in production environments.</p>
<h3>TL;DR</h3>
<p><strong>Most RAG tutorials stop at the happy path.Production systems fail in the unhappy path.</strong></p>
<p>Here’s what actually breaks in production:</p>
<ul>
<li><p>Fixed-size chunking reduces retrieval quality</p>
</li>
<li><p>Measure retrieval separately (Precision@K, Recall@K)</p>
</li>
<li><p>Add hallucination validation (NLI)</p>
</li>
<li><p>Track retrieval and generation latency independently</p>
</li>
<li><p>Production RAG is a systems problem, not just an LLM problem</p>
</li>
</ul>
<p>Every RAG tutorial shows you the same thing:</p>
<pre><code class="language-python"># The tutorial version
chunks = split_text(docs, chunk_size=512)
embeddings = embed(chunks)
results = vector_db.search(query, top_k=5)
answer = llm.generate(query, context=results)
</code></pre>
<p><strong>This works in a notebook. It fails in production.</strong></p>
<p>Here’s what the tutorials don’t tell you.</p>
<hr />
<h3>Problem 1: Why Fixed-Size Chunking Fails in RAG Systems</h3>
<p>512-token chunks are arbitrary. They:</p>
<ul>
<li><p>Cut sentences mid-thought</p>
</li>
<li><p>Separate headings from content</p>
</li>
<li><p>Break semantic meaning</p>
</li>
</ul>
<p><strong>What actually works: semantic chunking</strong></p>
<pre><code class="language-python">from sentence_transformers import SentenceTransformer
import numpy as np

def semantic_chunk(text, model, threshold=0.3):
    sentences = split_into_sentences(text)
    embeddings = model.encode(sentences)

    chunks, current_chunk = [], [sentences[0]]

    for i in range(1, len(sentences)):
        similarity = cosine_similarity(
            embeddings[i - 1].reshape(1, -1),
            embeddings[i].reshape(1, -1)
        )[0][0]

        if similarity &lt; threshold:  # semantic boundary
            chunks.append(" ".join(current_chunk))
            current_chunk = []
        current_chunk.append(sentences[i])

    if current_chunk:
        chunks.append(" ".join(current_chunk))
    return chunks
</code></pre>
<p><strong>Impact:</strong> +23% retrieval accuracy improvement on held-out eval.</p>
<hr />
<h3>Problem 2: Missing Retrieval Evaluation in RAG Systems</h3>
<p>Most teams evaluate RAG systems by asking:</p>
<blockquote>
<p>“Does the final answer look correct?”</p>
</blockquote>
<p>This is misleading.</p>
<p>A correct-looking answer doesn’t guarantee:</p>
<ul>
<li>The retrieved context was relevant  </li>
<li>The model didn’t compensate with its own knowledge  </li>
<li>The system will generalize to new queries</li>
</ul>
<p>In practice, retrieval and generation fail in different ways.</p>
<p>Without measuring retrieval quality separately, you have no visibility into where the system is breaking.</p>
<p><strong>Measure retrieval directly:</strong></p>
<pre><code class="language-python">def evaluate_retrieval(eval_set, retriever, k=5):
    """
    eval_set: list of {query, relevant_chunk_ids}
    """
    precision_scores, recall_scores = [], []

    for item in eval_set:
        retrieved = retriever.search(item['query'], top_k=k)
        retrieved_ids = {r.id for r in retrieved}
        relevant_ids = set(item['relevant_chunk_ids'])

        precision = len(retrieved_ids &amp; relevant_ids) / len(retrieved_ids)
        recall = len(retrieved_ids &amp; relevant_ids) / len(relevant_ids)

        precision_scores.append(precision)
        recall_scores.append(recall)

    return {
        'precision_at_k': np.mean(precision_scores),
        'recall_at_k': np.mean(recall_scores)
    }
</code></pre>
<p>Track this metric independently from generation quality. They fail in different ways.</p>
<hr />
<h3>Problem 3: No Hallucination Guardrails in LLM Systems</h3>
<p>RAG doesn’t eliminate hallucination- it shifts where it happens.</p>
<p>Instead of the model inventing facts, it can:</p>
<ul>
<li>Misinterpret retrieved context  </li>
<li>Over-generalize from partial information  </li>
<li>Confidently answer questions with weak grounding</li>
</ul>
<p>This makes hallucination harder to detect because the answer <em>looks</em> correct.</p>
<p><strong>Add NLI-based validation post-generation:</strong></p>
<pre><code class="language-python">from transformers import pipeline

nli = pipeline(
    "text-classification",
    model="cross-encoder/nli-deberta-v3-base"
)

def validate_answer(answer, source_chunks, threshold=0.7):
    premise = " ".join(source_chunks)
    result = nli(f"{premise} [SEP] {answer}")

    entailment_score = next(
        r['score'] for r in result
        if r['label'] == 'ENTAILMENT'
    )

    if entailment_score &lt; threshold:
        return {
            "answer": answer,
            "confident": False,
            "message": "Answer may not be fully supported by sources",
            "entailment_score": entailment_score
        }

    return {"answer": answer, "confident": True}
</code></pre>
<hr />
<h3>Problem 4: Most teams track total latency:</h3>
<p>retrieval + generation + network + processing</p>
<p>But this hides the real bottleneck.</p>
<p>In production systems:</p>
<ul>
<li>Retrieval latency depends on indexing and search  </li>
<li>Generation latency depends on model and prompt size</li>
</ul>
<p>Without separating them, you can’t debug performance issues effectively.</p>
<pre><code class="language-python">import time
from dataclasses import dataclass

@dataclass
class RAGLatencyBudget:
    retrieval_ms: int = 40
    generation_ms: int = 60

def rag_with_budget(query, retriever, generator, budget=RAGLatencyBudget()):
    t0 = time.time()
    chunks = retriever.search(query)
    retrieval_ms = (time.time() - t0) * 1000

    t1 = time.time()
    answer = generator.generate(query, chunks)
    generation_ms = (time.time() - t1) * 1000

    # Log separately — they fail differently
    metrics.record('retrieval_latency_ms', retrieval_ms)
    metrics.record('generation_latency_ms', generation_ms)

    if retrieval_ms &gt; budget.retrieval_ms:
        alerts.warn(f"Retrieval exceeded budget: {retrieval_ms:.0f}ms")

    return answer
</code></pre>
<hr />
<h3>The Checklist Before You Ship</h3>
<ul>
<li><p>[ ] Semantic chunking (not fixed-size)</p>
</li>
<li><p>[ ] Retrieval precision measured separately (Precision@K, Recall@K)</p>
</li>
<li><p>[ ] NLI-based hallucination validation</p>
</li>
<li><p>[ ] Retrieval + generation latency tracked independently</p>
</li>
<li><p>[ ] Adversarial eval set (trap questions)</p>
</li>
<li><p>[ ] “I don’t know” path implemented</p>
</li>
</ul>
<hr />
<h3>Final Thought</h3>
<p>The model is the easy part.</p>
<p>Production RAG systems don’t fail because of LLMs.
They fail because of retrieval, evaluation, and system design.</p>
<hr />
<p>If you're building RAG systems in production, what challenges have you faced?</p>
<p>Let’s discuss 👇</p>
]]></content:encoded></item></channel></rss>