Migrating from LangChain to LlamaIndex

How to migrate your RAG pipeline from LangChain 1.0 to LlamaIndex, with updated create_agent and FunctionAgent code examples for indexing, querying, and tool calling.

From: LangChain To: LlamaIndex Difficulty: Medium
Migrating from LangChain to LlamaIndex

TL;DR

  • Yes, you can switch - LlamaIndex does the same retrieval job in three lines of code instead of seven-plus components
  • What breaks: agent code written before LangChain 1.0 (October 2025) is already stale on the LangChain side too - create_react_agent and AgentExecutor are legacy now regardless of which framework you pick
  • What improves: less boilerplate for pure RAG, built-in SentenceSplitter and VectorStoreIndex abstractions, native FunctionAgent for tool calling
  • Medium difficulty, 1-2 weeks for a typical RAG application, faster if you're only migrating the retrieval layer

Why Switch Your RAG Framework?

LangChain and LlamaIndex solve different problems. LangChain is an orchestration framework - it chains LLM calls, tool use, and workflows together. LlamaIndex is a data framework - it's built specifically for indexing, retrieving, and querying documents. If your primary workload is RAG, LlamaIndex does that job with less boilerplate and better defaults.

The practical difference shows up in code volume. A basic RAG pipeline in classic LangChain required a document loader, a text splitter, an embedding model, a vector store, a retriever, a prompt template, and a chain connecting them all. In LlamaIndex, you load documents, create an index, and query it. Three steps.

That doesn't mean LlamaIndex replaces LangChain everywhere. For agent workflows with complex tool calling, multi-step reasoning, and human-in-the-loop patterns, LangChain's create_agent (built on the LangGraph runtime) is still a strong choice, especially since LangChain and LangGraph both hit their 1.0 stable milestone in October 2025. Many production stacks now use both: LlamaIndex for data ingestion and retrieval, LangChain for orchestration. This guide focuses on migrating the retrieval layer, and calls out where LangChain's own API has moved since this guide was last checked.

What Changed Since Last Check

If you last touched either framework earlier in 2026, both moved under you:

  • LangChain 1.0 shrank the core langchain package down to agents, messages, tools, and model init. Legacy chains, the old retrievers module, the indexing API, and the hub module were split into a separate langchain-classic package (maintained for security fixes only, through December 2026). The agent constructor is now langchain.agents.create_agent, replacing langgraph.prebuilt.create_react_agent.
  • LlamaIndex finished its own agent consolidation earlier: FunctionCallingAgent, the old ReActAgent implementation, and AgentRunner were removed in v0.13.0 for workflow-based FunctionAgent, ReActAgent, CodeActAgent, and AgentWorkflow, all imported from llama_index.core.agent.workflow.

If your existing migration notes reference create_tool_calling_agent, AgentExecutor, or FunctionCallingAgent.from_tools(), treat them as outdated - both frameworks have replaced those patterns.

Feature Parity Table

FeatureLangChainLlamaIndexNotes
Document loading160+ loader integrations (langchain_community)200+ data connectors via LlamaHubLlamaIndex has more connectors
Text splittingRecursiveCharacterTextSplitterSentenceSplitter, NodeParserLlamaIndex splits at semantic boundaries
Vector indexingInMemoryVectorStore, Chroma, Pinecone, etc.VectorStoreIndex (built-in)LlamaIndex wraps stores into an index abstraction
RetrievalRetriever interface (.as_retriever())QueryEngine + RetrieverLlamaIndex bundles retrieval and synthesis
RAG chainLCEL (now largely in langchain-classic) or create_agent with a retriever toolQueryEngine.query()Single call in LlamaIndex vs multi-step chain
Agentscreate_agent (langchain 1.0) / LangGraphFunctionAgent, AgentWorkflowBoth are workflow/graph based now
Memorycheckpointer (LangGraph)ChatMemoryBufferBoth support conversation history
Streamingagent.stream_events(version="v3")QueryEngine streamingBoth support token streaming
ObservabilityLangSmith (paid service)LlamaTraceBoth have tracing platforms
Structured outputToolStrategy / ProviderStrategyPydantic program + output parsersDifferent mechanisms, similar goal
Hybrid searchVia retrieversBuilt-in fusion retrieverLlamaIndex makes this easier
Multi-modalVia model integrationsNative multi-modal indexLlamaIndex has tighter integration

API Mapping and Code Examples

Document Loading and Indexing

This is where the biggest difference in developer experience still shows up.

LangChain (current, using langchain_core + langchain_text_splitters):

import pypdf
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore

def load_pdf_pages(file_path: str) -> list[Document]:
    reader = pypdf.PdfReader(file_path)
    return [
        Document(
            page_content=page.extract_text() or "",
            metadata={"source": file_path, "page": i},
        )
        for i, page in enumerate(reader.pages)
    ]

