Implement RAG Patterns
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
Implement RAG Patterns: Building Context-Aware Generative Applications
Introduction: Why Retrieval-Augmented Generation Matters
In the world of modern software development, Large Language Models (LLMs) have become remarkably capable at generating human-like text, writing code, and summarizing complex information. However, these models suffer from a fundamental limitation: they are frozen in time based on their training data. If you ask a standard LLM about your company’s internal policy updated yesterday or the specific stock levels in your warehouse right now, it will either hallucinate a plausible-sounding answer or admit it does not know. This is where Retrieval-Augmented Generation (RAG) becomes essential.
RAG is an architectural pattern that bridges the gap between an LLM's general knowledge and the specific, private, or real-time data that your application requires. Instead of relying solely on the model's internal parameters, RAG dynamically retrieves relevant documents from a trusted source, injects them into the prompt, and asks the model to generate a response based strictly on that provided context. By implementing RAG, you transform a generic AI into a domain-specific expert that provides verifiable, accurate, and up-to-date information.
This lesson explores the mechanics of RAG, from the initial data ingestion pipeline to the final response generation. We will move beyond the basic "retrieve-and-generate" concept to look at advanced patterns that ensure your AI applications are reliable, scalable, and maintainable. Whether you are building an internal knowledge base assistant or a customer-facing technical support tool, understanding these patterns is the foundational skill required for production-grade generative AI.
The Core Components of a RAG Pipeline
To build a functional RAG system, you need to orchestrate several moving parts. It is helpful to visualize the process as two distinct phases: the Ingestion Pipeline (preparing the data) and the Retrieval-Generation Loop (servicing the request).
1. Data Ingestion and Chunking
Before your model can read your data, the data must be formatted for search. Most raw documents—PDFs, web pages, or database entries—are far too long to fit into a model's context window. Therefore, you must break them down into smaller, meaningful pieces called "chunks."
Effective chunking is an art. If your chunks are too small, the model loses the surrounding context necessary to understand the answer. If they are too large, you risk diluting the signal with irrelevant information, which can confuse the model. A common practice is to use overlapping chunks, ensuring that the end of one chunk contains a small portion of the start of the next to preserve narrative flow.
2. Embedding Models and Vector Stores
Once you have your chunks, you need a way to search them semantically rather than just by keyword. This is achieved through "embeddings." An embedding is a numerical representation of a text string in a high-dimensional vector space. When two pieces of text are semantically similar, their vectors will be mathematically close to each other.
You will store these vectors in a specialized database known as a Vector Store. When a user asks a question, your application converts that question into an embedding using the same model used for the documents. It then performs a "nearest neighbor" search in the database to find the chunks that are most relevant to the query.
3. The Retrieval-Generation Loop
The final step is the generation phase. You take the retrieved chunks, format them into a prompt template, and send them to the LLM. The prompt typically follows this structure: "You are a helpful assistant. Use the following context to answer the user question. If the answer is not in the context, say you do not know. Context: [Chunks]. Question: [User Query]."
Callout: Vector Search vs. Traditional Keyword Search Traditional keyword search (like SQL
LIKEor Elasticsearch) looks for literal matching terms. If a user searches for "canine" but your documents only contain "dog," a keyword search will fail. Vector search, powered by embeddings, understands that "canine" and "dog" occupy similar semantic space, allowing the system to retrieve relevant results even when the exact phrasing differs.
Implementing the Ingestion Pipeline: A Practical Walkthrough
Let’s look at how to implement a basic ingestion pipeline using Python. For this example, we will assume you have a collection of text documents that need to be processed.
Step 1: Text Splitting
We use a text splitter to break documents into manageable sizes. The goal is to keep the chunk size within the limits of your embedding model's input size.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Initialize the splitter
# We use a chunk size of 1000 characters with a 200 character overlap
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
raw_text = "Your long document content goes here..."
chunks = text_splitter.split_text(raw_text)
Step 2: Generating Embeddings
Next, you send these chunks to an embedding provider (such as OpenAI's text-embedding-3-small or an open-source model like HuggingFace).
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(model="text-embedding-3-small")
# Generate vectors for each chunk
chunk_embeddings = embeddings_model.embed_documents(chunks)
Step 3: Storing in a Vector Database
You need a place to store these vectors so they can be queried later. Common choices include Pinecone, Milvus, Weaviate, or ChromaDB.
from langchain_community.vectorstores import Chroma
# Storing the embeddings in a local Chroma database
vector_db = Chroma.from_texts(
texts=chunks,
embedding=embeddings_model,
persist_directory="./chroma_db"
)
Note: Always ensure that the embedding model used for your documents is the exact same model used for your search queries. If you change models, you must re-index your entire dataset, as vectors from different models are not mathematically compatible.
Advanced Retrieval Strategies: Moving Beyond Basic Search
The basic RAG pattern is a great starting point, but it often struggles with complex queries. If a user asks "Compare the benefits of Project A versus Project B," a simple search might retrieve documents about Project A and Project B separately, but fail to synthesize the comparison.
1. Multi-Query Retrieval
In this pattern, the application uses an LLM to rewrite the user's input into multiple variations of the same question. It then executes all these queries against the vector store and aggregates the results. This increases the probability of finding the right information if the user's original phrasing was ambiguous.
2. Contextual Compression
Sometimes, retrieved chunks contain a lot of "noise"—irrelevant sentences that distract the LLM. Contextual compression involves taking the retrieved chunks and passing them through a secondary process that extracts only the relevant information from those chunks before sending them to the final generation step.
3. Parent-Document Retrieval
This is a sophisticated technique where you store small chunks for the purpose of search, but link them to a larger "parent" chunk. When a small chunk is retrieved, the system fetches the larger parent document. This allows the LLM to see the broader context without burdening the search index with massive, overlapping vectors.
Building the Generation Logic
Once you have retrieved your context, the generation phase is where the user experience is defined. You must carefully craft the "System Prompt" to ensure the model follows your instructions.
Example: Constructing the Prompt
def generate_response(user_query, retrieved_context):
prompt = f"""
You are an expert assistant for our internal documentation.
Use the following pieces of retrieved context to answer the question at the end.
If you don't know the answer, just say that you don't know. Do not try to make up an answer.
Context:
{retrieved_context}
Question:
{user_query}
Answer:
"""
# Send this prompt to the LLM
response = llm.invoke(prompt)
return response
Handling Hallucinations
A common pitfall is the model ignoring the context and relying on its internal training data. To prevent this, you should set the temperature parameter of your LLM to a low value (e.g., 0.0 or 0.1). A lower temperature makes the model more deterministic and less "creative," which is exactly what you want when the goal is factual accuracy based on provided documents.
Warning: Never trust the model to "know" if it is using the context. Explicitly instruct the model in the system prompt: "Only use the provided context. If the information is not present, respond with 'I cannot answer this based on the available documentation.'"
Best Practices for Production RAG
Transitioning from a prototype to a production RAG system requires attention to performance, security, and observability.
1. Evaluate with Ragas or TruLens
How do you know if your RAG system is actually working? You should implement evaluation frameworks that measure:
- Faithfulness: Did the answer come from the retrieved context, or was it hallucinated?
- Answer Relevance: Did the answer actually address the user's question?
- Context Precision: Did the retrieval step find the right documents, or was it full of irrelevant noise?
2. Implement Caching
LLM calls are expensive and slow. If your users are asking the same questions repeatedly, implement a caching layer (like Redis) to store the result of the RAG pipeline for common queries. This reduces latency and costs significantly.
3. Security and Access Control
If your RAG system is searching through sensitive documents, you must ensure that users can only retrieve information they are authorized to see. This is often called "Document-Level Security." You can achieve this by tagging documents with metadata (e.g., department: HR) and filtering your vector search queries to only include documents that match the user's permissions.
4. Monitoring and Logging
Log every step of the process: the raw query, the rewritten query (if any), the retrieved document IDs, and the final LLM response. When a user reports a bad answer, you need this audit trail to determine if the failure happened at the retrieval stage (the wrong document was found) or the generation stage (the LLM misinterpreted the correct document).
Comparison Table: RAG Architectural Choices
| Feature | Basic RAG | Agentic RAG |
|---|---|---|
| Control | Fixed pipeline (Retrieve -> Generate) | Dynamic (Model decides when/how to retrieve) |
| Complexity | Low | High |
| Best For | Simple Q&A on static documents | Complex tasks requiring multiple steps/tools |
| Error Handling | Static prompts | Self-correction and retry loops |
Callout: When to use Agentic RAG Basic RAG is sufficient for simple document retrieval. However, if your application needs to do more—such as searching a database, performing a calculation, or checking an external API before answering—you should look into Agentic RAG. In this pattern, the LLM acts as an "agent" that can decide which tools to call and when to retrieve data, rather than following a rigid, linear script.
Common Pitfalls and How to Avoid Them
1. The "Lost in the Middle" Phenomenon
Research has shown that LLMs often pay more attention to the information at the very beginning and the very end of a prompt, while ignoring the information in the middle. If you are retrieving five documents, the model might ignore the third one.
- Fix: Use "reranking" models. After retrieving the top 10 documents via vector search, pass them through a cross-encoder model that scores their relevance to the query, then feed only the top 3-5 most relevant ones to the LLM in a strategic order.
2. Inconsistent Data Formatting
If your source documents are a mix of poorly formatted PDFs, raw text, and tables, your chunks will be of poor quality.
- Fix: Invest time in data cleaning. Use tools like
UnstructuredorPyMuPDFto parse documents into clean text. If you have tables, consider converting them into Markdown format, as LLMs are much better at interpreting Markdown tables than raw CSV or tab-separated text.
3. Ignoring Query Expansion
Users often ask vague questions. A user might type "How do I fix the error?" without specifying which system they are talking about.
- Fix: Use a "Query Transformation" step. Before searching, have an LLM rewrite the user's query to include missing context (e.g., "The user is asking about the login error in the Payment Gateway module").
Step-by-Step: Building a Minimal Viable RAG Application
- Define the Scope: Choose a small set of documents (e.g., a specific product manual).
- Choose the Stack: Select a vector database (e.g., ChromaDB) and an embedding model (e.g., OpenAI
text-embedding-3-small). - Create the Pipeline:
- Load documents.
- Split into chunks (e.g., 500 characters, 50 overlap).
- Embed and insert into the vector store.
- Build the Retrieval Interface:
- Take the user query.
- Embed it.
- Query the vector store for
k=3documents.
- Build the Generation Interface:
- Format the prompt with the retrieved documents.
- Call the LLM (e.g., GPT-4o or Claude 3.5 Sonnet).
- Display the answer to the user.
- Add Feedback: Include a "Thumbs Up/Down" button to collect user data, which you can later use to fine-tune your retrieval strategy.
Frequently Asked Questions
Q: Can I use RAG for real-time data? A: Yes, but your vector store must be updated frequently. If your data changes every minute, you might need a streaming ingestion pipeline that updates the vector database in near real-time.
Q: How many documents should I retrieve? A: This depends on your chunk size and the LLM's context window. Typically, 3 to 5 documents are sufficient for most queries. Retrieving too many documents increases costs and the risk of hallucination.
Q: Is RAG better than Fine-Tuning? A: They serve different purposes. Fine-tuning is best for changing the "behavior" or "style" of a model. RAG is best for providing "factual, up-to-date knowledge." For most business applications, RAG is the primary tool, and fine-tuning is only used if the model struggles to follow specific formatting or tone instructions.
Q: What if my documents are in different languages?
A: Use a multilingual embedding model (like multilingual-e5). These models map text from different languages into the same vector space, allowing you to perform a query in English to retrieve a document written in Spanish.
Key Takeaways for Success
- Data Quality is King: The best LLM in the world cannot fix garbage input. Spend 80% of your effort cleaning and chunking your source documents effectively.
- Vector Search is Not Enough: Always consider adding a reranking step to ensure the most relevant information is presented to the model.
- Keep Temperature Low: When using RAG for factual tasks, keep the LLM temperature near zero to minimize creative hallucinations.
- Prioritize Observability: You cannot improve what you cannot measure. Log your queries, retrievals, and responses to build a feedback loop for continuous improvement.
- Start Simple: Don't build an Agentic RAG system on day one. Start with a basic retrieval pipeline, ensure it works for your use case, and then add complexity like multi-query or reranking as needed.
- Security Matters: If your data is private, ensure your vector database supports user-based access control or filter your search results based on user identity.
- Iterate on Prompts: The system prompt is the "logic" of your application. Treat it like code: version control it, test it, and update it based on user feedback.
Implementing RAG is a journey of refinement. By moving from a simple retrieval setup to a well-monitored, evaluated, and optimized system, you can build tools that provide real value to your users, turning static documentation into an interactive, intelligent resource. Remember that the goal is not just to provide an answer, but to provide an answer that is accurate, grounded in your data, and helpful.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons