<?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[AI Engineering by Data Scientist]]></title><description><![CDATA[Contains learning and thoughts of a Seasoned Data Scientist (11+ years of ML & AI experience) on building simple and complex Agentic AI systems as well as on ge]]></description><link>https://ankushagg-ai.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>AI Engineering by Data Scientist</title><link>https://ankushagg-ai.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 08:46:11 GMT</lastBuildDate><atom:link href="https://ankushagg-ai.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Practical Considerations while Designing a Production-Grade RAG System]]></title><description><![CDATA[Note: If you are looking for RAG fundamentals, then please check out my previous article in RAG series - https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer

]]></description><link>https://ankushagg-ai.hashnode.dev/practical-considerations-while-designing-a-production-grade-rag-system</link><guid isPermaLink="true">https://ankushagg-ai.hashnode.dev/practical-considerations-while-designing-a-production-grade-rag-system</guid><dc:creator><![CDATA[Ankush Aggarwal]]></dc:creator><pubDate>Sun, 13 Sep 2026 08:02:35 GMT</pubDate><content:encoded><![CDATA[<p>Note: If you are looking for RAG fundamentals, then please check out my previous article in RAG series - <a href="https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer">https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer</a></p>
<hr />
<p>A RAG prototype can be built in a few lines of code. A production-grade RAG system is a very different engineering problem.</p>
<h2>Architecture</h2>
<p>As discussed in above <a href="https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer">blog</a>, the architecture of modern RAG systems looks something like this -</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/5ab3ca89-c389-42ed-8b04-a14545e7001c.png" alt="" style="display:block;margin:0 auto" />

<p>However, many critical nuances are hidden in this simplified diagram, and these details can directly impact system quality, accuracy and performance. To understand these trade-offs, let's look at a much deeper view of Production Grade RAG Systems.</p>
<p>Below is a comprehensive RAG architecture which covers most of the components generally used in Production use cases -</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/f64e1038-ec1a-4c03-87fd-d7d469575044.png" alt="" style="display:block;margin:0 auto" />

