Cybersecurity

Why RAG Exists: The Hallucination Problem

Python LLMs RAG Fine-tuning MCP AI Agents Cloud GraphQL Databases HTML Embeddings Vector Search Semantic Search Hybrid Search
1,501 words

Retrieval-Augmented Generation (RAG) is the most practical technique for making LLMs answer questions using your own documents. This tutorial explains every component — from raw text to grounded, cited answers — with a complete Python project you can run today.

RAG Pipeline: Documents through chunking, embeddings, vector database, retrieval, reranking, context assembly, LLM generation, and citations
🎯 Key Takeaway: RAG combines document retrieval with LLM generation. Instead of asking the model to "remember" everything, you search your documents first, then give the LLM only the relevant pieces. This reduces hallucinations, provides citations, and keeps answers grounded in real data.

Why RAG Exists: The Hallucination Problem

Ask an LLM: "What is our company's refund policy?"

Without RAG, the model might:

  • Generate a plausible but incorrect policy
  • Use training data from other companies
  • Give a generic answer that doesn't match your actual documents

This isn't a bug — it's how LLMs work. They predict likely text based on patterns in training data. They don't "look up" your documents.

RAG solves this by searching your documents first, then giving the LLM only the relevant pieces as context.

⚠️ Without RAG

LLM guesses from training data

May hallucinate incorrect facts

No source attribution

Cannot access your documents

✅ With RAG

Searches actual documents

Grounds answer in real data

Provides source citations

Accesses your knowledge base

The RAG Pipeline: Every Component Explained

RAG has seven core components. Weakness in any one degrades the entire system.

1. Documents (Ingestion)

RAG starts with your data. This can be:

  • PDFs — policies, manuals, research papers
  • Web pages — documentation, articles, FAQs
  • Markdown/Text — notes, READMEs, specs
  • Databases — structured records exported as text
  • Emails/Chat — support conversations, threads

The ingestion step extracts clean text from these sources. PDF parsing, HTML stripping, and encoding normalization happen here.

2. Chunking

LLMs have limited context windows. You can't send entire libraries. Chunking splits documents into smaller, overlapping pieces.

# Word-level chunking with overlap
def chunk_text(text, chunk_size=100, overlap=20):
    words = text.split()
    chunks, start = [], 0
    while start < len(words):
        end = start + chunk_size
        chunks.append(" ".join(words[start:end]))
        start += chunk_size - overlap  # overlap preserves context
    return chunks

Why overlap? Without it, a sentence split across two chunks loses its meaning. Overlap ensures each chunk has enough surrounding context to be understood independently.

Chunk size matters: Too small → fragmented context. Too large → diluted relevance. Typical range: 200–500 tokens for most use cases.

3. Embeddings

Each chunk needs to be converted into a numerical vector that captures its meaning. This is what embeddings do.

A good embedding model places semantically similar texts close together in vector space. "Python functions" and "defining functions" end up near each other. "Python functions" and "car engines" end up far apart.

Popular embedding models:

ModelDimensionsMax TokensType
OpenAI text-embedding-3-small15368191API
OpenAI text-embedding-3-large30728191API
all-MiniLM-L6-v2384256Local
nomic-embed-text7688192Local
bge-large-en-v1.51024512Local

4. Vector Database

The vector database stores all chunk embeddings and enables fast similarity search. When a query arrives, it finds the most similar chunks.

Common options:

  • FAISS — Facebook's library, fast, local, no server needed
  • Chroma — lightweight, developer-friendly, good for prototyping
  • Qdrant — production-ready, filtering, scaling
  • Pinecone — fully managed cloud service
  • Weaviate — hybrid search, GraphQL API

5. Retrieval

When a user asks a question, the system:

  1. Converts the question into an embedding
  2. Searches the vector database for the top-k most similar chunks
  3. Returns the chunks with similarity scores

Top-k is typically 3–10 chunks. Too few → missing relevant information. Too many → diluted context and higher cost.

6. Reranking

Initial retrieval is fast but approximate. Reranking re-scores the top results using a more precise model.

Why rerank? Vector search uses a bi-encoder (fast, approximate). Reranking uses a cross-encoder (slow, precise). The cross-encoder reads the query and each chunk together, producing a more accurate relevance score.

Learn more in our Reranking in RAG tutorial.

7. Context Assembly and Generation

The final step assembles retrieved chunks into a context string and sends it to the LLM with the user's question.

# Context assembly
context = ""
for i, (chunk, score) in enumerate(retrieved_chunks):
    context += f"[Source {i+1}: {chunk.title}]\n{chunk.text}\n\n"

# LLM prompt with context
prompt = f"""Answer the question using ONLY the provided context.

Context:
{context}

Question: {question}

Cite sources [Source N] in your answer."""

Complete Python Project: Mini-RAG System

Here's a working RAG system you can run with zero dependencies. It demonstrates every step of the pipeline using TF-IDF vectors instead of neural embeddings.

import math, re
from collections import Counter

# 1. CHUNKING
def chunk_text(text, size=100, overlap=20):
    words = text.split()
    chunks, start = [], 0
    while start < len(words):
        chunks.append(" ".join(words[start:start+size]))
        start += size - overlap
    return chunks

# 2. TF-IDF EMBEDDINGS
def build_idf(docs):
    n = len(docs)
    df = Counter()
    for doc in docs:
        for t in set(re.findall(r'[a-z]+', doc.lower())):
            df[t] += 1
    return {t: math.log((n+1)/(c+1))+1 for t,c in df.items()}

def tfidf(text, idf):
    tokens = re.findall(r'[a-z]+', text.lower())
    tf = Counter(tokens)
    total = len(tokens) or 1
    return {t: (c/total)*idf.get(t,1) for t,c in tf.items()}

def cosine(a, b):
    common = set(a) & set(b)
    dot = sum(a[k]*b[k] for k in common)
    return dot / (math.sqrt(sum(v**2 for v in a.values()))
              * math.sqrt(sum(v**2 for v in b.values())))

# 3. RETRIEVAL
idf = build_idf(chunks)
q_vec = tfidf("What web frameworks does Python have?", idf)
scores = [(i, cosine(q_vec, tfidf(c, idf))) for i,c in enumerate(chunks)]
top_3 = sorted(scores, key=lambda x: x[1], reverse=True)[:3]

# 4. CONTEXT + ANSWER
context = "\n\n".join(chunks[i] for i,_ in top_3)
# Send context + question to your LLM

The full demo with evaluation, reranking, and mock LLM is available in the article repository. It runs with zero external dependencies.

Evaluation: How Do You Know It Works?

RAG evaluation measures two things: retrieval quality and answer quality.

Retrieval Metrics

MetricWhat It MeasuresGood Value
Precision@KHow many of top-K results are relevant?> 0.7
Recall@KHow many relevant results are in top-K?> 0.8
MRRPosition of first relevant result> 0.7
NDCGRanking quality of all results> 0.8

Answer Quality Metrics

  • Faithfulness — Is the answer supported by the retrieved context?
  • Answer Relevance — Does the answer actually address the question?
  • Context Relevance — Is the retrieved context actually useful?

See our RAG Evaluation tutorial for detailed benchmarks.

Common RAG Mistakes

Common Mistake 1: Chunks too large

Large chunks reduce retrieval precision. The model gets the whole document when it only needed one paragraph.

Common Mistake 2: No overlap

Sentences split across chunk boundaries lose meaning. Always use 10–20% overlap.

Common Mistake 3: Ignoring metadata

Without source titles, dates, or categories, you can't filter or cite properly.

Common Mistake 4: No reranking

Vector search is approximate. Reranking catches relevant chunks that initial retrieval missed.

Common Mistake 5: Sending everything to the LLM

More context ≠ better answers. Curate the top 3–5 chunks, not the top 20.

Read more in Why RAG Systems Still Hallucinate.

Production Considerations

Building a prototype is easy. Making it production-ready requires addressing:

  • Document updates — How do you re-index when documents change?
  • Access control — Different users should see different documents
  • Latency — Vector search + reranking + LLM = multiple network calls
  • Cost — Embedding API calls, LLM tokens, vector DB hosting
  • Monitoring — Track retrieval quality, answer quality, and user satisfaction
  • Failure recovery — What happens when the vector DB is unavailable?

See our From RAG Prototype to Production guide.

🔑 Key Takeaway: RAG is not just "add a vector database." Every component — chunking strategy, embedding model, retrieval count, reranking, context assembly — affects answer quality. Measure retrieval and answer quality separately, because good retrieval doesn't guarantee good answers.

Related BestWordz Tutorials

FAQ

Do I need a vector database for RAG?

For small datasets (under 10,000 chunks), you can use in-memory similarity search with TF-IDF. Vector databases like FAISS, Chroma, or Qdrant are needed for larger datasets where you need fast nearest-neighbor search.

What chunk size should I use?

Start with 200–500 tokens per chunk with 10–20% overlap. Too small loses context; too large dilutes relevance. The optimal size depends on your documents and use case — test with real queries.

Can RAG eliminate hallucinations?

RAG significantly reduces hallucinations by grounding answers in retrieved documents, but doesn't eliminate them entirely. The LLM can still misinterpret context or generate unsupported text. Always verify critical answers.

How is RAG different from fine-tuning?

RAG retrieves relevant documents at query time and includes them as context. Fine-tuning modifies the model's weights during training. RAG is better for frequently changing data and provides citations. Fine-tuning is better for adapting model behavior and style.

Do I need embeddings for RAG?

Yes, for semantic search. Embeddings capture meaning, so "How do I define a function?" matches a chunk about "def keyword" even without exact word overlap. Without embeddings, you'd rely only on keyword matching (BM25), which misses synonyms and paraphrases.

Discuss this topic on BestWordz Community.

Continue Learning: RAG Fundamentals

From embeddings to production RAG systems

  1. The Five Types of Agent Memory
  2. Why RAG Exists: The Hallucination Problem (this article)
  3. What Are Embeddings?
  4. Hybrid Search: Combining BM25 and Vector Search
  5. RAG Architecture Explained: Every Component of a Retrieval-Augmented AI System