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.

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_agentandAgentExecutorare legacy now regardless of which framework you pick - What improves: less boilerplate for pure RAG, built-in
SentenceSplitterandVectorStoreIndexabstractions, nativeFunctionAgentfor 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
langchainpackage 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 separatelangchain-classicpackage (maintained for security fixes only, through December 2026). The agent constructor is nowlangchain.agents.create_agent, replacinglanggraph.prebuilt.create_react_agent. - LlamaIndex finished its own agent consolidation earlier:
FunctionCallingAgent, the oldReActAgentimplementation, andAgentRunnerwere removed in v0.13.0 for workflow-basedFunctionAgent,ReActAgent,CodeActAgent, andAgentWorkflow, all imported fromllama_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
| Feature | LangChain | LlamaIndex | Notes |
|---|---|---|---|
| Document loading | 160+ loader integrations (langchain_community) | 200+ data connectors via LlamaHub | LlamaIndex has more connectors |
| Text splitting | RecursiveCharacterTextSplitter | SentenceSplitter, NodeParser | LlamaIndex splits at semantic boundaries |
| Vector indexing | InMemoryVectorStore, Chroma, Pinecone, etc. | VectorStoreIndex (built-in) | LlamaIndex wraps stores into an index abstraction |
| Retrieval | Retriever interface (.as_retriever()) | QueryEngine + Retriever | LlamaIndex bundles retrieval and synthesis |
| RAG chain | LCEL (now largely in langchain-classic) or create_agent with a retriever tool | QueryEngine.query() | Single call in LlamaIndex vs multi-step chain |
| Agents | create_agent (langchain 1.0) / LangGraph | FunctionAgent, AgentWorkflow | Both are workflow/graph based now |
| Memory | checkpointer (LangGraph) | ChatMemoryBuffer | Both support conversation history |
| Streaming | agent.stream_events(version="v3") | QueryEngine streaming | Both support token streaming |
| Observability | LangSmith (paid service) | LlamaTrace | Both have tracing platforms |
| Structured output | ToolStrategy / ProviderStrategy | Pydantic program + output parsers | Different mechanisms, similar goal |
| Hybrid search | Via retrievers | Built-in fusion retriever | LlamaIndex makes this easier |
| Multi-modal | Via model integrations | Native multi-modal index | LlamaIndex 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's AgentWorkflow coordinates handoffs between multiple FunctionAgent instances, replacing the deprecated AgentRunner/FunctionCallingAgent classes.
Source: llamaindex.ai
Mapping LangChain Concepts to LlamaIndex
| LangChain Concept | LlamaIndex Equivalent | Notes |
|---|---|---|
DocumentLoader | SimpleDirectoryReader / LlamaHub readers | LlamaHub has 200+ connectors |
RecursiveCharacterTextSplitter | SentenceSplitter / NodeParser | LlamaIndex splits at sentence boundaries |
InMemoryVectorStore / community stores | VectorStoreIndex | Wraps stores (Chroma, Pinecone, pgvector) |
Retriever | index.as_retriever() | Returns nodes instead of documents |
LCEL chain (now langchain-classic) | QueryEngine | Combines retrieval + synthesis |
create_agent | FunctionAgent / AgentWorkflow | Both are the current, non-deprecated path |
LangGraph checkpointer | ChatMemoryBuffer | Both track conversation history |
@tool decorator | FunctionTool / QueryEngineTool | Both wrap callables as agent tools |
| LangSmith | LlamaTrace / Arize Phoenix | Observability 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 Component | LangChain | LlamaIndex |
|---|---|---|
| Framework license | Free (MIT) | Free (MIT) |
| LLM API calls | Same - see our LLM API pricing comparison | Same |
| Embedding API calls | Same - see our embedding models pricing | Same |
| Vector store | Same - see our vector database comparison | Same |
| Managed observability/parsing platform | LangSmith: free (5,000 traces/mo), Plus $39/seat/mo (10,000 traces/mo included), Enterprise custom | LlamaCloud: 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
Node vs Document abstraction. LangChain uses
Documentobjects withpage_contentandmetadata. LlamaIndex usesNodeobjects withtext,metadata, and relationships between nodes. If your code referencesdoc.page_content, change it tonode.text.Default chunking is different. LangChain's
RecursiveCharacterTextSplitterdefaults to 1000-character chunks. LlamaIndex'sSentenceSplitterdefaults to 1024-token chunks with sentence-aware boundaries. Your retrieval quality may change even with the same data - test recall on your evaluation set.LangChain's own agent API moved twice in a year. If you're migrating old LangChain code, don't assume
AgentExecutororcreate_tool_calling_agentare current - they predate the 1.0 release. Update tocreate_agentfirst, then decide whether to migrate to LlamaIndex at all, since some of the pain you're trying to escape may already be fixed.langchain-classicis a one-way ramp down. Anything still importing legacy chains, the old retrievers module, or the hub fromlangchain-classiconly gets security patches through December 2026. Don't build new retrieval code against it.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
TransformationsorNodePostprocessorsinstead of pipe steps.Old LlamaIndex agent classes are gone, not deprecated-but-working.
FunctionCallingAgent,AgentRunner, and the pre-workflowReActAgentwere removed in v0.13.0. If a tutorial or old internal doc references those, it'll raise anImportError, not a deprecation warning.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.
Streaming event names changed. LangChain's
stream_eventsnode 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:
- LangChain v1 Migration Guide
- LangChain and LangGraph Agent Frameworks Reach v1.0 Milestones
- LangChain Retrieval Documentation
- LangChain Knowledge Base Tutorial
- langchain-classic on PyPI
- LlamaIndex Documentation
- LlamaIndex Agents Documentation
- FunctionAgent / AgentWorkflow Basic Introduction
- LlamaIndex Deprecated Terms
- Introducing AgentWorkflow
- LangChain GitHub Releases
- LlamaIndex GitHub Releases
- LangSmith Pricing
- LlamaCloud Pricing
✓ Last verified July 20, 2026
