Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LangChain and LlamaIndex

🎯 Goal

After reading this chapter:

  • You know the difference between LangChain and LlamaIndex frameworks
  • You can build document loading, splitting, embedding pipelines
  • You can work with chains and agents (LangChain)
  • You build fast RAG with indexes (LlamaIndex)
  • You also get familiar with modern alternatives (Pydantic AI, Instructor, raw API)

**Note:**In 2024-2026, industry sentiment is moving awayfrom LangChain (too complex, excessive abstraction). Modern approach: raw API + Instructor + minimal framework. But LangChain is still used in many projects — you should know it.

What to learn

  • LangChain: chains, agents, memory, callbacks
  • LangChain LCEL(LangChain Expression Language)
  • LlamaIndex: indexes, retrievers, query engines
  • Document loaders — PDF, HTML, Notion, GitHub
  • Text splitters — RecursiveCharacter, Markdown, Code
  • Modern alternatives — Pydantic AI, Instructor, raw API
  • LangGraph — multi-agent workflows
  • LangSmith — observability

Libraries

pip install langchain langchain-openai langchain-anthropic langchain-community
pip install llama-index llama-index-llms-openai
pip install pydantic-ai instructor
pip install unstructured pypdf                # document loading

Framework comparison

LangChainLlamaIndexRaw API + Instructor
Learning curveSteepMediumLow
RAG supportGoodExcellentManual
AgentsComplexGoodNeeds LangGraph
ProductionMixed reviewsGoodBest
PerformanceSlowOKFastest
Industry trend⬇️⬆️⬆️⬆️
Code clarityAbstractBetterClearest

Recommendation:

  • New project → raw API + Instructor + LlamaIndex(for RAG)
  • Existing LangChain — keep, but migrate for new features
  • Complex agent workflows → LangGraph

Code examples

LangChain — basic chain

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# LCEL syntax (new, recommended)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant. Language: {language}"),
    ("user", "{question}"),
])

chain = prompt | llm | StrOutputParser()

# Run
result = chain.invoke({"language": "uzbek", "question": "What is Python?"})
print(result)

LangChain — RAG (simple)

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 1. Load
loader = PyPDFLoader("document.pdf")
docs = loader.load()

# 2. Split
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
chunks = splitter.split_documents(docs)

# 3. Embed + store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)

# 4. Retrieve + answer
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(
    llm=ChatOpenAI(model="gpt-4o-mini"),
    retriever=retriever,
    return_source_documents=True,
)

result = qa_chain.invoke({"query": "What is said about the document?"})
print(result["result"])

LlamaIndex — quickest RAG

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core import Settings

# Settings (global)
Settings.llm = OpenAI(model="gpt-4o-mini")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

# 1. Load (PDF, txt, markdown — various)
documents = SimpleDirectoryReader("data/").load_data()

# 2. Index (automatic embedding + chunk)
index = VectorStoreIndex.from_documents(documents)

# 3. Query
query_engine = index.as_query_engine(similarity_top_k=5)
response = query_engine.query("About this document")
print(response)
print(response.source_nodes)  # which chunks it came from

LlamaIndex — Chat engine (multi-turn)

from llama_index.core.memory import ChatMemoryBuffer

memory = ChatMemoryBuffer.from_defaults(token_limit=4000)

chat_engine = index.as_chat_engine(
    chat_mode="context",
    memory=memory,
    system_prompt="You are an experienced assistant. Answer only based on the given context.",
)

response = chat_engine.chat("Explain about this project")
print(response.response)

# Follow-up
response = chat_engine.chat("What are the main challenges?")

Pydantic AI — modern alternative

from pydantic_ai import Agent
from pydantic import BaseModel

class WeatherInfo(BaseModel):
    temperature: float
    condition: str
    humidity: int

weather_agent = Agent(
    model="openai:gpt-4o-mini",
    result_type=WeatherInfo,
    system_prompt="You are a weather agent. Estimate information for the given city.",
)

result = weather_agent.run_sync("Weather in Toshkent")
print(result.data)  # Type-safe WeatherInfo object

Instructor — fully type-safe

import instructor
from openai import OpenAI
from pydantic import BaseModel

client = instructor.from_openai(OpenAI())

class Person(BaseModel):
    name: str
    age: int
    occupation: str

# Guaranteed structured output (retries on failure)
person = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=Person,
    messages=[{"role": "user", "content": "My name is Ali, I am 30 years old, a developer"}],
)

print(person)  # Person(name="Ali", age=30, occupation="developer")

LangGraph — multi-agent

from langgraph.graph import StateGraph, END
from typing import TypedDict

class AgentState(TypedDict):
    question: str
    research: str
    answer: str

# Node functions
def researcher(state):
    # Search/research
    return {"research": "Information found..."}

def writer(state):
    # Generate answer
    return {"answer": f"Answer:{state['research']}"}

def reviewer(state):
    # Review
    if len(state["answer"]) < 50:
        return {"answer": state["answer"], "needs_revision": True}
    return {"answer": state["answer"], "needs_revision": False}

# Build graph
workflow = StateGraph(AgentState)
workflow.add_node("researcher", researcher)
workflow.add_node("writer", writer)
workflow.add_node("reviewer", reviewer)

workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "writer")
workflow.add_edge("writer", "reviewer")

workflow.add_conditional_edges(
    "reviewer",
    lambda x: "writer" if x.get("needs_revision") else END,
)

app = workflow.compile()
result = app.invoke({"question": "What is Python?"})

Document loaders — variety

# PDF
from langchain_community.document_loaders import PyPDFLoader
docs = PyPDFLoader("file.pdf").load()

# HTML / Website
from langchain_community.document_loaders import WebBaseLoader
docs = WebBaseLoader("https://example.com").load()

# YouTube transcript
from langchain_community.document_loaders import YoutubeLoader
docs = YoutubeLoader.from_youtube_url("https://...", add_video_info=True).load()

# Notion
from langchain_community.document_loaders import NotionDirectoryLoader
docs = NotionDirectoryLoader("notion_export/").load()

# GitHub
from langchain_community.document_loaders import GitHubIssuesLoader
docs = GitHubIssuesLoader("owner/repo", access_token="...").load()

# CSV/Excel
from langchain_community.document_loaders import CSVLoader
docs = CSVLoader("data.csv").load()

Text splitters

from langchain.text_splitter import (
    RecursiveCharacterTextSplitter,
    MarkdownHeaderTextSplitter,
    CharacterTextSplitter,
)

# Recursive — most common (with various separators)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
)

# Markdown — by header
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
])

# Code — semantic splitting
from langchain.text_splitter import Language, RecursiveCharacterTextSplitter
py_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=1000, chunk_overlap=100
)

Backend integration

FastAPI + LlamaIndex RAG service

from fastapi import FastAPI
from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app):
    # Load persisted index
    storage_context = StorageContext.from_defaults(persist_dir="./index_storage")
    app.state.index = load_index_from_storage(storage_context)
    app.state.query_engine = app.state.index.as_query_engine(similarity_top_k=5)
    yield

app = FastAPI(lifespan=lifespan)

class QueryRequest(BaseModel):
    question: str

@app.post("/query")
async def query(req: QueryRequest):
    response = await app.state.query_engine.aquery(req.question)
    return {
        "answer": str(response),
        "sources": [
            {"text": node.text[:200], "score": node.score}
            for node in response.source_nodes
        ],
    }

Production architecture

User → FastAPI → LlamaIndex (in-memory) → Qdrant (vectors) → LLM API
                       ↓
                   Redis (cache)
                       ↓
                 Postgres (history, logs)
                       ↓
                  Langfuse (observability)

Resources

LangChain

LlamaIndex

Modern alternatives

Observability

  • Langfuselangfuse.com (open source)
  • LangSmith — from LangChain
  • Phoenix (Arize) — open source

🏋️ Exercises

🟢 Easy

  1. Simple chain with LangChain LCEL (prompt | llm | parser).
  2. RAG a PDF in 5 lines with LlamaIndex.
  3. Resume → structured Pydantic with Instructor.

🟡 Medium

  1. Multi-source RAG: PDF + website + YouTube transcript — combined index.
  2. Conversational RAG: multi-turn with chat history.
  3. LangGraph workflow: 3-agent pipeline (researcher → writer → reviewer).

🔴 Hard

  1. Production RAG service: LlamaIndex + Qdrant + FastAPI + Langfuse. 100+ documents, async query, source citations, monitoring.
  2. Framework comparison: Write the same RAG in LangChain, LlamaIndex, and raw API; compare time and accuracy.
  3. Migration: Move existing LangChain code to Pydantic AI or raw API.

Capstone

notebooks/month-05/04_langchain_llamaindex.md:

  • 50+ documents in Uzbek (PDFs, websites)
  • RAG index with LlamaIndex
  • Multi-turn chat engine
  • Source citations
  • FastAPI + Streamlit UI

✅ Checklist

  • LangChain LCEL syntax
  • LlamaIndex basic RAG
  • Pydantic AI / Instructor (modern)
  • Document loaders and text splitters
  • Multi-source RAG
  • Chat engine memory
  • LangGraph multi-agent
  • Production observability (Langfuse)

Moving on to Vector Databases.