docs = load_pdf_pages("report.pdf")

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    add_start_index=True,
)
all_splits = text_splitter.split_documents(docs)

embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_store = InMemoryVectorStore(embeddings)
ids = vector_store.add_documents(documents=all_splits)

retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5},
)

LlamaIndex:

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# Load and index in two lines
documents = SimpleDirectoryReader(input_files=["report.pdf"]).load_data()
index = VectorStoreIndex.from_documents(documents)

LlamaIndex handles text splitting, embedding, and vector storage internally. The defaults use a SentenceSplitter with 1024-token chunks and an in-memory vector store. You can override any of these, but the defaults work for most use cases. LangChain still requires you to wire the loader, splitter, embedding model, and vector store together explicitly - the langchain-community loaders like PyPDFLoader still work if you prefer them over manual pypdf parsing.

Querying

LangChain:

The old LCEL pipe-operator chain (retriever | prompt | llm | parser) still runs if you already have it, but LCEL's chain-building helpers now live in langchain-classic. For new code, the documented pattern is to give a retriever tool to create_agent:

from langchain.agents import create_agent, tool

@tool
def search_report(query: str) -> str:
    """Search the indexed report for relevant passages."""
    docs = retriever.invoke(query)
    return "\n".join(d.page_content for d in docs)

agent = create_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[search_report],
    system_prompt="Answer only using the search_report tool's results.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What were the key findings?"}]}
)
print(result["messages"][-1].content)

LlamaIndex:

query_engine = index.as_query_engine()
response = query_engine.query("What were the key findings?")
print(response)

LlamaIndex's query_engine handles retrieval, context formatting, and LLM synthesis in a single call. LangChain's create_agent route gives you an actual agent loop with tool-calling, error handling middleware, and checkpointing, which is more machinery than plain RAG needs. For standard RAG, the LlamaIndex version is still clearly less code; see our capabilities page on RAG for which models perform best in the retrieval role either framework hands off to.

Streaming Responses

LangChain:

stream = agent.stream_events(
    {"messages": [{"role": "user", "content": "Summarize the main conclusions"}]},
    version="v3",
)
async for event in stream:
    print(event, end="")

LlamaIndex:

query_engine = index.as_query_engine(streaming=True)
response = query_engine.query("Summarize the main conclusions")
response.print_response_stream()

Both frameworks support streaming natively. Note that LangChain's event-streaming node name changed from "agent" to "model" in v1.0 - if you have old code filtering on event["name"] == "agent", it will silently stop matching.

Agent with Tools

This is where LangChain still has an ecosystem advantage, though both APIs have moved.

LangChain:

from langchain.agents import create_agent, tool

@tool
def search_docs(query: str) -> str:
    """Search internal documents."""
    docs = retriever.invoke(query)
    return "\n".join(d.page_content for d in docs)

agent = create_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[search_docs],
    system_prompt="You are a helpful research assistant.",
)
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Find revenue figures"}]}
)

LlamaIndex:

from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import QueryEngineTool

search_tool = QueryEngineTool.from_defaults(
    query_engine=query_engine,
    name="doc_search",
    description="Search internal documents",
)

agent = FunctionAgent(
    tools=[search_tool],
    llm=llm,
    system_prompt="You are a helpful research assistant.",
)
response = await agent.run(user_msg="Find revenue figures")

Both approaches work, and both now take a system_prompt string parameter - that naming converged between the two frameworks over the last few releases. LlamaIndex's QueryEngineTool wraps any query engine into a tool automatically, which is convenient when your agent's primary action is searching indexed data. For agents that need to interact with external APIs and databases, LangChain's tool ecosystem and middleware system (built-in human-in-the-loop, summarization, PII redaction) still have more pre-built pieces.

LlamaIndex AgentWorkflow architecture diagram showing agent handoff and state coordination LlamaIndex's AgentWorkflow coordinates handoffs between multiple FunctionAgent instances, replacing the deprecated AgentRunner/FunctionCallingAgent classes. Source: llamaindex.ai

Mapping LangChain Concepts to LlamaIndex

LangChain ConceptLlamaIndex EquivalentNotes
DocumentLoaderSimpleDirectoryReader / LlamaHub readersLlamaHub has 200+ connectors
RecursiveCharacterTextSplitterSentenceSplitter / NodeParserLlamaIndex splits at sentence boundaries
InMemoryVectorStore / community storesVectorStoreIndexWraps stores (Chroma, Pinecone, pgvector)
Retrieverindex.as_retriever()Returns nodes instead of documents
LCEL chain (now langchain-classic)QueryEngineCombines retrieval + synthesis
create_agentFunctionAgent / AgentWorkflowBoth are the current, non-deprecated path
LangGraph checkpointerChatMemoryBufferBoth track conversation history
@tool decoratorFunctionTool / QueryEngineToolBoth wrap callables as agent tools
LangSmithLlamaTrace / Arize PhoenixObservability platforms

