Toolmingo
Guides10 min read

What is RAG? Retrieval-Augmented Generation Explained

Learn what Retrieval-Augmented Generation (RAG) is, how it works, when to use it vs fine-tuning, and how to build a simple RAG pipeline with code examples in Python.

Retrieval-Augmented Generation (RAG) is an AI architecture that gives a Large Language Model (LLM) access to external knowledge at inference time. Instead of relying solely on what the model learned during training, RAG retrieves relevant documents from a knowledge base and augments the prompt before asking the LLM to generate an answer.

RAG solves two of the biggest problems with standalone LLMs: knowledge cutoff (the model does not know recent events) and hallucination (the model invents facts it does not know). It is now the most widely used pattern for building production AI applications.

RAG in 30 seconds

Problem with raw LLMs RAG solution
Training data has a cutoff date Retrieve up-to-date documents at query time
Model hallucinates missing facts Provide verified source documents in the prompt
Cannot access private/proprietary data Index your own documents in a vector store
Context window is limited Retrieve only the most relevant chunks
Fine-tuning is expensive RAG requires no model retraining

How RAG works — step by step

User query
    │
    ▼
[1. Embed query]  →  query vector
    │
    ▼
[2. Vector search]  →  top-K relevant chunks
    │
    ▼
[3. Build prompt]  →  "Context: <chunks>\n\nQuestion: <query>"
    │
    ▼
[4. LLM generates]  →  grounded answer
    │
    ▼
Answer (with source citations)

Offline indexing (done once or periodically)

  1. Load documents — PDFs, web pages, database rows, Markdown files
  2. Chunk — split documents into passages (e.g., 512 tokens with 50-token overlap)
  3. Embed — convert each chunk to a dense vector using an embedding model
  4. Store — write vectors + metadata to a vector database (Chroma, Pinecone, pgvector, etc.)

Online retrieval (every query)

  1. Embed the user question using the same embedding model
  2. Similarity search — find the top-K chunks whose vectors are closest to the query vector
  3. Stuff into prompt — insert retrieved text as context for the LLM
  4. Generate — the LLM reads the context and answers the question

RAG architecture diagram

Documents ──► Chunker ──► Embedder ──► Vector DB
                                           │
User Query ──────► Embedder ──► Retriever ─┘
                                    │
                                    ▼
                            [Context + Query]
                                    │
                                    ▼
                                   LLM
                                    │
                                    ▼
                                 Answer

Simple RAG pipeline in Python

# pip install openai chromadb

import openai
import chromadb
from chromadb.utils import embedding_functions

# 1. Setup
client = openai.OpenAI()   # uses OPENAI_API_KEY env var
embed_fn = embedding_functions.OpenAIEmbeddingFunction(
    model_name="text-embedding-3-small"
)

chroma = chromadb.Client()
collection = chroma.create_collection("docs", embedding_function=embed_fn)

# 2. Index documents
documents = [
    "Python was created by Guido van Rossum and released in 1991.",
    "FastAPI is a modern Python web framework based on type hints.",
    "RAG stands for Retrieval-Augmented Generation.",
    "Vector databases store high-dimensional embeddings for similarity search.",
]

collection.add(
    documents=documents,
    ids=[f"doc_{i}" for i in range(len(documents))],
)

# 3. Query function
def rag_query(question: str, top_k: int = 2) -> str:
    # Retrieve relevant chunks
    results = collection.query(query_texts=[question], n_results=top_k)
    context = "\n".join(results["documents"][0])

    # Build augmented prompt
    prompt = f"""Answer the question using ONLY the context below.
If the context does not contain the answer, say "I don't know."

Context:
{context}

Question: {question}
Answer:"""

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
    )
    return response.choices[0].message.content

# 4. Use it
print(rag_query("When was Python released?"))
# "Python was released in 1991."

print(rag_query("What is FastAPI?"))
# "FastAPI is a modern Python web framework based on type hints."

print(rag_query("Who invented JavaScript?"))
# "I don't know."  <- grounded, no hallucination

RAG with LangChain

# pip install langchain langchain-openai langchain-chroma

from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader

# Load and split
loader = TextLoader("my_docs.txt")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

