RAG & Knowledge Systems

Introduction to RAG

Understand Retrieval-Augmented Generation (RAG) — the technique that connects LLMs to your own data for accurate, grounded responses.

Introduction to RAG

Retrieval-Augmented Generation (RAG) is a technique that combines LLMs with external knowledge retrieval. Instead of relying solely on what the model learned during training, RAG retrieves relevant documents and includes them in the prompt.

The Problem RAG Solves

LLMs have two fundamental limitations:

  1. Knowledge cutoff: Training data ends at a specific date
  2. Hallucination: Models confidently generate incorrect information

RAG addresses both by grounding the model's responses in real, current data.

Without RAG:
User: "What's our company's refund policy?"
LLM:  "Most companies offer 30-day refunds..." (generic, potentially wrong)

With RAG:
User: "What's our company's refund policy?"
[Retrieves: refund-policy.pdf → "14-day money-back guarantee..."]
LLM:  "Your company offers a 14-day money-back guarantee..." (accurate!)

How RAG Works

                    ┌─────────────────┐
User Question ────→│  1. Embed Query  │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ 2. Search Vector│
                    │    Database     │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ 3. Retrieve Top │
                    │    Documents    │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ 4. Combine with │
                    │   Original Query│
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ 5. Send to LLM  │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  6. LLM Responds│
                    │  with Context   │
                    └─────────────────┘

The RAG Pipeline Steps

  1. Indexing Phase (done once, ahead of time):

    • Load your documents (PDFs, web pages, databases)
    • Split them into chunks
    • Generate embeddings for each chunk
    • Store in a vector database
  2. Query Phase (happens per user query):

    • Convert the user's question into an embedding
    • Search the vector database for similar chunks
    • Retrieve the top-k most relevant chunks
    • Add them to the LLM prompt as context
    • Generate a response grounded in the retrieved documents

What Are Embeddings?

Embeddings are numerical representations of text that capture meaning. Similar texts have similar embeddings.

python
from openai import OpenAI

client = OpenAI()

def get_embedding(text):
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding  # List of 1536 floats

# Similar texts → similar embeddings
emb1 = get_embedding("How do I return a product?")
emb2 = get_embedding("What is your refund policy?")
emb3 = get_embedding("Tell me about the weather")

# emb1 and emb2 will be close (similar meaning)
# emb3 will be far from both (different topic)

Simple RAG Implementation

Here's a minimal RAG system using Python:

python
from openai import OpenAI
import numpy as np

client = OpenAI()

# Step 1: Your knowledge base
documents = [
    "Our refund policy allows returns within 14 days of purchase.",
    "Premium plans cost $49/month and include priority support.",
    "Free shipping on orders over $50 within the United States.",
    "Business hours are Monday to Friday, 9 AM to 6 PM IST.",
    "Contact support at support@example.com or call +91 98765 43210."
]

# Step 2: Create embeddings for all documents
def create_index(docs):
    embeddings = []
    for doc in docs:
        emb = get_embedding(doc)
        embeddings.append(emb)
    return np.array(embeddings)

doc_embeddings = create_index(documents)

# Step 3: Search for relevant documents
def search(query, top_k=3):
    query_emb = np.array(get_embedding(query))

    # Cosine similarity
    similarities = np.dot(doc_embeddings, query_emb) / (
        np.linalg.norm(doc_embeddings, axis=1) * np.linalg.norm(query_emb)
    )

    top_indices = similarities.argsort()[-top_k:][::-1]
    return [(documents[i], similarities[i]) for i in top_indices]

# Step 4: Generate answer with context
def rag_query(question):
    # Retrieve relevant documents
    results = search(question, top_k=3)
    context = "\n".join([doc for doc, score in results])

    # Generate answer
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": """Answer the user's question based ONLY on the
                provided context. If the answer isn't in the context,
                say "I don't have information about that."

                Context:
                """ + context
            },
            {
                "role": "user",
                "content": question
            }
        ],
        temperature=0.2
    )
    return response.choices[0].message.content

# Usage
answer = rag_query("What's the refund policy?")
print(answer)
# → "Returns are accepted within 14 days of purchase."

answer = rag_query("Do you offer free shipping?")
print(answer)
# → "Yes, free shipping is available on orders over $50 within the US."

Document Chunking

Large documents need to be split into smaller chunks for effective retrieval:

python
def chunk_text(text, chunk_size=500, overlap=50):
    """Split text into overlapping chunks."""
    words = text.split()
    chunks = []
    start = 0

    while start < len(words):
        end = start + chunk_size
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start = end - overlap  # Overlap for context continuity

    return chunks

Chunking Strategies

StrategyHowBest For
Fixed sizeSplit every N words/tokensSimple, predictable
Sentence-basedSplit at sentence boundariesNatural text
Paragraph-basedSplit at paragraph breaksStructured documents
SemanticUse LLM to find topic boundariesComplex documents
RecursiveTry large chunks, subdivide if too bigMixed-format documents

RAG vs Fine-Tuning

AspectRAGFine-Tuning
Data freshnessReal-time, always currentStatic after training
Setup effortModerate (build pipeline)High (prepare dataset, train)
CostPer-query (retrieval + LLM)Upfront training cost
Best forFactual Q&A, documentationStyle, format, domain expertise
Knowledge updatesJust update the documentsRetrain the model
TransparencyCan show source documentsBlack box

When to Use RAG

RAG is ideal for:

  • Internal documentation Q&A — Company wikis, policies, procedures
  • Customer support — Product docs, FAQs, troubleshooting guides
  • Legal/compliance — Search through contracts, regulations
  • Research — Query across papers, reports, data
  • E-commerce — Product information, inventory queries

RAG Limitations

  1. Retrieval quality — Bad retrieval = bad answers (garbage in, garbage out)
  2. Context window limits — Can only pass so many chunks to the LLM
  3. Latency — Extra step (retrieval) adds response time
  4. Chunking challenges — Poor chunking loses context
  5. Not a replacement for fine-tuning — RAG handles knowledge, not behavior

Key Takeaways

  1. RAG = Retrieval + Generation — Fetch relevant docs, then answer with them
  2. Embeddings capture meaning — Similar questions find similar documents
  3. Chunking matters — How you split documents affects retrieval quality
  4. RAG is the #1 technique for building knowledge-grounded AI applications
  5. Start simple — A basic RAG pipeline can be built in under 100 lines of code

In the next topic, we'll dive deeper into vector databases and embeddings.