Pricing Impact

Both frameworks are open source under MIT-compatible licenses. There's no licensing cost for either one - the migration itself is free.

The real costs come from the services they connect to:

Cost ComponentLangChainLlamaIndex
Framework licenseFree (MIT)Free (MIT)
LLM API callsSame - see our LLM API pricing comparisonSame
Embedding API callsSame - see our embedding models pricingSame
Vector storeSame - see our vector database comparisonSame
Managed observability/parsing platformLangSmith: free (5,000 traces/mo), Plus $39/seat/mo (10,000 traces/mo included), Enterprise customLlamaCloud: free (10,000 credits/mo), Starter $50/mo (40,000 credits), Pro $500/mo (400,000 credits)
Trace/credit overage$2.50 per 1,000 base traces, $5.00 per 1,000 extended-retention traces~$1.25 per 1,000 credits, $0.00125-$0.05625 per page depending on parse complexity

If you're using LangSmith for tracing and evaluation, switching to LlamaIndex means adopting LlamaTrace or a third-party tool like Arize Phoenix (also open source). For a 10M-token-per-month RAG workload, the LLM and embedding bill is identical between frameworks since both just call the same underlying model APIs - the only cost delta comes from which observability or document-parsing platform you attach.

Known Gotchas

  1. Node vs Document abstraction. LangChain uses Document objects with page_content and metadata. LlamaIndex uses Node objects with text, metadata, and relationships between nodes. If your code references doc.page_content, change it to node.text.

  2. Default chunking is different. LangChain's RecursiveCharacterTextSplitter defaults to 1000-character chunks. LlamaIndex's SentenceSplitter defaults to 1024-token chunks with sentence-aware boundaries. Your retrieval quality may change even with the same data - test recall on your evaluation set.

  3. LangChain's own agent API moved twice in a year. If you're migrating old LangChain code, don't assume AgentExecutor or create_tool_calling_agent are current - they predate the 1.0 release. Update to create_agent first, then decide whether to migrate to LlamaIndex at all, since some of the pain you're trying to escape may already be fixed.

  4. langchain-classic is a one-way ramp down. Anything still importing legacy chains, the old retrievers module, or the hub from langchain-classic only gets security patches through December 2026. Don't build new retrieval code against it.

  5. Query engines aren't chains. LangChain's LCEL let you compose arbitrary pipelines with the pipe operator. LlamaIndex's query engines are more opinionated - they handle retrieval and synthesis together. For custom post-processing, use LlamaIndex's Transformations or NodePostprocessors instead of pipe steps.

  6. Old LlamaIndex agent classes are gone, not deprecated-but-working. FunctionCallingAgent, AgentRunner, and the pre-workflow ReActAgent were removed in v0.13.0. If a tutorial or old internal doc references those, it'll raise an ImportError, not a deprecation warning.

  7. Embedding model defaults matter. LlamaIndex defaults to OpenAI embeddings unless you specify otherwise. If your LangChain setup uses a different embedding model, set it explicitly in LlamaIndex or your vectors won't be compatible with existing data.

  8. Streaming event names changed. LangChain's stream_events node identifier changed from "agent" to "model" in v1.0. Filters written against the old name stop matching silently, they don't error.

FAQ

Can I use LlamaIndex and LangChain together?

Yes, and many teams do. Use LlamaIndex for data indexing and retrieval, LangChain's create_agent for orchestration and tool-calling logic. LlamaIndex provides adapters for wrapping its tools into LangChain-compatible tools.

Will my existing vector store work with LlamaIndex?

Most likely. LlamaIndex supports Chroma, Pinecone, pgvector, Weaviate, Qdrant, FAISS, and 30+ other vector stores. You can point LlamaIndex at your existing store without re-indexing.

Do I need to rewrite my LangChain agent code even if I don't switch to LlamaIndex?

Yes, if it predates LangChain 1.0 (October 2025). create_react_agent and AgentExecutor are legacy now; create_agent is the current path regardless of which retrieval framework you pair it with.

Is LlamaIndex better for production RAG?

For pure retrieval workloads, LlamaIndex's defaults produce strong results with less code. For complex pipelines with multiple tools, middleware, and conditional logic, LangChain's create_agent with LangGraph offers more flexibility.

Do I need to re-embed all my documents?

Only if you change the embedding model. If you keep the same model and vector store, LlamaIndex can query existing vectors without re-indexing.

How does the learning curve compare?

LlamaIndex has a gentler learning curve for RAG applications. LangChain's create_agent and middleware system have a steeper learning curve but offer more composability for non-RAG agent use cases.


Sources:

✓ Last verified July 20, 2026

LinkedIn
Reddit
Hacker News
Telegram
Migrating from LangChain to LlamaIndex
About the author AI Education & Guides Writer

Priya is an AI educator and technical writer whose mission is making artificial intelligence approachable for everyone - not just engineers.