<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Aishwarya Radhakrishnan on Medium]]></title>
        <description><![CDATA[Stories by Aishwarya Radhakrishnan on Medium]]></description>
        <link>https://medium.com/@aishwaryaradhakrishnan?source=rss-d758d746ee5------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*ZOu2jDxzw2zQUwFW.</url>
            <title>Stories by Aishwarya Radhakrishnan on Medium</title>
            <link>https://medium.com/@aishwaryaradhakrishnan?source=rss-d758d746ee5------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 22:54:40 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@aishwaryaradhakrishnan/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Selecting the right RAG+LLM strategy-II (RAG design patterns, Agent assisted workflows)]]></title>
            <link>https://medium.com/@aishwaryaradhakrishnan/selecting-the-right-rag-llm-strategy-ii-rag-design-patterns-agent-assisted-workflows-35d3d38981c2?source=rss-d758d746ee5------2</link>
            <guid isPermaLink="false">https://medium.com/p/35d3d38981c2</guid>
            <category><![CDATA[llm-agent]]></category>
            <category><![CDATA[rags]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[llamaindex]]></category>
            <dc:creator><![CDATA[Aishwarya Radhakrishnan]]></dc:creator>
            <pubDate>Sun, 02 Jun 2024 19:37:55 GMT</pubDate>
            <atom:updated>2024-06-02T19:37:55.738Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-PCMHFq1zJSVHdAf" /><figcaption>Photo by <a href="https://unsplash.com/@barnimages?utm_source=medium&amp;utm_medium=referral">Barn Images</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>This is a follow up of <a href="https://medium.com/@aishwaryaradhakrishnan/selecting-the-right-rag-llm-strategy-for-your-use-case-1-cf18411a86d3">this </a>article.</p><h3>Overview of RAG pipelines</h3><p>Most robust RAG pipelines in production have the following key components</p><blockquote>Embedding strategies</blockquote><blockquote>Retrieval types and evaluation of context</blockquote><blockquote>Post Retrieval techniques</blockquote><blockquote>Quantitative metrics for user response</blockquote><p>For complex use cases, where more than one type of data is being handled by the same RAG system , we can define separate workflows for each of the use case. An end-end robust RAG system looks like this-</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/831/1*0WQiv_z7QtYsFiy09vHZFw.jpeg" /><figcaption>End-end RAG pipeline in production environments</figcaption></figure><p>Lets dive into each of the individual components of the workflow, i’ve already spoken about chunking strategy in the previous post in this series, the main focus of this is the Query Pre-processing ,post processing and workflow parts of RAG.</p><h3><strong>Query Preprocessing and Routing</strong></h3><p>There are different ways of pre-processing your queries such as</p><ol><li>Query based Routing</li></ol><p>2. Sub Query generation</p><p>3. HyDe transformation</p><p>4. Query Decomposition</p><p>There’s newer methods and techniques added into llama toolkit everyday. But for now lets deep dive into each of these techniques to see where what fits best for the use case.</p><h4><strong><em>Query Based Routing</em></strong></h4><p>Agent Assisted Workflows are very useful in cases where more than one type of information is embedded and a query might be required to be analyzed for intent before being part of a chain of different workflows . LlamaIndex has a Agent Query Pipeline, that helps define a DAG like workflow for query routing .</p><p>A working example of the notebook i’ve used to summarize a call transcript from Yahoo! Finance is <a href="https://colab.research.google.com/drive/1NOGSS5-FNNCGF9QJO5mwOzAm_4SdfUai#scrollTo=tfzG-sjFTkpq&amp;uniqifier=2">here </a>. However the parts of the notebook that are interesting are the way’s the agentic workflow gets stitched together</p><pre>from llama_index.core.query_pipeline import QueryPipeline as QP<br><br>qp = QP(verbose=True,llm=OpenAI(model=&quot;gpt-3.5-turbo&quot;))<br><br>qp.add_modules(<br>    {<br>        &quot;agent_input&quot;: agent_input_component,<br>        &quot;react_prompt&quot;: react_prompt_component,<br>        &quot;llm&quot;:OpenAI(model=&quot;gpt-3.5-turbo&quot;),<br>        &quot;react_output_parser&quot;: parse_react_output,<br>        &quot;run_tool&quot;: run_tool,<br>        &quot;process_response&quot;: process_response,<br>        &quot;process_agent_response&quot;: process_agent_response,<br>    }<br>)<br>qp.add_chain([&quot;agent_input&quot;, &quot;react_prompt&quot;, &quot;llm&quot;, &quot;react_output_parser&quot;])<br><br># add conditional link from react output to tool call (if not done)<br>qp.add_link(<br>    &quot;react_output_parser&quot;,<br>    &quot;run_tool&quot;,<br>    condition_fn=lambda x: not x[&quot;done&quot;],<br>    input_fn=lambda x: x[&quot;reasoning_step&quot;],<br>)<br># add conditional link from react output to final response processing (if done)<br>qp.add_link(<br>    &quot;react_output_parser&quot;,<br>    &quot;process_response&quot;,<br>    condition_fn=lambda x: x[&quot;done&quot;],<br>    input_fn=lambda x: x[&quot;reasoning_step&quot;],<br>)<br><br># whether response processing or tool output processing, add link to final agent response<br>qp.add_link(&quot;process_response&quot;, &quot;process_agent_response&quot;)<br>qp.add_link(&quot;run_tool&quot;, &quot;process_agent_response&quot;)</pre><p>Each of the modules are then chained together in the form of a DAG as below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/820/1*tnt_Ct9WHuQyMo0Iz-UMbw.png" /><figcaption>DAG of a sample workflow for routing</figcaption></figure><p>The user query then gets parsed to find the right “tool” or module defined in your DAG</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_Dkyc4ZSlmzxgGldK49XLQ.png" /><figcaption>Assisted workflow — where the actions are predefined , but which action to take is dynamically chosen based on user query</figcaption></figure><p>If you are indexing and querying against multiple tenants (like in SaaS), and each tenant has their own workflow or integrations with other orchestrations e.g., a workflow on Uber data might require the summary to be emailed , where as a workflow on a health insurance- might require you to submit the user response to a different system as an example of where you might want to route based on the query .</p><h4>Sub-Question generation</h4><p>Information retrieval platforms based on multiple models perform with different levels of accuracy in IR tasks or NER tasks based on which LLM is being used. In the healthcare domain, when there is more than one information component requested in a user query, its been my experience that some of the SLM’s and older order LLMs (like 3.5) while are cost effective might require the user query to be split into sub questions. As an example.-</p><pre>from llama_index.core import VectorStoreIndex, SimpleDirectoryReader<br>from llama_index.core.tools import QueryEngineTool, ToolMetadata<br>from llama_index.core.query_engine import SubQuestionQueryEngine<br>from llama_index.core.callbacks import CallbackManager, LlamaDebugHandler<br>from llama_index.core import Settings<br>llama_debug = LlamaDebugHandler(print_trace_on_end=True)<br>callback_manager = CallbackManager([llama_debug])<br><br>Settings.callback_manager = callback_manager<br>pg_essay = SimpleDirectoryReader(input_files=[&quot;/content/call_transcript_elv.pdf&quot;]).load_data()<br><br># build index and query engine<br>vector_query_engine = VectorStoreIndex.from_documents(<br>    pg_essay,<br>    use_async=True,<br>).as_query_engine()<br><br># setup base query engine as tool<br>query_engine_tools = [<br>    QueryEngineTool(<br>        query_engine=vector_query_engine,<br>        metadata=ToolMetadata(<br>            name=&quot;Elevance finance call&quot;,<br>            description=&quot;Elevance earnings finance call transcript&quot;,<br>        ),<br>    ),<br>]<br><br>query_engine = SubQuestionQueryEngine.from_defaults(<br>    query_engine_tools=query_engine_tools,<br>    use_async=True,<br>)<br>response = query_engine.query(<br>    &quot;What was gael and mark&#39;s overview of first quarter performance  &quot;<br>)</pre><p>The SubQuestionQueryEngine can now differentiate the two components of query as follows -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jxKhdlz1NNcB8dgd6nMhsw.png" /></figure><h4>HyDE Transformation</h4><p>HyDe works by asking an LLM at inference time to generate a hypothetical document that matches the user query.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*hNlN2YUEa1RC-daX8zEHAw.png" /></figure><p>Next, these hypothetical documents undergo a pivotal transformation through an encoder, which has been trained using an unsupervised contrastive learning method.</p><p>To further refine the search process, the average of the query’s embedding and the embeddings of the hypothetical documents is calculated. This resultant query embedding represents a distilled representation of the query’s intent, now imbued with the contextual nuances captured from the hypothetical documents.</p><p>The final step involves computing the similarity between the query embedding (composed of the query and hypothetical document embeddings) and the document embeddings derived from the corpus. By leveraging the same encoder for both hypothetical documents and the corpus, HyDE seamlessly maps the query to the most pertinent documents in the collection.</p><p>A sample working of hyde transformation is as follows, however a more detailed working sample including the transformation prompt is <a href="https://docs.llamaindex.ai/en/stable/examples/query_transformations/HyDEQueryTransformDemo/">here </a>.</p><pre>elv_call_transcript = SimpleDirectoryReader(<br>        input_files=[&quot;/content/call_transcript_elv.pdf&quot;]<br>    ).load_data()<br>transcript_index = VectorStoreIndex.from_documents(elv_call_transcript)<br>transcript_index.storage_context.persist(persist_dir=&quot;/content/&quot;)<br>transcript_engine = transcript_index.as_query_engine(similarity_top_k=3)<br>hyde = HyDEQueryTransform(include_original=True)<br>transcript_hyde_query_engine = TransformQueryEngine(transcript_engine, hyde)<br>query_engine_tools = [<br>    QueryEngineTool(<br>        query_engine=transcript_hyde_query_engine,<br>        metadata=ToolMetadata(<br>            name=&quot;call_transcript&quot;,<br>            description=(<br>                &quot;Provides information about Elevance financials for year 2024. &quot;<br>                &quot;Use a detailed plain text question as input to the tool.&quot;<br>            ),<br>        ),<br>    )]</pre><h4>Query Decomposition</h4><p>If sub question simply breaks down information from a user query, multi-step query transformation, is a more creative approach underpinning the self-ask method. This technique involves a language model autonomously posing and answering a series of follow-up questions before addressing the original query, enabling it to independently synthesize and integrate information.</p><p><strong><em>Autonomous Inquiry</em></strong></p><p>The self-ask method begins with the LLM generating additional, relevant questions based on the initial query.</p><p><strong><em>Independent Learning and Synthesis</em></strong></p><p>As the LLM answers these follow-up questions, it connects scattered facts, synthesizing insights along the way. This iterative process allows the LLM to unearth relationships and uncover insights that might remain hidden in a single-step approach.</p><p><strong><em>Providing a Synthesized Answer</em></strong></p><p>After this iterative exploration, the QueryEngine combines the independently gathered information to produce a more informed and cohesive response to the initial query.</p><p>Lets look at working example-</p><pre>from llama_index.core.indices.query.query_transform.base import (<br>    StepDecomposeQueryTransform,<br>)<br># gpt-3<br>step_decompose_transform_gpt3 = StepDecomposeQueryTransform(<br>    llm=gpt35, verbose=True<br>)<br>index_summary = &quot;Used to answer questions about the document&quot;<br># set Logging to DEBUG for more detailed outputs<br>from llama_index.core.query_engine import MultiStepQueryEngine<br><br>query_engine = index.as_query_engine(llm=gpt35)<br>query_engine = MultiStepQueryEngine(<br>    query_engine=query_engine,<br>    query_transform=step_decompose_transform_gpt3,<br>    index_summary=index_summary,<br>)<br>response_gpt35 = query_engine.query(<br>    &quot;what was gaels overview of the financial earnings&quot;,<br>)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0wSAaRpRBFqsHOTu8GNF3g.png" /></figure><p>The Response above is the query decomposition which is then again used by the LLM to better synthesize the answer as below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Hd5KPInnBAYW66RpGC10g.png" /></figure><p>The complete working notebook is in the link <a href="https://github.com/aishadvitya/llm_playground/blob/main/multistep_decomposition.ipynb">here</a></p><h3>Query Post Processing</h3><p>In the post processing , there are two parts- Ranking results and evaluation. But since evaluation is a core area cutting across different components of RAG, the next part of the series will be a more comprehensive deep dive into evaluation metrics</p><h4>Re-Ranking</h4><p>While re-ranking has been hugely simplified by out of box customizations like Cohere Re-ranking and its integration with the llama ecosystem, its still a lot more fun (especially for trickier use cases) to train your cross encoder for re-ranking.</p><p>For the purposes of this experiment (also because im bound by org policy), ive used a toy dataset to demonstrate cross encoder training. The model used below is BERT ,but any of BERT based models like x-bert , roberta can be used in substitution.</p><pre>&quot;&quot;&quot;<br>Methodology:<br>Three steps are followed for AugSBERT data-augmentation strategy with Domain Transfer / Cross-Domain -<br>1. Cross-Encoder aka BERT is trained over STSb (source) dataset.<br>2. Cross-Encoder is used to label QQP training (target) dataset (Assume no labels/no annotations are provided).<br>3. Bi-encoder aka SBERT is trained over the labeled QQP (target) dataset.<br><br>&quot;&quot;&quot;<br><br>from torch.utils.data import DataLoader<br>from sentence_transformers import models, losses, util, LoggingHandler, SentenceTransformer<br>from sentence_transformers.cross_encoder import CrossEncoder<br>from sentence_transformers.cross_encoder.evaluation import CECorrelationEvaluator<br>from sentence_transformers.evaluation import BinaryClassificationEvaluator<br>from sentence_transformers.readers import InputExample<br>from datetime import datetime<br>from zipfile import ZipFile<br>import logging<br>import csv<br>import sys<br>import torch<br>import math<br>import gzip<br>import os<br><br><br># You can specify any huggingface/transformers pre-trained model here, for example, bert-base-uncased, roberta-base, xlm-roberta-base<br>model_name = &quot;bert-base-uncased&quot;<br>batch_size = 16<br>num_epochs = 1<br>max_seq_length = 128<br>use_cuda = torch.cuda.is_available()<br><br>###### Read Datasets ######<br>sts_dataset_path = &quot;datasets/stsbenchmark.tsv.gz&quot;<br>qqp_dataset_path = &quot;quora-IR-dataset&quot;<br><br><br># Check if the STSb dataset exists. If not, download and extract it<br>if not os.path.exists(sts_dataset_path):<br>    util.http_get(&quot;https://sbert.net/datasets/stsbenchmark.tsv.gz&quot;, sts_dataset_path)<br><br># Check if the QQP dataset exists. If not, download and extract<br>if not os.path.exists(qqp_dataset_path):<br>    logging.info(&quot;Dataset not found. Download&quot;)<br>    zip_save_path = &quot;quora-IR-dataset.zip&quot;<br>    util.http_get(url=&quot;https://sbert.net/datasets/quora-IR-dataset.zip&quot;, path=zip_save_path)<br>    with ZipFile(zip_save_path, &quot;r&quot;) as zipIn:<br>        zipIn.extractall(qqp_dataset_path)<br><br># Cross encoder will be saved in this location. Path will be like this output/cross-encoder/stsb_indomain_bert-base-uncased<br># Knowing the path correctly will be helpful in next step<br>cross_encoder_path = (<br>    &quot;output/cross-encoder/stsb_indomain_&quot;<br>    + model_name.replace(&quot;/&quot;, &quot;-&quot;)<br>)<br><br># Bi encoder will be saved in this location. Path will be like this output/cross-encoder/qqp_cross_domain_bert-base-uncased<br># Knowing the path correctly will be helpful in next step<br>bi_encoder_path = (<br>    &quot;output/bi-encoder/qqp_cross_domain_&quot;<br>    + model_name.replace(&quot;/&quot;, &quot;-&quot;)<br>)<br><br>###### Cross-encoder (simpletransformers) ######<br><br>logging.info(&quot;Loading cross-encoder model: {}&quot;.format(model_name))<br># Use Huggingface/transformers model (like BERT, RoBERTa, XLNet, XLM-R) for cross-encoder model<br>cross_encoder = CrossEncoder(model_name, num_labels=1)<br><br>###### Bi-encoder (sentence-transformers) ######<br><br>logging.info(&quot;Loading bi-encoder model: {}&quot;.format(model_name))<br><br># Use Huggingface/transformers model (like BERT, RoBERTa, XLNet, XLM-R) for mapping tokens to embeddings<br>word_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length)<br><br># Apply mean pooling to get one fixed sized sentence vector<br>pooling_model = models.Pooling(<br>    word_embedding_model.get_word_embedding_dimension(),<br>    pooling_mode_mean_tokens=True,<br>    pooling_mode_cls_token=False,<br>    pooling_mode_max_tokens=False,<br>)<br><br>bi_encoder = SentenceTransformer(modules=[word_embedding_model, pooling_model])<br><br><br># Step 1: Train cross-encoder model with STSbenchmark<br><br>logging.info(&quot;Step 1: Train cross-encoder: {} with STSbenchmark (source dataset)&quot;.format(model_name))<br><br>gold_samples = []<br>dev_samples = []<br>test_samples = []<br><br>with gzip.open(sts_dataset_path, &quot;rt&quot;, encoding=&quot;utf8&quot;) as fIn:<br>    reader = csv.DictReader(fIn, delimiter=&quot;\t&quot;, quoting=csv.QUOTE_NONE)<br>    for row in reader:<br>        score = float(row[&quot;score&quot;]) / 5.0  # Normalize score to range 0 ... 1<br><br>        if row[&quot;split&quot;] == &quot;dev&quot;:<br>            dev_samples.append(InputExample(texts=[row[&quot;sentence1&quot;], row[&quot;sentence2&quot;]], label=score))<br>        elif row[&quot;split&quot;] == &quot;test&quot;:<br>            test_samples.append(InputExample(texts=[row[&quot;sentence1&quot;], row[&quot;sentence2&quot;]], label=score))<br>        else:<br>            # As we want to get symmetric scores, i.e. CrossEncoder(A,B) = CrossEncoder(B,A), we pass both combinations to the train set<br>            gold_samples.append(InputExample(texts=[row[&quot;sentence1&quot;], row[&quot;sentence2&quot;]], label=score))<br>            gold_samples.append(InputExample(texts=[row[&quot;sentence2&quot;], row[&quot;sentence1&quot;]], label=score))<br><br><br># We wrap gold_samples (which is a List[InputExample]) into a pytorch DataLoader<br>train_dataloader = DataLoader(gold_samples, shuffle=True, batch_size=batch_size)<br><br><br># We add an evaluator, which evaluates the performance during training<br>evaluator = CECorrelationEvaluator.from_input_examples(dev_samples, name=&quot;sts-dev&quot;)<br><br># Configure the training<br>warmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1)  # 10% of train data for warm-up<br>logging.info(&quot;Warmup-steps: {}&quot;.format(warmup_steps))<br><br># Train the cross-encoder model<br>cross_encoder.fit(<br>    train_dataloader=train_dataloader,<br>    evaluator=evaluator,<br>    epochs=num_epochs,<br>    evaluation_steps=1000,<br>    warmup_steps=warmup_steps,<br>    output_path=cross_encoder_path,<br>)<br><br># Step 2: Label QQP train dataset using cross-encoder (BERT) model<br><br>logging.info(&quot;Step 2: Label QQP (target dataset) with cross-encoder: {}&quot;.format(model_name))<br><br>cross_encoder = CrossEncoder(cross_encoder_path)<br><br>silver_data = []<br><br>with open(os.path.join(qqp_dataset_path, &quot;classification/train_pairs.tsv&quot;), encoding=&quot;utf8&quot;) as fIn:<br>    reader = csv.DictReader(fIn, delimiter=&quot;\t&quot;, quoting=csv.QUOTE_NONE)<br>    for row in reader:<br>        if row[&quot;is_duplicate&quot;] == &quot;1&quot;:<br>            silver_data.append([row[&quot;question1&quot;], row[&quot;question2&quot;]])<br><br>silver_scores = cross_encoder.predict(silver_data)<br><br># All model predictions should be between [0,1]<br>assert all(0.0 &lt;= score &lt;= 1.0 for score in silver_scores)<br><br>binary_silver_scores = [1 if score &gt;= 0.5 else 0 for score in silver_scores]<br><br># Step 3: Train bi-encoder (SBERT) model with QQP dataset - Augmented SBERT<br><br>logging.info(&quot;Step 3: Train bi-encoder: {} over labeled QQP (target dataset)&quot;.format(model_name))<br><br># Convert the dataset to a DataLoader ready for training<br>logging.info(&quot;Loading BERT labeled QQP dataset&quot;)<br>qqp_train_data = list(<br>    InputExample(texts=[data[0], data[1]], label=score) for (data, score) in zip(silver_data, binary_silver_scores)<br>)<br><br><br>train_dataloader = DataLoader(qqp_train_data, shuffle=True, batch_size=batch_size)<br>train_loss = losses.MultipleNegativesRankingLoss(bi_encoder)<br><br>###### Classification ######<br># Given (quesiton1, question2), is this a duplicate or not?<br># The evaluator will compute the embeddings for both questions and then compute<br># a cosine similarity. If the similarity is above a threshold, we have a duplicate.<br>logging.info(&quot;Read QQP dev dataset&quot;)<br><br>dev_sentences1 = []<br>dev_sentences2 = []<br>dev_labels = []<br><br>with open(os.path.join(qqp_dataset_path, &quot;classification/dev_pairs.tsv&quot;), encoding=&quot;utf8&quot;) as fIn:<br>    reader = csv.DictReader(fIn, delimiter=&quot;\t&quot;, quoting=csv.QUOTE_NONE)<br>    for row in reader:<br>        dev_sentences1.append(row[&quot;question1&quot;])<br>        dev_sentences2.append(row[&quot;question2&quot;])<br>        dev_labels.append(int(row[&quot;is_duplicate&quot;]))<br><br>evaluator = BinaryClassificationEvaluator(dev_sentences1, dev_sentences2, dev_labels)<br><br># Configure the training.<br>warmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1)  # 10% of train data for warm-up<br>logging.info(&quot;Warmup-steps: {}&quot;.format(warmup_steps))<br><br># Train the bi-encoder model<br>bi_encoder.fit(<br>    train_objectives=[(train_dataloader, train_loss)],<br>    evaluator=evaluator,<br>    epochs=num_epochs,<br>    evaluation_steps=1000,<br>    warmup_steps=warmup_steps,<br>    output_path=bi_encoder_path,<br>)<br><br># Evaluate Augmented SBERT performance on QQP benchmark dataset<br><br># Loading the augmented sbert model<br>bi_encoder = SentenceTransformer(bi_encoder_path)<br><br>logging.info(&quot;Read QQP test dataset&quot;)<br>test_sentences1 = []<br>test_sentences2 = []<br>test_labels = []<br><br>with open(os.path.join(qqp_dataset_path, &quot;classification/test_pairs.tsv&quot;), encoding=&quot;utf8&quot;) as fIn:<br>    reader = csv.DictReader(fIn, delimiter=&quot;\t&quot;, quoting=csv.QUOTE_NONE)<br>    for row in reader:<br>        test_sentences1.append(row[&quot;question1&quot;])<br>        test_sentences2.append(row[&quot;question2&quot;])<br>        test_labels.append(int(row[&quot;is_duplicate&quot;]))<br><br>evaluator = BinaryClassificationEvaluator(test_sentences1, test_sentences2, test_labels)<br>bi_encoder.evaluate(evaluator)</pre><p>Once the cross encoder model is trained, you can use this in conjunction with llamaindex as below</p><pre>import os<br><br>from llama_index.core import VectorStoreIndex,SimpleDirectoryReader<br>from llama_index.core.postprocessor import SentenceTransformerRerank<br>import openai<br><br>openai.api_key = &quot;sk...&quot;<br># Load documents from a dtputirectory<br>documents = SimpleDirectoryReader(&#39;datasets/data&#39;).load_data()<br><br>#Now lets use the bi encoder trained ealier<br>print(&quot;######## Using bi-encoder trained with our data&quot;)<br><br>index = VectorStoreIndex(<br>        documents, embed_model=&quot;local:output/bi-encoder/qqp_cross_domain_bert-base-uncased&quot;, show_progress=True<br>    )<br>query_engine = index.as_query_engine()<br><br>response = query_engine.query(&quot;What did the author do growing up?&quot;)<br>print(response)<br><br>#Now lets use the cross encoder trained ealier for re-ranking<br>print(&quot;######## Using cross-encoder trained with our data for reranking&quot;)<br><br># Initialize the reranker<br>rerank = SentenceTransformerRerank(<br>    model=&quot;./output/cross-encoder/stsb_indomain_bert-base-uncased&quot;, top_n=3<br>)<br><br><br># Build the query engine<br>query_engine = index.as_query_engine(similarity_top_k=10, node_postprocessors=[rerank])<br><br>response = query_engine.query(&quot;What did the author do growing up?&quot;)<br>print(response)<br><br>     </pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pq7KlIJDZoefI_z6VzKQ4A.png" /></figure><p>While these are good basics to get started, the most pertinent of them all is the evaluation(which i think deserves its own series ) but will be in a follow up to this three part write-up.</p><h4>References</h4><ul><li><a href="https://docs.llamaindex.ai/en/stable/examples/finetuning/rerankers/cohere_custom_reranker/">Custom Cohere Reranker - LlamaIndex</a></li><li><a href="https://arxiv.org/abs/2212.10496">Precise Zero-Shot Dense Retrieval without Relevance Labels</a></li></ul><p><a href="https://github.com/run-llama/llama_index/blob/main/llama-index-integrations/tools/llama-index-tools-google/examples/advanced_tools_usage.ipynb">https://github.com/run-llama/llama_index/blob/main/llama-index-integrations/tools/llama-index-tools-google/examples/advanced_tools_usage.ipynb</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35d3d38981c2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Selecting the right RAG+LLM strategy for your use case — 1]]></title>
            <link>https://medium.com/@aishwaryaradhakrishnan/selecting-the-right-rag-llm-strategy-for-your-use-case-1-cf18411a86d3?source=rss-d758d746ee5------2</link>
            <guid isPermaLink="false">https://medium.com/p/cf18411a86d3</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[llm-evaluation]]></category>
            <category><![CDATA[rags]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Aishwarya Radhakrishnan]]></dc:creator>
            <pubDate>Thu, 11 Apr 2024 16:04:57 GMT</pubDate>
            <atom:updated>2024-04-11T17:28:39.545Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Selecting the right RAG+LLM strategy for your use case — 1</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oDrfoWtb6jOE17AJ" /><figcaption>Photo by <a href="https://unsplash.com/@gillyberlin?utm_source=medium&amp;utm_medium=referral">Gilly</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>With the increase in the number of LLM’s released per day, and the RAG approaches, I’m excited to explore an end-end approach on decision making when it comes to RAGs</p><h3>Key Drivers</h3><p>We’ll look into some of they key considerations that go into deciding which approach to take — this is assuming your organization has hosted instances/access to LLM’s and VDB’s</p><p><em>Cost</em></p><p><em>Data processing pipelines</em></p><p><em>Selecting a RAG approach</em></p><p><em>Evaluation metrics</em></p><h3>Cost</h3><p>Cost of LLM’s is one of the main key considerations into maintaining your RAG pipeline or even beginning to consider a use case for automation. How do you factor in cost/token into your RAG pipeline? There are out-of-the box token solutions like <a href="https://docsbot.ai/tools/gpt-openai-api-pricing-calculator">docsbot</a> that have an up to date $/token</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1021/1*XKNjDSfPxOhdkLMiNz_t_w.png" /><figcaption>comparison of price of chat completion end points of selected models as of 9-Apr-2024</figcaption></figure><p>But this is a first step into understanding how much your model is going to cost per query . When migrating and switching to more cost effective models, the prompts can get quite verbose, if you are choosing to use this on specific domain use cases . eg., while gpt-4 might be able to understand context from a medical domain, gpt-3.5 might require a few shot examples to understand .</p><p><em>overall cost =(No.of documents in a day)*(no of queries/document)*(no of tokens/query)*(cost_per_token)</em></p><p>The number of tokens depends on the context-length of each model. packages like <a href="https://github.com/openai/tiktoken">tiktoken</a> offer out of the box calculations for number of tokens consumed/query.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XBZ4IrB-SYcYh3Wk2e06NA.png" /><figcaption>prompt template with corresponding tokens</figcaption></figure><h3>Data Ingestion Pipelines</h3><p>Data pre-processing and indexing are key to building effective (Retrieval Augmented Generation (RAG) pipelines. Before we go into an in-depth view of RAG, it is important to invest effort in building efficient data processing steps that will help in extracting the right data before we pass it to LLM to better utilize the information .</p><p>There are many libraries like <a href="https://unstructured.io/">unstructured</a> and <a href="https://github.com/nlmatics/nlm-ingestor">NLMIngestor</a> that help extract sections, tables, images from pdfs. A Naive RAG pipeline would probably chunk and index a document and then retrieve a context based on a query.</p><h4><strong>Load from PDF</strong></h4><p>Note that the NLMIngestor (which is used by llmsherpa below) is Apache2 licensed and can be hosted in your own environment</p><pre>llmsherpa_api_url = &quot;https://readers.llmsherpa.com/api/document/developer/parseDocument?renderFormat=all&quot;<br>pdf_url = &quot;https://arxiv.org/pdf/1910.13461.pdf&quot; # also allowed is a file path e.g. /home/downloads/xyz.pdf<br>pdf_reader = LayoutPDFReader(llmsherpa_api_url)<br>doc = pdf_reader.read_pdf(pdf_url)</pre><h4><strong>Index and Query</strong></h4><p>The example used Qdrant in memory but the configuration can be changed when using in production</p><pre>qdrant = QdrantClient(&quot;:memory:&quot;) # Create in-memory Qdrant instance, for testing, CI/CD<br>llmsherpa_api_url = &quot;https://readers.llmsherpa.com/api/document/developer/parseDocument?renderFormat=all&quot;<br>pdf_url = &quot;https://arxiv.org/pdf/2102.12060.pdf&quot; # also allowed is a file path e.g. /home/downloads/xyz.pdf<br>pdf_reader = LayoutPDFReader(llmsherpa_api_url)<br>doc = pdf_reader.read_pdf(pdf_url)<br>metadata,documents,idx=[],[],[]<br>for section in doc.sections():<br>  if section.children:<br>     metadata.append({&quot;heading&quot;:section.to_text(include_children=False)})<br>     print(f&quot;The heading is {section.to_text(include_children=False)}&quot;)<br>     documents.append(&#39;&#39;.join(str(chunk.to_text()) for chunk in section.chunks()))<br>     idx.append(section.page_idx)<br><br>qdrant.add(collection_name=&quot;test_collection&quot;,documents=documents,metadata=metadata,ids=idx)</pre><p>The end -end flow to index and query is in <a href="https://colab.research.google.com/drive/1pEKnHeHlXvOTS7eu1m6RyU27Jcin8iZw#scrollTo=gzZpWvKia-4T">colab-notebook.</a></p><h3>RAG and others..</h3><p>The other part of this series will focus on RAG , different types of RAG and the Evaluation metrics.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cf18411a86d3" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>