# Embed and store
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# RAG chain
prompt = ChatPromptTemplate.from_template("""
Answer the question based only on the following context:

{context}

Question: {question}
""")

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

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

answer = chain.invoke("What does this document say about X?")
print(answer.content)

Chunking strategies

How you split documents dramatically affects retrieval quality.

Strategy How Best for
Fixed-size Split every N tokens Simple, fast, good default
Recursive Try paragraphs then sentences then words General text (LangChain default)
Semantic Group sentences by embedding similarity Dense topic documents
Document-aware Split by headings, code blocks Markdown, HTML, code
Sliding window Overlapping chunks (50-100 token overlap) Prevents context loss at boundaries

Rule of thumb: 256–512 tokens per chunk with 10–15% overlap. Experiment with your data.


Embedding models

Model Provider Dimensions Notes
text-embedding-3-small OpenAI 1,536 Fast, cheap, good quality
text-embedding-3-large OpenAI 3,072 Higher quality, more expensive
text-embedding-ada-002 OpenAI 1,536 Legacy, still widely used
all-MiniLM-L6-v2 HuggingFace 384 Free, runs locally, fast
bge-large-en-v1.5 HuggingFace 1,024 Strong open-source model
embed-english-v3.0 Cohere 1,024 Good multilingual support

Always use the same embedding model for indexing and querying.


Vector databases

Database Type Notes
Chroma Open-source, local Easy start, great for prototyping
pgvector PostgreSQL extension Keep data in Postgres, production-ready
Pinecone Managed cloud Serverless, scales automatically
Weaviate Open-source / cloud Hybrid search (dense + sparse)
Qdrant Open-source / cloud Fast, Rust-based, payload filtering
Milvus Open-source / cloud High scale, billions of vectors
FAISS Meta library In-memory, no persistence, fast

For most projects: Chroma (dev) → pgvector or Pinecone (production).


RAG vs Fine-tuning vs Prompt engineering

Prompt engineering RAG Fine-tuning
When Built-in knowledge sufficient Need external / private data Need style / format changes
Cost Low (just prompt tokens) Medium (embedding + retrieval) High (training compute)
Freshness Static (training cutoff) Real-time (update index) Static (re-train to update)
Privacy Data in prompt only Data in your vector store Data used in training
Accuracy Depends on model knowledge High (grounded in docs) High (baked into weights)
Complexity Low Medium High
Hallucination risk High for unknown facts Low (sources in context) Medium

Decision rule:

  • Need factual accuracy over private docs → RAG
  • Need the model to learn a new tone or format → Fine-tuning
  • Want the model to use existing knowledge better → Prompt engineering
  • Best results → RAG + fine-tuned model

Common RAG failure modes

Problem Symptom Fix
Retrieval misses Relevant doc not returned Better chunking, reranking, hybrid search
Context stuffing Too many chunks, LLM confused Reduce top-K, add reranker
Chunk boundary cut-off Answer split across chunks Add overlap, use semantic chunking
Embedding mismatch Query and doc use different models Always use same model for both
Hallucination despite context LLM ignores retrieved docs Stronger system prompt, lower temperature
Stale index Old data returned Implement incremental indexing or TTL
Slow retrieval High latency Use ANN index (HNSW), add caching
No answer (false negative) "I don't know" when doc exists Expand top-K, tune similarity threshold

Advanced RAG patterns

Hybrid search (dense + sparse)

Combine vector similarity with keyword search (BM25) for better recall:

# Using Weaviate hybrid search
results = collection.query.hybrid(
    query="Python web framework",
    alpha=0.5,  # 0 = BM25 only, 1 = vector only
    limit=5
)

Reranking

Use a cross-encoder to re-score retrieved chunks before passing to LLM:

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

scores = reranker.predict([(query, chunk) for chunk in top_chunks])
sorted_chunks = [c for _, c in sorted(zip(scores, top_chunks), reverse=True)]
top_reranked = sorted_chunks[:3]

HyDE (Hypothetical Document Embeddings)

Ask the LLM to generate a hypothetical answer first, then embed that for retrieval:

# 1. Generate hypothetical answer
hypo_answer = llm.generate(f"Write a passage that answers: {question}")
# 2. Embed the hypothetical answer (not the question)
query_vector = embed(hypo_answer)
# 3. Search with this vector — often retrieves better results

Multi-query retrieval

Generate multiple query variants to improve recall:

queries = llm.generate(f"Generate 3 different phrasings of: {question}")
all_chunks = [retrieve(q) for q in queries]
unique_chunks = deduplicate(all_chunks)

RAG evaluation metrics

Metric What it measures Tool
Context Precision Are retrieved chunks relevant? RAGAS
Context Recall Did retrieval find all needed info? RAGAS
Faithfulness Is the answer grounded in context? RAGAS
Answer Relevancy Does the answer address the question? RAGAS
Latency End-to-end response time Custom timing
# pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision

results = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(results)

RAG stack options

Component Lightweight Production
Document loader Manual / pypdf Unstructured.io
Chunker RecursiveCharacterTextSplitter Semantic chunker
Embeddings text-embedding-3-small text-embedding-3-large or BGE
Vector DB Chroma (local) pgvector / Pinecone / Qdrant
Retriever Simple similarity Hybrid + reranker
LLM GPT-4o-mini GPT-4o / Claude Opus 4.6
Framework None (raw API) LangChain / LlamaIndex
Evaluation Manual RAGAS
Observability Print logs LangSmith / Langfuse

Common mistakes

Mistake Why it hurts Fix
Embedding entire documents Retrieves too much noise Chunk to 256–512 tokens
Skipping overlap between chunks Cuts context at boundaries Add 10–15% overlap
Using different embedding models for index vs query Vectors are incomparable Always use the same model
No reranking for top-K > 5 LLM gets confused by noise Add cross-encoder reranker
Trusting retrieval blindly Missing chunks cause hallucination Log retrieved context, monitor faithfulness
Not updating the index Stale, wrong answers Set up periodic re-indexing or upsert
Huge chunks to include more context LLM "lost in the middle" problem Keep chunks small, retrieve more of them
No evaluation dataset No way to measure improvements Build a Q&A ground truth set early

RAG vs related terms

Term What it is
RAG Retrieval + generation: fetch docs at query time to ground the LLM
Fine-tuning Train the LLM on new examples to update its weights
Semantic search Find similar documents by vector distance — the retrieval step of RAG
Vector database Database optimised for storing and querying embedding vectors
Embedding A dense numerical representation of text capturing semantic meaning
Context window The token limit of the LLM — RAG helps by selecting only relevant text
Prompt engineering Crafting instructions for the LLM — RAG provides the content alongside
Agent An LLM that decides when and what to retrieve (vs static RAG pipeline)
GraphRAG Microsoft technique: build a knowledge graph over docs for better reasoning

Frequently asked questions

Why use RAG instead of just making the context window bigger? Even 1M-token context windows have limits: cost scales linearly with tokens, latency increases, and LLMs suffer "lost in the middle" — they attend worse to content in the middle of a long context. RAG retrieves only the handful of relevant passages, keeping prompts focused and cheap.

Does RAG eliminate hallucination? No — it reduces hallucination when the answer exists in the knowledge base. If the retrieved chunks are irrelevant or the LLM ignores them, it can still hallucinate. Combine RAG with faithfulness evaluation and a strong system prompt such as "answer ONLY from the context".

When should I fine-tune instead of RAG? Fine-tune when you need the model to change its behavior or format — writing in your brand tone, following a specific output schema, or learning domain-specific abbreviations. Use RAG when you need the model to access specific facts or documents.

How do I choose chunk size? Start with 512 tokens and 50-token overlap. Run retrieval experiments: if recall is low (the right chunk is never retrieved), try smaller chunks. If the LLM complains about missing context, try larger chunks or increase top-K.

Can RAG work without a vector database? Yes — for small knowledge bases (fewer than 100 documents), you can embed everything in memory with FAISS or even a simple cosine similarity loop. Vector databases add scalability, persistence, filtering, and hybrid search at the cost of infrastructure.

What is the difference between RAG and an AI agent? A basic RAG pipeline retrieves documents in a fixed sequence every time. An AI agent decides dynamically which tools to call, when to retrieve, how many times to retry, and can chain multiple retrieval steps. Think of RAG as one tool an agent might use, not a mutually exclusive pattern.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools