Lesson 18 LangChain & LlamaIndex
Theme:

Theory

Reading time: ~20 minutes (~1500 words)  ·  Prerequisites: Familiarity with LLM API calls (Lessons 02–03), basic RAG concepts (Lesson 10)

1. The Case for Orchestration Frameworks

When you first start building with LLMs, the workflow is simple: construct a prompt, send it to an API, parse the response. A few dozen lines of Python and the anthropic or openai SDK are all you need.

As applications grow more complex, patterns recur. You need to chain multiple LLM calls together, where the output of one becomes the input of the next. You need to manage conversation history across turns. You need to let the model call external tools and decide which tool to use. You need to load documents, split them into chunks, embed them, store them in a vector database, retrieve relevant chunks at query time, and synthesise an answer.

You could build all of this from scratch — and for simple applications, you should. But orchestration frameworks like LangChain and LlamaIndex exist to provide tested, reusable implementations of these common patterns. They save you from re-inventing the wheel, but they also add abstraction layers that can make debugging harder and hide what the model actually receives.

The central question is not "which framework should I use?" but rather: does my application's complexity justify the overhead of a framework?

2. LangChain: The General-Purpose Toolkit

LangChain is the most widely adopted LLM orchestration framework. Originally released in late 2022, it has evolved significantly. The current recommended approach is LangChain Expression Language (LCEL), which replaced the older LLMChain and SequentialChain classes.

Core Abstractions

LangChain is built around the Runnable interface. Every component — prompt templates, chat models, output parsers, retrievers, tools — implements this interface, which provides four methods: invoke() for single inputs, batch() for multiple inputs, stream() for streaming output, and their async equivalents. This uniform interface is what makes the pipe operator work.

Chains with LCEL

The simplest LCEL chain connects a prompt template to a model to a parser:

from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "Summarise the following text in {style} style."),
    ("human", "{text}")
])
model = ChatAnthropic(model="claude-sonnet-4-20250514")
parser = StrOutputParser()

chain = prompt | model | parser
result = chain.invoke({"style": "bullet-point", "text": "...long document..."})

You can extend this by piping the result into another chain. For example, after summarising, you could extract keywords, translate the summary, or format it as JSON. Each step is a composable Runnable.

Memory

LangChain provides several memory strategies for multi-turn conversations:

In practice, many teams implement their own memory management because the built-in options do not always align with their specific requirements. This is one area where frameworks sometimes add complexity without sufficient payoff.

Agents and Tools

A LangChain agent is an LLM that can decide which tools to call and in what order. You define tools — functions the model can invoke — and the agent runtime handles the loop: the model proposes a tool call, the runtime executes it, the result is fed back to the model, and the model decides whether to call another tool or return a final answer.

from langchain_core.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor

@tool
def search_database(query: str) -> str:
    """Search the product database for items matching the query."""
    # ... actual database search logic ...
    return results

tools = [search_database]
agent = create_tool_calling_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
response = executor.invoke({"input": "Find all products under $50"})

3. LlamaIndex: The Data Framework

LlamaIndex takes a different philosophy. While LangChain is a general-purpose toolkit, LlamaIndex is purpose-built for one thing: connecting LLMs to your data. If your primary pattern is "load documents, index them, query them with natural language," LlamaIndex provides a more streamlined experience.

The Data Pipeline

LlamaIndex defines a clear pipeline for data-augmented LLM applications:

  1. Loading — Ingest data from files, databases, APIs, or web pages using connectors. SimpleDirectoryReader handles local files; LlamaHub provides hundreds of community connectors.
  2. Indexing — Parse documents into nodes (chunks), generate embeddings, and store them in an index. VectorStoreIndex is the most common choice for semantic similarity search.
  3. Querying — Convert the index into a query engine that retrieves relevant nodes and synthesises an answer using the LLM.
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.anthropic import Anthropic
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.llm = Anthropic(model="claude-sonnet-4-20250514")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = SimpleDirectoryReader("./company_docs").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("What is our refund policy?")

Index Types

LlamaIndex offers several index types, each suited to different query patterns:

Index TypeHow It WorksBest For
VectorStoreIndexEmbeds nodes, retrieves by cosine similaritySemantic search, most RAG use cases
SummaryIndexScans every node sequentiallySummarisation over entire corpus
TreeIndexBuilds a hierarchical summary treeMulti-level summarisation
KeywordTableIndexExtracts keywords, matches by keyword overlapKeyword-based retrieval

Response Synthesis

After retrieval, LlamaIndex must synthesise an answer from the retrieved nodes. The framework provides several strategies:

4. When to Use What

Here is a practical decision framework:

Use direct API calls when:

Use LangChain when:

Use LlamaIndex when:

These are not mutually exclusive. Some teams use LlamaIndex for their retrieval layer and LangChain for the agent/orchestration layer on top. Others use neither and build everything with direct API calls plus a vector database client. The right answer depends on your team's needs, your application's complexity, and how much abstraction you are comfortable debugging through.

5. Common Pitfalls

6. Key Concepts Summary

ConceptWhat It MeansWhy It Matters
LCELLangChain's pipe-based composition syntaxThe modern way to build LangChain applications
RunnableUniversal interface for all LangChain componentsEnables composability via the pipe operator
VectorStoreIndexLlamaIndex's primary index for semantic searchThe default choice for most RAG applications
Query EngineCombines retrieval and synthesis in LlamaIndexThe main interface for asking questions of your data
Response SynthesizerStrategy for combining retrieved nodes into an answerAffects quality, cost, and latency of RAG responses
Framework vs. Direct APIThe decision to add an abstraction layer or notImpacts debugging, dependencies, and development speed

Further Reading

Official Documentation

Critical Perspectives

Integration Guides

Next lesson: Lesson 19 — Comparing LLM Providers — benchmarking Claude, GPT-4, Gemini, and open-source models head-to-head.