<p>The most critical components in the above architecture which directly impact response relevancy and system performance are -</p>
<ol>
<li><p>Chunking</p>
</li>
<li><p>Indexing</p>
</li>
<li><p>Search</p>
</li>
</ol>
<p>Let's understand the challenges and importance of each as well as the techniques used to improve overall system performance.</p>
<p><strong>Note</strong>: This article mostly focusses on Dense Retrieval, while Sparse Retrieval is extensively covered in previous article.</p>
<h3>Query Re-writing (Optional)</h3>
<p>Users interact with LLMs in a very naive/human manner. It can happen that the prompts entered by them can be very verbose and only a small part of the prompt is actually relevant enough to generate the right response.</p>
<p>For e.g. <em>"I went to the garden with my baby and he played a lot with his friends. They played football, played on swings and ran a lot. He is generally fussy about eating. Later we went to the mall and played there as well. Now, he is tired and I don't know what to cook for him quickly".</em></p>
<p>Such a prompt, if sent for retrieval, will not only cost a lot but will also have higher probability of returning irrelevant results since the response should essentially be about quick, healthy food for the baby and not about games, playing etc.</p>
<p>Therefore, it is sometimes important to use another LLM to first re-write the query before submitting it to the retriever so that things like database search are optimized and high response relevancy is maintained.</p>
<p>Some useful techniques include -</p>
<ol>
<li><p><strong>Named Entity Recognition (NER)</strong> - NER Models can easily identify entities in prompts like person, locations, time, objects etc., which can then be passed as structural signals to the LLM to re-write the prompt. For example, GLINER supports zero-shot NER and can be used when entity extraction is required.</p>
</li>
<li><p><strong>Hypothetical Document Embeddings (HYDE)</strong> - In this technique, we first generate hypothetical documents which would ideally answer the query. Then we generate its embedding and finally send that embedding to the retriever for finding relevant documents.</p>
</li>
</ol>
<h3>Chunking</h3>
<p>Chunking is the process of splitting a document into smaller, semantically meaningful units that can be independently embedded and retrieved. It also helps keep each unit within the embedding model's input limits.</p>
<p>Now, if we <strong>create too big chunks</strong> then there will be too many information within a single chunk. If Embedding Model's context window is small, then a significant part of the chunk can be lost, otherwise the embedding will be too generic since a lot of information will be there in the chunk.</p>
<p>If we <strong>create too small chunks</strong>, we run the risk of losing surrounding context for each chunk and hence impacting relevance accuracy since few relevant documents might come out as irrelevant because of disjoint chunks.</p>
<p>In short, Chunk Size affects -</p>
<ol>
<li><p>Retrieval Granularity</p>
</li>
<li><p>Embedding Quality</p>
</li>
<li><p>Context Preservation</p>
</li>
<li><p>Number of retrieved chunks</p>
</li>
<li><p>Downstream Context/Token Cost</p>
</li>
</ol>
<p>Hence, right <strong>chunking strategy is critical to overall RAG accuracy</strong> !</p>
<p>Following are some of the possible chunking strategies which can be selected depending upon the use case -</p>
<p><strong>Fixed Size Chunking</strong></p>
<p>Generally done at a word level or character level, this strategy provides fixed size chunks, even if deriving proper meaning of a text required multiple words within the same chunk.</p>
<p><strong>Tip:</strong> Retrieval Quality can be significantly improved with Fixed Size Chunking by adding an overlap of <em>'n'</em> characters between 2 consecutive chunks, to each chunk. However, optimal overlap depends on the document structure and should be determined through empirical validations.</p>
<p><strong>Recursive Character Text Splitting</strong></p>
<p>This strategy recursively splits text using a hierarchy of separators—for example, paragraphs, newlines and spaces—while attempting to keep related content together. This produces variable-sized chunks and often preserves semantic structure better than blindly splitting at a fixed character count. The only downside is that it can result in too many chunks and also some of the chunks can be small and irrelevant when viewed in isolation.</p>
<p>In most of the practical cases, Fixed Size &amp; Recursive Text Splitting are both used in conjunction. For e.g. texts like heading and follow-up paragraph are first split by recursive character splitting but then headings are merged because of fixed size splitting, thereby, making them more meaningful.</p>
<p>There are also more advanced chunking strategies which help improve accuracy significantly but come with higher cost as well</p>
<p><strong>Semantic Chunking</strong></p>
<ol>
<li><p>Chunk at sentence level and create vector for each chunk</p>
</li>
<li><p>Calculate cosine distance between 2 consecutive chunks</p>
</li>
<li><p>If cos-distance &gt; threshold, merge chunks to create bigger chunk</p>
</li>
</ol>
<p><strong>LLM Based Chunking</strong></p>
<p>Use another LLM in the RAG pipeline to create chunks of the source document by including instructions like <em>'keep concepts together'</em>, <em>'add breaks when new topic starts</em>' etc.</p>
<p><strong>Context-Aware Chunking</strong></p>
<p>Use another LLM to add additional context to every single chunk (e.g. summary text) and re-create the chunk. This strategy is flexible enough to be applied on top of any other previous strategies.</p>
<p>Based on my experience, here is a quick suggestion on when to apply which strategy -</p>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>Best Suited For</th>
</tr>
</thead>
<tbody><tr>
<td>Fixed Size</td>
<td>Simple, Unstructured Text; Cost is critical</td>
</tr>
<tr>
<td>Recursive</td>
<td>Structured Documents; Cost is critical</td>
</tr>
<tr>
<td>Semantic</td>
<td>Topic-Heavy Documents; Medium Scale</td>
</tr>
<tr>
<td>LLM Based</td>
<td>Complex Document Structures; Cost not critical</td>
</tr>
<tr>
<td>Context-Aware</td>
<td>When isolated chunks lose meaning; Accuracy is critical</td>
</tr>
</tbody></table>
<h3>Indexing &amp; Search</h3>
<p>Once we have document embeddings (N) and incoming query's embedding (Q), we can find K-Nearest Neighbors for Q using any similarity measure but this will have a high complexity of <strong>O(N*D)</strong>, where</p>
<p>N = number of documents &amp; D = dimension size</p>
<p>In Production Systems, N can run into millions &amp; D can be in thousands, hence, such an exact KNN search approach becomes infeasible.</p>
<p>Since, the way we store our vectors (Indexing) hugely impacts the subsequent search operation (Search), most of the Vector DB providers today provide integrated Indexing &amp; Search functionality.</p>
<p><strong>Inverted Files (IVF)</strong></p>
<p>This solves the problem of <em>'too many vectors to check'</em> by -</p>
<ol>
<li><p>Grouping documents into multiple groups (<em>nlist</em>) using clustering algorithms like K-Means and each group's centroid vector is calculated</p>
</li>
<li><p>Each group is then organized as Inverted Lists (remember BM25!) labelled by group's centroid vector</p>
</li>
<li><p>For given Q, we first run similarity search with all the centroid vectors</p>
</li>
<li><p>Then, find most similar documents only from within the groups whose centroid vectors were the closest.</p>
</li>
</ol>
<p>This can dramatically reduce the number of database vectors that need to be compared, trading some recall for lower search cost.</p>
<p><strong>Product Quantization (PQ)</strong></p>
<p>In this approach, original vector is partitioned into <em>'m'</em> sub-vectors/sub-spaces and a separate codebook is learned for each sub-space independently. Each sub-vector is then represented by the ID of its nearest codeword, significantly reducing the memory required to store the vector and enabling efficient approximate distance computation. Thus, basically providing a compression technique to efficiently run '<em>semantic search over large vectors'</em>.</p>
<p>Q is also then partitioned into sub-vectors and cosine distance is calculated across centroids for each sub-vector (m*K combinations). Documents (represented by list of centroid-ids) are fetched based on the shortest cumulative distance across Q's sub-vectors by matching centroid-ids.</p>
<p><strong>IVF+PQ</strong></p>
<p>The more popular technique here is the one which brings the best of both worlds - first grouping document vectors using K-Means &amp; finding top-k centroid vectors. Then, <em>residual vectors</em> are computed (original - centroid) and partitioning is done on these residual vectors.</p>
<p><strong>Tip</strong>: With PQ based approaches, we end up effectively doing an Approximate Nearest Neighbor (ANN) Search instead of Exact Search. For most practical purposes, ANN Search is good enough especially when dealing with millions of documents and latency is critical. Exact Search based techniques are generally reserved for Post-Retrieval/Re-Ranking.</p>
<p><strong>Navigable Small Worlds (Hierarchical - HNSW)</strong></p>
<p>Most Vector DBs today implement a different strategy for indexing &amp; search which leverages the flexibility of graphs. In NSW, a graph is built connecting close vectors (nodes) with each other but limiting the number of connections -- every node is connected to maximum <em>'k'</em> other nodes, based on cost vs accuracy analysis.</p>
<p>In Hierarchical NSW, multiple graphs are built (imagine vertically), each connected with another graph below it using common nodes. Number of neighbors of each node increase as we go towards lower graphs. A node present in upper graph will always be present in lower graphs.</p>
<p>Search starts by finding the most similar node in top most graph and its similar neighbors are added to relevant documents list as we traverse downwards, with each similar document becoming the parent node for next search. This enables highly efficient approximate nearest-neighbor search with approximately logarithmic scaling in many practical settings.</p>
<p><strong>Choosing an Index</strong></p>
<p>Here is a comparative analysis of HNSW and IVF (+PQ):</p>
<table>
<thead>
<tr>
<th>Criteria</th>
<th>Often Better</th>
</tr>
</thead>
<tbody><tr>
<td>Retrieval Speed/Latency</td>
<td>HNSW</td>
</tr>
<tr>
<td>Index Build Cost</td>
<td>IVF+PQ</td>
</tr>
<tr>
<td>Memory Footprint</td>
<td>IVF+PQ</td>
</tr>
<tr>
<td>Accuracy</td>
<td>HNSW</td>
</tr>
<tr>
<td>Scalability</td>
<td>IVF+PQ</td>
</tr>
<tr>
<td>Document Updates Handling</td>
<td>HNSW</td>
</tr>
<tr>
<td>Build Complexity</td>
<td>HNSW</td>
</tr>
<tr>
<td>Metadata Filtering Performance</td>
<td>IVF+PQ</td>
</tr>
</tbody></table>
<p>As you can see, there is no universal winner here.</p>
<p><strong>Tip:</strong> My personal experience tells me that while the right choice depends on the required recall, latency, memory budget, update pattern and filtering requirements, however, if accuracy and latency are paramount, then HNSW is an attractive default, otherwise if Cost is critical and marginally lower accuracy and latency is acceptable, then IVF+PQ is particularly attractive.</p>
<h2>System Optimization</h2>
<p>While Data Scientists remain mostly concerned about getting the architecture right as it will directly impact accuracy and response relevancy, AI engineers need to also look at implementing an optimized system with considerable focus on <strong>cost optimization</strong> - both memory and compute, as well as <strong>latency</strong> (response time).</p>
<p>Following are some of the best techniques to explore in finding the right balance of accuracy, cost and latency -</p>
<h3><strong>Cost Optimization</strong></h3>
<ol>
<li><p><strong>Smaller models</strong> for retrieval lead to less memory footprint, less compute cost and better latency, but suffer from lower accuracy. Bigger models can be saved for Re-ranking</p>
</li>
<li><p>If, however, smaller models is not an option, <strong>Model Quantization</strong> generally incurs only marginal drop in accuracy for most use cases while reducing compute cost significantly. Model quantization represents model weights and/or activations using lower-precision numerical formats, such as INT8 or lower-bit formats instead of FP16/FP32.</p>
</li>
<li><p>In addition to compressing the model, <strong>Vector Quantization</strong> can also be done to reduce compute cost -</p>
<ol>
<li><p>Use lightweight embeddings, possibly generated using a smaller embedding models, for retrieval and full-size embeddings (from bigger models) for re-ranking</p>
</li>
<li><p><strong>Matryoshka Embedding Learning</strong> - Some embedding models are trained so that useful information is preserved in progressively smaller prefixes of the embedding vector. This allows applications to use a lower-dimensional representation for fast retrieval and the full representation when higher accuracy is required.</p>
</li>
</ol>
</li>
<li><p><strong>Smaller, optimized prompts</strong> can help save a lot on token/compute costs at response generation time. Some techniques to include -</p>
<ol>
<li><p>Retrieve less number of top-k documents</p>
</li>
<li><p>Output tokens are commonly charged higher than input tokens, so set a limit on maximum number of output tokens</p>
</li>
<li><p>Include system prompts to encourage LLM to generate shorter responses</p>
</li>
</ol>
</li>
<li><p><strong>Memory Cost</strong> - Production systems typically need persistent storage and an index optimized for vector retrieval. While this can be provided by a dedicated vector database or by an existing database/search platform with vector capabilities, these are essential for scalability, redundancy, failure handling etc. So using the right memory for right purpose often leads to significant cost savings. One such option can be -</p>
<ol>
<li><p>RAM (Faster, Expensive) - Ideal for HSNW Indexes for fast retrieval</p>
</li>
<li><p>Disk (Slower, Cheaper) - Mostly for infrequently accessed vectors</p>
</li>
<li><p>Cloud Object (Slowest, Cheapest) - Ideal for storing Raw Documents</p>
</li>
</ol>
</li>
</ol>
<h3>Latency</h3>
<p>Defined as <em>Response Time or Turnaround Time taken by an AI system to generate response for an input query</em>, Latency is one of the most important system performance metric which is used in AI Systems across domains. Higher latency leads to end user frustration and depletion of trust in system usability.</p>
<p>In order to optimize overall latency of a RAG System, it is important to breakdown and measure latency of each individual component, in other words, measure &amp; optimize separately -</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/ddf4390c-61f2-4143-a246-7ef251211ed0.png" alt="" style="display:block;margin:0 auto" />

<p>Also, most of the techniques mentioned for cost optimization also help directly in improving latency of the system like smaller models, optimized prompts, quantization etc.</p>
<p>There are also some additional latency specific optimization techniques which may prove to be beneficial for certain large-scale use cases -</p>
<ol>
<li><p>Using a small <strong>Router LLM</strong> to first determine if Retrieval is required or is query can be answered directly by the LLM model itself. And if Retrieval is required, then it can also determine complexity of the query so as to route to bigger or smaller LLMs for response generation.</p>
</li>
<li><p>Keep frequently submitted prompts and their responses in <strong>cache memory</strong>. Here, first match incoming query with previously answered query and if match is found, send cache content to a smaller LLM for response generation. Use the typical flow only if there is no match found cache.</p>
</li>
</ol>
<p><strong>Note</strong>: Caching requires careful invalidation when source documents or permissions change.</p>
<h2>Evaluation</h2>
<p>AI-based systems require extensive evaluation as it is the single most important component which can help ensure if the system will fail in Production or not.</p>
<p>Generally in Production RAG Systems, evaluation is done in 2 Stages -</p>
<ol>
<li><p>Retrieval Evaluation</p>
</li>
<li><p>Generation/RAG Evaluation</p>
</li>
</ol>
<h3><strong>Retrieval Evaluation</strong></h3>
<p><em>Are we retrieving the right information ?</em></p>
<p>There are some standard evaluation metrics widely used to measure retrieval accuracy -</p>
<p>$$Precision @ K = \frac {Number\ of\ relevant\ documents\ in\ K} {K}$$</p>
<p> $$ Recall @ K = \frac {Number\ of\ relevant\ documents\ in\ K} {Total\ Relevant\ Documents}$$</p>
<p> $$ MeanAveragePrecision\ (MAP) = Average\ [\frac {1}{|Q|}\sum_{q\in Q} AP(q)]$$</p>
<p> $$ AveragePrecision(q) = \frac {1}{R_q}\sum_{k=1}^K Precision@k\ *\ rel_k$$</p>
<p> $$ MeanReciprocalRank\ (MRR) = \frac {1}{|Q|}\sum_{i=1}^{|Q|} \frac {1}{Rank_i}$$</p>
<p> $$ NormalizedDiscountedCumulativeGain (NDCG@K) = \frac {DCG@K}{IDCG@K}$$</p>
<p> $$ DCG@K = \sum_{i=1}^K \frac {rel_i}{log_2(i+1)}$$</p>
<p>$$rel_i = relevant\ score\ of\ item\ at\ position\ i$$</p>
<p> $$ IDCG@K = Max\ DCG\ score.\ Calculated\ by\ sorting\ all\ items\ by\ relevance\ in\ descending\ order$$</p>
<p>Where,</p>
<p>R_q = number of relevant items for query q; Q = collection of total queries</p>
<h3>Generation/RAG Evaluation</h3>
<p><em>Given the retrieved context, did the system generate a good answer?</em></p>
<p>Here, I will mention two most common RAG evaluation techniques used in Production -</p>
<ol>
<li><p><strong>Human-as-a-Judge</strong> - AI Engineers curate a large set of prompts covering as much diversity as possible including document-related prompts to evaluate relevance accuracy as well as other prompts to evaluate guardrails and security. Each one of the prompts is then manually evaluated by humans for pre-decided criteria</p>
</li>
<li><p><strong>LLM-as-a-Judge</strong> - Most of the time AI systems are first evaluated by another LLM(s) which act a Judge against a pre-decided rubric of evaluation criteria. '<em>RAGAS'</em> is one such library which provides this functionality. Here, the rubric generally comprises of metrics like -</p>
<ol>
<li><p><strong>Response Relevancy</strong> - Evaluates relevance of response regardless of factual accuracy. Here input prompt is compared, in terms of similarity, with synthetic prompts which could have led to the same response.</p>
</li>
<li><p><strong>Faithfulness</strong> - It determines factual accuracy by making additional LLM calls to determine if the response claim is factually supported by the retrieved information.</p>
</li>
<li><p><strong>Correctness</strong> - When an Expected/Reference Answer exists, it determines if the answer matches the same.</p>
</li>
</ol>
</li>
</ol>
<p>There are also additional metrics like <strong>Noise Sensitivity</strong> and <strong>Citation Ability</strong> which are sometimes used to evaluate effectiveness of the RAG system.</p>
<p><strong>Tip:</strong> RAGAS is a great library for RAG evaluation. Reading about certain metrics and definitions (not just their implementation) brings better clarity on applicability of certain metrics for robustness of your specific use case.</p>
<p>Before we end this article, I wanted to share my personal thoughts on debugging RAG Pipelines.</p>
<h3>Debugging</h3>
<p>Understanding points of failure in a Production RAG Pipeline and their possible reasons is one of the most underrated skill in my opinion. If mapped well, this can save a lot of valuable engineering time and also help maintain user trust.</p>
<p>Some of the most common failure nodes in a RAG system and their potential causes are -</p>
<table>
<thead>
<tr>
<th>Failure Mode</th>
<th>Source of Problem</th>
</tr>
</thead>
<tbody><tr>
<td>Relevant document isn't retrieved</td>
<td>Retrieval</td>
</tr>
<tr>
<td>Relevant document is retrieved but buried at rank 20</td>
<td>Re-Ranking</td>
</tr>
<tr>
<td>Correct document retrieved but chunk lacks context</td>
<td>Chunking</td>
</tr>
<tr>
<td>Correct context retrieved but LLM ignores it</td>
<td>Generation/Prompt</td>
</tr>
<tr>
<td>Correct answer but unacceptable latency/cost</td>
<td>System Design</td>
</tr>
</tbody></table>
<hr />
<p>That's all on RAG for now. Feel free to comment below if there are some other possibilities in the architecture or optimization or evaluation which helped you improve your specific use cases.</p>
]]></content:encoded></item><item><title><![CDATA[RAG & Information Retrieval: Understanding the Retrieval Layer]]></title><description><![CDATA[LLMs are remarkably good at generating answers. But generation alone doesn't guarantee that the answer is relevant, up-to-date, or grounded in your organization's data.
Fine-tuning or retraining can h]]></description><link>https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer</link><guid isPermaLink="true">https://ankushagg-ai.hashnode.dev/rag-information-retrieval-understanding-the-retrieval-layer</guid><category><![CDATA[RAG ]]></category><category><![CDATA[Information Retrieval ]]></category><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[AI Architecture]]></category><category><![CDATA[Rag architecture]]></category><dc:creator><![CDATA[Ankush Aggarwal]]></dc:creator><pubDate>Fri, 31 Jul 2026 03:30:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>LLMs are remarkably good at generating answers. But generation alone doesn't guarantee that the answer is relevant, up-to-date, or grounded in your organization's data.</strong></p>
<p>Fine-tuning or retraining can help adapt a model to a specific use case, but neither is an ideal solution when the underlying information changes frequently or exists outside the model's training data.</p>
<p>This is where <strong>Retrieval-Augmented Generation (RAG)</strong> comes in.</p>
<h2>RAG</h2>
<p>In simple words, give model access to external source of information before generating any response, thereby grounding the response in factual data. From this very sentence, we can see that RAG consists of 2 stages -</p>
<ol>
<li><p>Access to secondary information</p>
</li>
<li><p>Response Generation (or Text Generation)</p>
</li>
</ol>
<p>While Transformer-based language models are exceptionally good at text generation, Information Retrieval (IR)—the technology underlying search engines for decades—provides a natural way to retrieve relevant external information before generation.</p>
<p>Hence, RAG architecture became -</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/a9d97ad2-cfb8-430c-8952-3f2c9a7b4706.png" alt="" style="display:block;margin:0 auto" />

<h2>Retrieval</h2>
<p>Text Retrieval is one of the most critical part of any RAG system. While there are many complexities involved in implementing the same, one of the easiest, fastest implementation came from Information Retrieval (IR) systems.</p>
<p>IR systems have mostly been based on Keyword-Search, i.e. documents are represented using term-based representations and are scored based on the occurrence and importance of query terms. This has been a fast and efficient technique which enabled search engines return matching links/documents based on search queries.</p>
<p>Because the total vocabulary across a corpus can be very large, each document typically contains only a small subset of those terms. As a result, the corresponding vector representation contains mostly zeros and is therefore called a <em><strong>sparse representation</strong></em>. Hence, this approach is commonly referred to as <em>Sparse Retrieval</em>.</p>
<h3><strong>Sparse Retrieval</strong></h3>
<p>$$Each\ document\ d_i:\ [c_1,c_2,....,c_k]\ -\ vector\ of\ dimension\ k$$</p>
<p>$$k\ =\ total\ unique\ words\ across\ corpus$$</p>
<p>$$c_j\ =\ count\ of\ word\ j\ in\ the\ document\ (simplified\ version)$$</p>
<p>In Sparse retrieval, Each document is saved as a vector representation of word counts. For any incoming user query -</p>
<ol>
<li><p>Query is tokenized into words</p>
</li>
<li><p>For each word in the query, a score is computed against each document</p>
</li>
<li><p>Scores for all query words are added as a document score for that query</p>
</li>
<li><p>Documents are then returned as sorted list based on these scores - higher the score, relevant the document</p>
</li>
</ol>
<h3><strong>Score Computation</strong></h3>
<p>Multiple techniques have been used for computing this <em>'relevance score'</em>, but the 2 main techniques used widely were -</p>
<ol>
<li><p>TF-IDF</p>
</li>
<li><p>BM25 (modified TF-IDF)</p>
</li>
</ol>
<p><strong>TF-IDF</strong></p>
<p><strong>Term Frequency (TF)</strong> represents frequency (count) of term (word) in the document. Idea: Higher Count --&gt; More Relevant Document</p>
<p><strong>Inverted Document Frequency (IDF)</strong> represents inverted count of documents containing the term (word). It is used to give additional importance to rare, meaningful words</p>
<p>Formally defined as -</p>
<p>$$TF(t,d) = 1+log_{10}Count(t,d),\ if\ Count(t,d)\ &gt;\ 0$$</p>
<p>$$TF(t,d) = 0,\ if\ Count(t,d)\ =\ 0$$</p>
<p>$$IDF(t)\ =\ log_{10}(N/DF_t),\ N=total\ docs,\ DF_t=docs\ containing\ word\ t$$</p>
<p>TF will tend to bias towards longer documents, hence, TF is modified as -</p>
<p>$$TF(t,d) = 1+log_{10}(Count(t,d)/total\ words\ in\ doc\ d),\ if\ Count(t,d)\ &gt;\ 0$$</p>
<p>$$Score(q,d)=\sum_{t\in q\cap d} TF_{t,d}\ .\ IDF_{t}$$</p>
<p><strong>Note:</strong> A common alternative is to normalize term frequency by document length (as above). The exact TF normalization varies across implementations but the important intuition is that raw term counts can favor longer documents.</p>
<p><strong>Best Matching 25 (BM25)</strong></p>
<p>BM25 can be viewed as an evolution of TF-IDF that introduces tunable controls for <em>'term-frequency saturation'</em> and <em>'document-length normalization'</em>. This flexibility has made BM25 one of the most widely used scoring functions for Sparse Retrieval.</p>
<p><strong>Term Frequency Saturation</strong> - TF-IDF will uniformly increase the score of documents based on the count of a given keyword in the document. This may or may not be desirable depending upon use case. BM25 introduces a factor, <em><strong>'k'</strong></em>, which can control the effect of term count</p>
<p><strong>Document Length Normalization</strong> - TF-IDF either places a full penalty on the length of the document or does not place a penalty at all, based on the TF definition used. BM25 makes it configurable using a factor <em><strong>'b'</strong></em>.</p>
<p>$$IDF(t) = log_{10}[(N-DF_t+0.5)/(DF_t+0.5)]$$</p>
<p>$$Score(q,d)=\sum_{t\in q\cap d} IDF_{t}\ .\ [(TF_{t,d}.(k+1))\ /\ (TF_{t,d}+k(1-b+b(|d|/avg|d|)))]$$</p>
<p>Here,</p>
<p>|d| = length of document d; Avg|d| = Avg. length of all documents in corpus,</p>
<p>k = commonly between 0.5 to 2; and 0 &lt;= b &lt;= 1</p>
<p><strong>Tuning 'b'</strong> - Increase to apply higher normalization like in cases where document sizes are of varying length and longer ones should not dominate.</p>
<p><strong>Tuning 'k'</strong> - Increase if term repetition is actually important like legal documents, scientific journals, tech manuals etc.</p>
<h3><strong>Indexing</strong></h3>
<p>A core data structure behind efficient sparse retrieval is <strong>Inverted Index</strong>. It maps each term to a <em>postings list</em> containing document IDs and, typically, term-frequency information. This allows the search engine to efficiently retrieve documents containing query terms.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/edbd8ca8-786e-4eb7-aad1-74c8fdf33e4c.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Dense Retrieval</strong></h3>
<p>Since Sparse Retrieval is only concerned with occurrence of words, it can only help find documents containing the exact words. Therefore, retrieval accuracy will be low if -</p>
<ol>
<li><p>Documents contain synonyms or</p>
</li>
<li><p>Meaning of the document is completely different based on order of words but the words are essentially same as in query</p>
</li>
<li><p>Documents contain few common words but mostly different words but overall context or meaning is similar</p>
</li>
<li><p>Query is specific to rare words or words not present in memory (corpus)</p>
</li>
</ol>
<p>But thanks to Transformer architecture and subsequent explosion of Language Models - big and small, it is now possible to encode the meaning of a document in an <strong>embedding</strong> (another name for vector representation).</p>
<p>An embedding model is commonly a fine tuned model trained on retrieval objectives, which encodes text into a dense vector representation designed to capture useful semantic information. These vectors are much smaller than a vocabulary-sized sparse representation, hence the term <em>dense vectors</em>. Searching this embedding space is commonly referred to as <em>semantic search</em>.</p>
<p>Here's a quick comparison between Sparse &amp; Dense Retrieval</p>
<table>
<thead>
<tr>
<th>Criteria</th>
<th>Sparse Retrieval</th>
<th>Dense Retrieval</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Approach</strong></td>
<td>Lexical Matching</td>
<td>Semantic Matching</td>
</tr>
<tr>
<td><strong>Unit</strong></td>
<td>Term Based</td>
<td>Embedding Based</td>
</tr>
<tr>
<td><strong>Strength</strong></td>
<td>Excellent for Exact Terms</td>
<td>Better for Semantic Matching</td>
</tr>
<tr>
<td><strong>Scoring</strong></td>
<td>BM25/TF-IDF</td>
<td>Cosine Similarity</td>
</tr>
<tr>
<td><strong>Indexing</strong></td>
<td>Inverted Index</td>
<td>Vector Index</td>
</tr>
</tbody></table>
<p>There are primarily 2 ways of implementing Dense Retrieval -</p>
<p><strong>Cross Encoder</strong></p>
<p>Both query and document are combined into a single input, separated by special tokens, and fed as input to the model. Model is trained to predict a score, representative of the relevance of the document for the given query.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/36747f8b-6731-49c9-8f36-17385862cc09.png" alt="" style="display:block;margin:0 auto" />

<p>Cross-encoders can model query-document interactions more directly and often provide stronger relevance judgments, but they are substantially more expensive because the query and each candidate document must be processed together</p>
<p><strong>Bi-Encoder</strong></p>
<p>A bi-encoder independently encodes the query and document into vector representations. The model is trained so that relevant query-document pairs have similar representations for e.g. a Siamese Network.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/6ee83c41-0df5-4c15-aa3b-4d8944052249.png" alt="" style="display:block;margin:0 auto" />

<p>The biggest advantage of Bi-Encoders is that embeddings for all documents can be pre-computed and stored in a vector database (databases specialized for vector storage &amp; retrieval). Whenever a query comes, its embedding is generated using the same model (hence, query &amp; document embeddings will be in same latent space) and is then used to search similar documents using a similarity metric on the embeddings. This makes this approach trade some interaction modeling for superior scalability.</p>
<p>In Production RAG Systems, there are generally 2 stages of dense retrieval -</p>
<ol>
<li><p><strong>Retrieval</strong> - Bi-Encoders are generally employed for fast retrieval when size of data (documents) is huge. This will help reduce search set to a limited set of relevant documents (commonly called '<em>candidate set'</em>).</p>
</li>
<li><p><strong>Post-Retrieval (Re-Ranking)</strong> - Once we get our candidate set, it becomes feasible to run our cross-encoder model to return a more accurate relevance score for each of the candidate documents. Final response is then generated based on the rank of each document, calculated based on cross-encoder's similarity score.</p>
</li>
</ol>
<p>Some common similarity measures used are -</p>
<ol>
<li><p>Euclidean Distance - Distance between 2 vectors (L2 Distance)</p>
</li>
<li><p>Dot Product - Sum of element-wise products of two vectors; equivalently, the product of their magnitudes and the cosine of the angle between them</p>
</li>
<li><p>Cosine Similarity (most common) - Measures the angular similarity between two vectors, independent of their magnitude</p>
</li>
</ol>
<h3><strong>Hybrid Retrieval</strong></h3>
<p>Many Production RAG systems today don't use only one retrieval technique, instead they deploy a Hybrid RAG system where both dense retrieval and sparse retrieval work in tandem. This is then complemented by <strong>'<em>Metadata Filtering'</em></strong> which can be applied alongside retrieval as a business-logic-based constraint—for example, filtering documents by tenant, date, document type, access permissions, or geography.</p>
<p><strong>Production RAG Architecture</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6a9bbdb3c75b01d98a662d42/f72dccaa-88a3-4dd7-bbaf-edcdc4d60f23.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Re-Ranking</strong></p>
<p>While above Architecture depicts a Sequential RAG - first <em>Dense</em>, then <em>Sparse</em> and finally <em>Filtering</em>, there are multiple ways of defining this architecture depending upon which leads to highest accuracy -</p>
<ol>
<li><p>Sequential</p>
<ol>
<li><p>Dense Retrieval as Candidate Selector</p>
</li>
<li><p>Sparse Retrieval for Ranking</p>
</li>
</ol>
</li>
<li><p>Parallel</p>
<ol>
<li><p>Dense Retrieval as Ranker 1</p>
</li>
<li><p>Sparse Retrieval as Ranker 2</p>
</li>
<li><p>Combined Ranking Score (Reciprocal Rank Fusion) =</p>
</li>
</ol>
</li>
</ol>
<p>$$Rank = [w_1\ /\ (K+Rank_{Ranker1})]\ +\ [w_2\ /\ (K+Rank_{Ranker2})]$$</p>
<p>$$w_1, w_2 = Weights\ of\ each\ ranker\ (configurable)$$</p>
<p>Metadata Filtering can be used across any design as business-logic based document filter.</p>
<p>Note: To improve final ranking of relevant documents in RAG, sometimes a secondary LLM is also used in Re-Ranking to provide relevance scores.</p>
<hr />
<p>This concludes the deep dive into the retrieval layer. If you'd like to explore how these concepts come together in a production-grade RAG architecture, check out my next article: <a href="https://ankushagg-ai.hashnode.dev/practical-considerations-while-designing-a-production-grade-rag-system"><em>Practical Considerations While Designing a Production-Grade RAG System</em>.</a></p>
]]></content:encoded></item></channel></rss>