Study interactive :: Progress tools open in the Study Hub reader.

RAG Guide

Building and evaluating RAG systems. Retrieval helps grounding. It does not eliminate unsupported answers.

TypeScript / Node stacks: If you implement RAG behind a Node or Next.js service, pair this guide’s architecture sections with LangChain’s JavaScript / TypeScript documentation. Code snippets in this file are mostly Python.

Table of Contents


Introduction

What is RAG?

Retrieval Augmented Generation (RAG) is an architecture that enhances Large Language Model (LLM) responses by:

  1. Retrieving relevant information from external knowledge bases
  2. Augmenting the prompt with retrieved context
  3. Generating responses using the LLM with the augmented context

Why RAG?

Problems RAG Helps With:

Use Cases:


What is RAG?

Core Concept

RAG combines two key components:

1. Retrieval System:

2. Generation System:

RAG vs Fine-Tuning

Aspect RAG Fine-Tuning
Data Updates Easy (update vector DB) Requires retraining
Cost Lower (no training) Higher (training costs)
Latency Slightly higher (retrieval step) Lower (direct inference)
Transparency Can cite sources Black box
Domain Knowledge External knowledge base Learned in weights
Unsupported answers Often fewer when retrieval and prompts are good; not eliminated Can still invent

When to Use RAG:

When to Use Fine-Tuning:


RAG Architecture

Basic RAG Flow

User QueryQuery EmbeddingVector SearchRetrieve Top-K DocumentsAugment Promptquery plus contextLLM GenerationResponse plus Sources

Detailed Architecture

Same path with the usual pieces named:

User QueryQuery Embeddingembedding modelVector SearchFAISS / Pinecone / WeaviateRetrieve Top-K DocumentsPrompt Builderquery, context, instructionsLLM GenerationResponse plus Sources

Components of RAG

1. Document Processing

Text Splitting:

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # Size of each chunk
    chunk_overlap=200,    # Overlap between chunks
    length_function=len,
    separators=["\n\n", "\n", " ", ""]  # Split on these
)

documents = text_splitter.split_documents(your_documents)

Metadata Extraction:

2. Embedding Models

Choosing Embedding Model:

from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings

# Option 1: OpenAI (paid)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Option 2: Hugging Face (free, good quality)
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

3. Vector Database

Popular Vector Databases:

# FAISS (Local)
from langchain_community.vectorstores import FAISS

vectorstore = FAISS.from_documents(documents, embeddings)
vectorstore.save_local("faiss_index")

# Pinecone (Cloud). API shape changes often. Check current Pinecone + LangChain docs
from langchain_community.vectorstores import Pinecone

vectorstore = Pinecone.from_documents(
    documents, 
    embeddings, 
    index_name="rag-index"
)

4. Retrieval

Retrieval Strategies:

# Basic similarity search
retriever = vectorstore.as_retriever(
    search_kwargs={"k": 5}  # Top 5 results
)

# MMR for diversity
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 5, "fetch_k": 20}
)

# With metadata filtering
retriever = vectorstore.as_retriever(
    search_kwargs={
        "k": 5,
        "filter": {"category": "technical"}
    }
)

5. Prompt Engineering

RAG Prompt Template:

from langchain.prompts import PromptTemplate

template = """Use the following pieces of context to answer the question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.

Context: {context}

Question: {question}

Answer:"""

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

Building RAG Systems

Basic RAG with Langchain

from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA

# 1. Load and split documents
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)
documents = text_splitter.split_documents(your_documents)

# 2. Create embeddings
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

# 3. Create vector store
vectorstore = FAISS.from_documents(documents, embeddings)

# 4. Create retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 5. Create LLM (temperature=0 lowers randomness; not a correctness guarantee)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 6. Create RAG chain
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    return_source_documents=True
)

# 7. Query
query = "What is machine learning?"
result = qa_chain({"query": query})
print(result["result"])
print("\nSources:")
for doc in result["source_documents"]:
    print(f"- {doc.page_content[:100]}...")

Conversational RAG

from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

# Add conversation memory
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True
)

# Conversational RAG
c>
    llm=llm,
    retriever=retriever,
    memory=memory,
    return_source_documents=True
)

# Query with conversation history
resp>"question": "What is RAG?"})
print(response["answer"])

# Follow-up question (uses conversation history)
resp>"question": "How does it work?"})
print(response2["answer"])

RAG with LlamaIndex

from llama_index import VectorStoreIndex, SimpleDirectoryReader, ServiceContext
from llama_index.llms import OpenAI

# Load documents
documents = SimpleDirectoryReader('data').load_data()

# Create index
index = VectorStoreIndex.from_documents(documents)

# Create query engine
query_engine = index.as_query_engine()

# Query
resp>"What is machine learning?")
print(response)
print("\nSources:")
for node in response.source_nodes:
    print(f"- {node.text[:100]}...")

Advanced RAG Techniques

1. Query Expansion

Expand query to improve retrieval:

from langchain_openai import ChatOpenAI

def expand_query(original_query):
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    prompt = f"""Expand this query to improve search results:
    
    Original: {original_query}
    
    Expanded:"""
    expanded = llm.invoke(prompt)
    return expanded.content

# Use expanded query for retrieval
expanded_query = expand_query("ML")
results = retriever.get_relevant_documents(expanded_query)

2. Re-ranking

