🌐 EN | 🇯🇵 JP

Chapter 4: RAG (Retrieval-Augmented Generation)

Building Knowledge-Grounded AI Systems

📖 Reading Time: 20-25 minutes 📊 Difficulty: Intermediate 💻 Code Examples: 6 📝 Exercises: 1

Learning Objectives

4.1 What is RAG?

RAG (Retrieval-Augmented Generation) is a technique that enhances LLM responses by retrieving relevant information from a knowledge base before generating answers.

RAG (Retrieval-Augmented Generation)

A two-step process: (1) Retrieve relevant documents from a knowledge base based on the user's query, (2) Generate a response using both the query and retrieved documents as context. This grounds the LLM's responses in factual, up-to-date information.

graph LR A[User Query] --> B[Retriever] B --> C[Vector Store] C --> D[Relevant Docs] D --> E[LLM + Context] A --> E E --> F[Grounded Response] style B fill:#667eea,color:#fff style C fill:#11998e,color:#fff style E fill:#f093fb,color:#fff

Why RAG?

Problem RAG Solution
LLMs have knowledge cutoffs Retrieve current information
LLMs can hallucinate facts Ground responses in real documents
LLMs lack domain knowledge Add custom knowledge bases
LLMs can't access private data Query internal documents securely

4.2 Document Loading

The first step is loading documents from various sources:

Required Dependencies

Install additional packages for document loaders:

pip install beautifulsoup4 pypdf  # For WebBaseLoader and PyPDFLoader
pip install chromadb              # For Chroma vector store
Code Example 1: Loading Documents
from langchain_community.document_loaders import (
    TextLoader,
    PyPDFLoader,
    WebBaseLoader,
    DirectoryLoader
)

# Load a text file
text_loader = TextLoader("./data/document.txt")
text_docs = text_loader.load()

# Load a PDF
pdf_loader = PyPDFLoader("./data/paper.pdf")
pdf_docs = pdf_loader.load()

# Load a web page
web_loader = WebBaseLoader("https://example.com/article")
web_docs = web_loader.load()

# Load all files in a directory
dir_loader = DirectoryLoader("./data/", glob="**/*.txt")
all_docs = dir_loader.load()

# Each document has content and metadata
for doc in text_docs:
    print(f"Content: {doc.page_content[:100]}...")
    print(f"Metadata: {doc.metadata}")

4.3 Text Splitting

Documents need to be split into chunks for efficient retrieval:

Code Example 2: Text Splitting Strategies
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter,
    CharacterTextSplitter
)

# Recommended: RecursiveCharacterTextSplitter
# Tries to split at natural boundaries (paragraphs, sentences, words)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # Maximum characters per chunk
    chunk_overlap=200,    # Overlap between chunks for context
    separators=["\n\n", "\n", " ", ""]  # Split priority
)

# Split documents
chunks = splitter.split_documents(text_docs)

print(f"Original documents: {len(text_docs)}")
print(f"After splitting: {len(chunks)} chunks")

# Each chunk maintains metadata
for i, chunk in enumerate(chunks[:3]):
    print(f"\nChunk {i}: {len(chunk.page_content)} chars")
    print(chunk.page_content[:100] + "...")

Chunking Best Practices (2025)

  • Semantic chunking: Keep related content together (paragraphs, sections)
  • Contextual headers: Include section headings in each chunk
  • Chunk size: 500-1500 tokens depending on your use case
  • Overlap: 10-20% overlap preserves context across chunk boundaries

4.4 Embeddings and Vector Stores

Embeddings convert text into numerical vectors for similarity search:

Code Example 3: Creating Embeddings and Vector Store
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# Create embeddings model
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Test embedding
test_embedding = embeddings.embed_query("What is machine learning?")
print(f"Embedding dimension: {len(test_embedding)}")  # 1536

# Create vector store from documents
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"  # Persist to disk
)

# Query the vector store
results = vectorstore.similarity_search(
    "How does neural network training work?",
    k=3  # Return top 3 results
)

for doc in results:
    print(f"Score: {doc.metadata.get('score', 'N/A')}")
    print(f"Content: {doc.page_content[:200]}...\n")

Popular Vector Stores

Vector Store Type Best For
Chroma Local/Cloud Development, small-medium scale
FAISS Local High-performance local search
Pinecone Cloud Production, managed service
Weaviate Cloud/Self-hosted Hybrid search, filtering
Milvus Cloud/Self-hosted Large-scale production

4.5 Building a RAG Chain

Code Example 4: Complete RAG Pipeline
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# Setup components
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(model="gpt-4")

# Create RAG prompt
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a helpful assistant. Answer the question based only on the following context.
If the context doesn't contain relevant information, say so.

Context:
{context}"""),
    ("human", "{question}")
])

# Helper function to format documents
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# Build RAG chain with LCEL
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

# Use the chain
response = rag_chain.invoke("What are the main types of machine learning?")
print(response)

4.6 Advanced RAG Patterns

Code Example 5: RAG with Source Citations
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field
from typing import List

# Define structured output with citations
class AnswerWithSources(BaseModel):
    answer: str = Field(description="The answer to the question")
    sources: List[str] = Field(description="List of source documents used")

# Create chain with structured output
llm_with_sources = ChatOpenAI(model="gpt-4").with_structured_output(AnswerWithSources)

rag_prompt_with_sources = ChatPromptTemplate.from_messages([
    ("system", """Answer based on the context. Include which sources you used.

Context:
{context}"""),
    ("human", "{question}")
])

def format_docs_with_sources(docs):
    formatted = []
    for i, doc in enumerate(docs):
        source = doc.metadata.get("source", f"doc_{i}")
        formatted.append(f"[Source: {source}]\n{doc.page_content}")
    return "\n\n".join(formatted)

rag_chain_with_sources = (
    {"context": retriever | format_docs_with_sources, "question": RunnablePassthrough()}
    | rag_prompt_with_sources
    | llm_with_sources
)

result = rag_chain_with_sources.invoke("What is deep learning?")
print(f"Answer: {result.answer}")
print(f"Sources: {result.sources}")
Code Example 6: Hybrid Search (Vector + Keyword)
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# Create keyword-based retriever
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3

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

# Combine with ensemble (hybrid search)
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # 40% keyword, 60% semantic
)

# Use in RAG chain
results = ensemble_retriever.invoke("neural network optimization techniques")
for doc in results:
    print(doc.page_content[:100] + "...")

Exercise

Exercise: Build a Documentation Q&A System

Build a RAG system for answering questions about a documentation website:

  1. Load documentation pages using WebBaseLoader
  2. Split into chunks with RecursiveCharacterTextSplitter
  3. Store in a Chroma vector database
  4. Create a RAG chain that answers questions with source citations

Test with questions like: "How do I install this library?" and "What are the main features?"

Summary

Disclaimer

This content is provided for educational purposes. LangChain is developed and maintained by LangChain Inc.