Re-rank retrieved results for better relevance:

from sentence_transformers import CrossEncoder

# Load re-ranker
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

# Re-rank results
pairs = [[query, doc.page_content] for doc in retrieved_docs]
scores = reranker.predict(pairs)
ranked = sorted(zip(retrieved_docs, scores), key=lambda x: x[1], reverse=True)

Combine semantic and keyword search:

from langchain.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever

# Semantic retriever
semantic_retriever = vectorstore.as_retriever()

# Keyword retriever (BM25)
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5

# Ensemble retriever
ensemble_retriever = EnsembleRetriever(
    retrievers=[semantic_retriever, bm25_retriever],
    weights=[0.7, 0.3]  # Weight semantic more
)

4. Parent Document Retriever

Retrieve parent documents after finding relevant chunks:

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

# Store for parent documents
store = InMemoryStore()

# Parent document retriever
parent_retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=text_splitter,
    parent_splitter=parent_splitter
)

# Add documents
parent_retriever.add_documents(documents)

5. Self-Query Retriever

Extract metadata filters from query:

from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# Metadata fields
metadata_field_info = [
    {"name": "category", "description": "Document category"},
    {"name": "date", "description": "Publication date"}
]

# Self-query retriever
self_query_retriever = SelfQueryRetriever.from_llm(
    llm=llm,
    vectorstore=vectorstore,
    document_contents="Documents about machine learning",
    metadata_field_info=metadata_field_info
)

# Query with metadata
results = self_query_retriever.get_relevant_documents(
    "What are recent papers about neural networks?"
)

Vector Databases

Comparison

Database Type Scalability Features Best For
FAISS Local Medium Fast, free Development, small scale
Pinecone Cloud High Managed, easy Production, cloud-first
Weaviate Self-hosted High GraphQL, ML Production, control
Chroma Embedded Medium Simple, Python Development, prototyping
Qdrant Self-hosted High Fast, Rust Production, performance
Milvus Self-hosted Very High Distributed Enterprise, large scale

FAISS Example

import faiss
import numpy as np

# Create index
dimension = 384  # Embedding dimension
index = faiss.IndexFlatL2(dimension)

# Add embeddings
embeddings = np.array([...])  # Your embeddings
index.add(embeddings)

# Search
query_embedding = np.array([...])
k = 5
distances, indices = index.search(query_embedding.reshape(1, -1), k)

Pinecone Example

from pinecone import Pinecone

# Initialize (Pinecone v3+ client; check current docs if this drifts)
pc = Pinecone(api_key="your-key")

# Create index if needed (serverless/pod options vary by account)
# pc.create_index(name="rag-index", dimension=384, metric="cosine", ...)

index = pc.Index("rag-index")

# Upsert vectors
index.upsert(vectors=[
    ("id1", [0.1, 0.2, ...]),
    ("id2", [0.3, 0.4, ...])
])

# Query
results = index.query(
    vector=[0.1, 0.2, ...],
    top_k=5,
    include_metadata=True
)

Evaluation and Optimization

Evaluation Metrics

1. Retrieval Metrics:

2. Generation Metrics:

Evaluation Framework

from langchain.evaluation import QAEvalChain

# Create evaluation chain
eval_chain = QAEvalChain.from_llm(llm)

# Evaluate
predicti>"query": q} for q in questions])
eval_results = eval_chain.evaluate(
    examples=examples,
    predictions=predictions
)

Optimization Strategies

1. Chunk Size:

2. Chunk Overlap:

3. Top-K Retrieval:

4. Embedding Model:

5. Prompt Engineering:


Production Deployment

Architecture Considerations

1. Scalability:

2. Latency:

3. Cost:

Deployment Example

from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

app = FastAPI()

class QueryRequest(BaseModel):
    question: str
    top_k: int = 3

@app.post("/rag/query")
async def query_rag(request: QueryRequest):
    # Retrieve
    docs = retriever.get_relevant_documents(
        request.question,
        k=request.top_k
    )
    
    # Generate
    result = qa_chain({
        "query": request.question,
        "context": "\n\n".join([doc.page_content for doc in docs])
    })
    
    return {
        "answer": result["result"],
        "sources": [doc.metadata for doc in docs]
    }

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Monitoring

Key Metrics:


Best Practices

1. Document Processing

2. Embedding Selection

3. Retrieval Optimization

4. Prompt Engineering

5. Evaluation


Common Pitfalls

1. Poor Chunking

Problem: Chunks too small or too large
Solution: Experiment with chunk sizes, preserve semantic boundaries

2. Irrelevant Retrieval

Problem: Retrieved docs not relevant
Solution: Improve embeddings, use re-ranking, expand queries

3. Context Overflow

Problem: Too much context, exceeds token limit
Solution: Limit top-k, use summarization, filter retrieved docs

4. Hallucination / unsupported answers

Problem: LLM invents facts or ignores retrieved context
Solution: Better prompts, stronger retrieval/re-ranking, citation checks, and abstain-when-unsure behavior. RAG helps; it does not eliminate this failure mode (see AI Myths Busted).

5. Slow Performance

Problem: High latency
Solution: Cache embeddings, use faster models, optimize retrieval


Resources

Libraries

Papers

Tutorials

Tools


Try next: Pick 20 real user questions. Measure retrieval hit rate before you touch the generator.