10 LangChain Chains You Must Know (LLMChain to RouterChain)
Master all major LangChain chain types — LLMChain, SequentialChain, RouterChain, and more — with real Python code and a guide on when to use each.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
When I first started building with LangChain, I treated every task as a single prompt. Ask one question, get one answer. It worked for prototypes. It fell apart the moment I needed to do anything non-trivial — summarize a document, route a user query to the right handler, run three steps where each step's output feeds the next.
Chains are how LangChain solves that problem. They're not complicated conceptually — a chain is just a sequence of operations. But the variety of chain types, each optimized for different patterns, is where a lot of developers get confused.
This guide covers the 10 most important chain types with working code, a comparison table for when to use each, and honest notes on which ones are legacy and which ones you should actually be building with in 2026.
If you're brand new to LangChain, start with the LangChain tutorial 2025 first. If you're ready to move beyond chains into agents, the AI agents explained guide is the natural next step.
The Mental Model Behind Chains
Before diving into code, it helps to understand why chains exist. An LLM call is stateless — you send a prompt, you get a response. For anything more complex than a single Q&A, you need to:
- Format inputs consistently
- Pass outputs from one step as inputs to the next
- Handle branching logic (different paths for different inputs)
- Manage context and memory across steps
Chains give you a composable way to do all of that. In modern LangChain, most chains are built with LangChain Expression Language (LCEL) using the | pipe operator. Some older chain types (like LLMChain and SequentialChain) still exist for backward compatibility, but the LCEL patterns are what you should use for new code.
LLMChain: The Foundation
LLMChain is the oldest and simplest chain type. It connects a prompt template to a language model and optionally an output parser.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Modern LCEL approach (recommended)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant specializing in {domain}."),
("human", "{question}")
])
chain = prompt | llm | StrOutputParser()
result = chain.invoke({
"domain": "Python programming",
"question": "What is a decorator?"
})
print(result)
# Legacy LLMChain approach (still works, not recommended for new code)
from langchain.chains import LLMChain
legacy_chain = LLMChain(llm=llm, prompt=prompt)
result = legacy_chain.invoke({"domain": "Python", "question": "What is a list?"})
print(result["text"])
The LCEL version is shorter, clearer, and supports async and streaming natively. Use it. The LLMChain class is effectively legacy code at this point.
SequentialChain: Chaining Steps Together
SequentialChain lets you connect multiple chains so the output of one becomes the input of the next. The classic use case is multi-step document processing — summarize, then translate, then format.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# Step 1: Summarize
summarize_prompt = ChatPromptTemplate.from_template(
"Summarize the following text in 2-3 sentences:\n\n{text}"
)
# Step 2: Extract keywords from the summary
keywords_prompt = ChatPromptTemplate.from_template(
"Extract 5 important keywords from this summary. Return as comma-separated list:\n\n{summary}"
)
# Step 3: Generate a title
title_prompt = ChatPromptTemplate.from_template(
"Given these keywords: {keywords}\n\nWrite a compelling blog post title."
)
# Chain them with LCEL
step1 = summarize_prompt | llm | StrOutputParser()
step2 = keywords_prompt | llm | StrOutputParser()
step3 = title_prompt | llm | StrOutputParser()
# Full sequential pipeline
from langchain_core.runnables import RunnablePassthrough
full_pipeline = (
{"summary": step1, "text": RunnablePassthrough()}
| RunnablePassthrough.assign(keywords=lambda x: step2.invoke({"summary": x["summary"]}))
| RunnablePassthrough.assign(title=lambda x: step3.invoke({"keywords": x["keywords"]}))
)
article_text = """
The Python programming language celebrated its 35th anniversary in 2026.
Created by Guido van Rossum, it has become the most popular programming
language globally, widely used in data science, web development, automation,
and artificial intelligence applications.
"""
result = full_pipeline.invoke({"text": article_text})
print(f"Summary: {result['summary']}")
print(f"Keywords: {result['keywords']}")
print(f"Title: {result['title']}")
When SequentialChain Actually Matters
The pure sequential pattern shines when each step meaningfully transforms the data before passing it forward. If you're just reformatting — adding a prefix or suffix — don't bother with a chain. When the LLM is doing real work at each step (extraction, translation, classification), chaining is worth it.
RouterChain: Directing Inputs to the Right Handler
RouterChain is one of the more interesting patterns in LangChain. Instead of processing every input the same way, the router looks at the input and sends it to a specialized sub-chain.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Define specialized chains for each domain
code_chain = (
ChatPromptTemplate.from_template(
"You are a senior software engineer. Answer this code question:\n{question}"
) | llm | StrOutputParser()
)
math_chain = (
ChatPromptTemplate.from_template(
"You are a mathematics professor. Solve this step by step:\n{question}"
) | llm | StrOutputParser()
)
general_chain = (
ChatPromptTemplate.from_template(
"You are a knowledgeable assistant. Answer this question:\n{question}"
) | llm | StrOutputParser()
)
# Router prompt
router_prompt = ChatPromptTemplate.from_template("""
Given the question below, classify it as one of: "code", "math", or "general".
Respond with ONLY the category word.
Question: {question}
""")
router_chain = router_prompt | llm | StrOutputParser()
def route_and_answer(question: str) -> str:
# Step 1: classify
category = router_chain.invoke({"question": question}).strip().lower()
# Step 2: route to the right chain
if category == "code":
return code_chain.invoke({"question": question})
elif category == "math":
return math_chain.invoke({"question": question})
else:
return general_chain.invoke({"question": question})
# Test
print(route_and_answer("How do I reverse a list in Python?"))
print(route_and_answer("What is the derivative of x^2?"))
print(route_and_answer("Who invented the telephone?"))
This pattern is particularly useful for customer support systems, where you might have specialized chains for billing, technical support, and product information. The router decides which expert to consult.
TransformChain: Custom Transformations Without LLM Calls
Sometimes you need a step in your pipeline that transforms data without calling an LLM. TransformChain (or a RunnableLambda in LCEL) handles this.
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini")
# Custom transform function — no LLM call
def preprocess_text(inputs: dict) -> dict:
text = inputs["raw_text"]
# Clean up the text
cleaned = text.strip().replace("\n\n\n", "\n\n")
word_count = len(cleaned.split())
return {
"text": cleaned,
"word_count": word_count,
"is_long": word_count > 500
}
transform = RunnableLambda(preprocess_text)
# Use it in a pipeline
analysis_prompt = ChatPromptTemplate.from_template(
"Analyze this {word_count}-word text and provide key insights:\n\n{text}"
)
pipeline = transform | analysis_prompt | llm | StrOutputParser()
result = pipeline.invoke({
"raw_text": " Python is a versatile programming language... "
})
print(result)
RunnableLambda is more flexible than the older TransformChain class. You can wrap any Python function and drop it into an LCEL pipeline.
RetrievalQAChain: Answering Questions from Documents
This is one of the most-used chain types in production. It retrieves relevant documents from a vector store and uses them to answer questions. This is the backbone of RAG systems.
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
from langchain_text_splitters import RecursiveCharacterTextSplitter
# Setup (assumes you have documents loaded)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Create vector store from documents
documents = [
"Python was created by Guido van Rossum and released in 1991.",
"LangChain is a framework for building LLM applications.",
"Vector databases store embeddings for similarity search.",
]
vectorstore = Chroma.from_texts(
texts=documents,
embedding=embeddings,
persist_directory="./chroma_db"
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# RAG prompt
rag_prompt = ChatPromptTemplate.from_messages([
("system", """Answer the question using only the provided context.
If the answer isn't in the context, say "I don't have that information."
Context: {context}"""),
("human", "{question}")
])
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# Build the RAG chain
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("When was Python created?")
print(answer)
For a complete walkthrough of the RAG pattern including document loading and chunking, see our RAG system tutorial. The vector database guide covers the storage options you can use with this pattern.
ConversationChain: Multi-Turn Dialogue
ConversationChain adds memory to an LLM so it can maintain context across multiple turns. This is the foundation of every chatbot.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
# Prompt with message history placeholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Keep responses concise."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm | StrOutputParser()
# Store for session histories
store = {}
def get_session_history(session_id: str) -> ChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# Wrap chain with message history
conversation = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# Multi-turn conversation
config = {"configurable": {"session_id": "user_123"}}
r1 = conversation.invoke({"input": "My name is Alex."}, config=config)
print(f"Bot: {r1}")
r2 = conversation.invoke({"input": "What's my name?"}, config=config)
print(f"Bot: {r2}") # Should remember "Alex"
For a full deep-dive on memory types, see LangChain multi-turn conversations and memory. The patterns there build on exactly this foundation.
MapReduceChain: Processing Long Documents
When a document is too long to fit in a single context window, you need to process it in chunks and then combine the results. MapReduceChain handles this in two phases.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_text_splitters import RecursiveCharacterTextSplitter
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=100)
# Map step: summarize each chunk
map_prompt = ChatPromptTemplate.from_template(
"Summarize the following text chunk in 2-3 sentences:\n\n{chunk}"
)
map_chain = map_prompt | llm | StrOutputParser()
# Reduce step: combine summaries into a final summary
reduce_prompt = ChatPromptTemplate.from_template(
"""You have these partial summaries of a long document:
{summaries}
Combine them into a comprehensive final summary of 4-5 sentences."""
)
reduce_chain = reduce_prompt | llm | StrOutputParser()
def map_reduce_summarize(long_text: str) -> str:
# Split into chunks
chunks = splitter.split_text(long_text)
# Map: summarize each chunk
chunk_summaries = [
map_chain.invoke({"chunk": chunk})
for chunk in chunks
]
# Reduce: combine all summaries
combined = "\n\n".join(
f"[Part {i+1}]: {s}"
for i, s in enumerate(chunk_summaries)
)
final_summary = reduce_chain.invoke({"summaries": combined})
return final_summary
# Test with a long document
long_document = """
[Imagine a 5000-word research paper here...]
""" * 10
summary = map_reduce_summarize(long_document)
print(summary)
This pattern scales to arbitrarily long documents since you can process as many chunks as needed in the map phase.
RefineChain: Iterative Document Processing
Where MapReduce processes chunks in parallel and combines at the end, RefineChain processes chunks sequentially, iteratively improving the answer.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
initial_prompt = ChatPromptTemplate.from_template(
"Summarize this text:\n\n{text}"
)
refine_prompt = ChatPromptTemplate.from_template(
"""Here's an existing summary: {existing_summary}
Now I have more information to add:
{new_context}
Refine the summary to incorporate this new information. Keep it concise."""
)
initial_chain = initial_prompt | llm | StrOutputParser()
refine_chain = refine_prompt | llm | StrOutputParser()
def refine_summarize(chunks: list[str]) -> str:
# Start with the first chunk
summary = initial_chain.invoke({"text": chunks[0]})
# Iteratively refine with each subsequent chunk
for chunk in chunks[1:]:
summary = refine_chain.invoke({
"existing_summary": summary,
"new_context": chunk
})
return summary
# The refine approach is better for tasks where context matters across chunks
# It's slower (sequential) but produces more coherent results
The trade-off: MapReduce is faster (parallel processing) but can miss cross-chunk connections. RefineChain is slower (sequential) but maintains coherence better for tasks like legal document analysis where context spans chunks.
APIChain: Calling External APIs
APIChain connects your chain to external APIs. It's especially useful when you want the LLM to decide which API endpoint to call and with what parameters.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import json
import requests
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Prompt that generates API call parameters
api_params_prompt = ChatPromptTemplate.from_template(
"""Given this user request, generate the API parameters as JSON.
Available endpoint: GET /weather?city={{city}}&units={{units}}
User request: {request}
Return only valid JSON like: {{"city": "London", "units": "metric"}}"""
)
params_chain = api_params_prompt | llm | StrOutputParser()
def weather_api_call(params_json: str) -> str:
try:
params = json.loads(params_json)
# In a real app, call the actual weather API here
# response = requests.get("https://api.weather.com/weather", params=params)
# For demo, return mock data
return f"Weather in {params.get('city', 'Unknown')}: 22°C, Sunny"
except:
return "Failed to fetch weather data"
format_prompt = ChatPromptTemplate.from_template(
"Format this weather data as a friendly message for the user:\n{weather_data}\n\nUser asked: {original_request}"
)
format_chain = format_prompt | llm | StrOutputParser()
def weather_chain(user_request: str) -> str:
params_json = params_chain.invoke({"request": user_request})
weather_data = weather_api_call(params_json)
response = format_chain.invoke({
"weather_data": weather_data,
"original_request": user_request
})
return response
print(weather_chain("What's the weather like in Paris right now?"))
Chain Types Comparison: When to Use What
| Chain Type | Use Case | LLM Calls | Supports LCEL | Recommended |
|---|---|---|---|---|
| LLMChain | Simple prompt → response | 1 | Via LCEL pipe | LCEL equivalent |
| SequentialChain | Multi-step linear processing | N (one per step) | Yes | Yes (LCEL) |
| RouterChain | Dynamic routing by input type | 1 (classify) + N | Yes | Yes |
| TransformChain | Non-LLM data transformation | 0 | Via RunnableLambda | Yes |
| RetrievalQAChain | Document Q&A with vector search | 1 | Yes | Yes |
| ConversationChain | Multi-turn chatbots | 1 per turn | Yes | Yes |
| MapReduceChain | Long doc processing (parallel) | N chunks + 1 | Manual | Context-dependent |
| RefineChain | Long doc processing (sequential) | N sequential | Manual | Context-dependent |
| APIChain | LLM-driven API calls | 1+ | Yes | Yes |
| StuffChain | Short doc Q&A (fits in context) | 1 | Yes | Yes for small docs |
My honest take: for greenfield projects in 2026, use LCEL for everything. The table above helps you understand the pattern you're implementing, but the implementation should be LCEL pipes, not the legacy class-based chains. They all work, but the class-based approach gets in the way more than it helps once you've done it a few times.
Combining Chains: A Real-World Pattern
Let's build something that combines several patterns — a research assistant that routes questions, retrieves context, and formats answers appropriately:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# Classifier
classify_prompt = ChatPromptTemplate.from_template(
"Classify this question as 'technical', 'factual', or 'creative': {question}\nRespond with one word."
)
classifier = classify_prompt | llm | StrOutputParser()
# Specialized chains
technical_chain = (
ChatPromptTemplate.from_template(
"Answer this technical question with code examples where appropriate:\n{question}"
) | llm | StrOutputParser()
)
factual_chain = (
ChatPromptTemplate.from_template(
"Answer this factual question with specific details and sources where possible:\n{question}"
) | llm | StrOutputParser()
)
creative_chain = (
ChatPromptTemplate.from_template(
"Answer this creatively, with vivid examples and analogies:\n{question}"
) | llm | StrOutputParser()
)
def route(inputs: dict) -> str:
question = inputs["question"]
category = classifier.invoke({"question": question}).strip().lower()
if "technical" in category:
return technical_chain.invoke({"question": question})
elif "factual" in category:
return factual_chain.invoke({"question": question})
else:
return creative_chain.invoke({"question": question})
research_assistant = RunnableLambda(route)
print(research_assistant.invoke({"question": "How does async/await work in Python?"}))
print(research_assistant.invoke({"question": "When was the Eiffel Tower built?"}))
print(research_assistant.invoke({"question": "What would a world without time zones feel like?"}))
This pattern is directly applicable to customer support, educational platforms, and any application where different query types need different handling styles.
For building full agents that can use tools and make multi-step decisions — not just route — see our guide on building AI agents with LangChain. The AI research agent guide shows a production-grade example of these patterns combined.
Conclusion
The ten chain types covered here represent the building blocks of almost every LangChain application in production. Start with the simple ones — LCEL pipes replacing LLMChain — and add complexity only when your use case genuinely requires it. RouterChain when you need dynamic routing, MapReduce when documents are too long for context, ConversationChain when state needs to persist.
The most important principle: use LCEL for all new code. The legacy class-based chain API still works, but it's heading for deprecation and the LCEL approach is more readable, testable, and flexible. Build with the grain of the framework, not against it.
Next up: learn how to add retrieval to your chains with the RAG system tutorial, or explore the full range of LangChain agent types when you're ready to move beyond chains into autonomous agent systems.
Frequently Asked Questions
What is the difference between SequentialChain and a simple LCEL pipe?
SequentialChain is the older, class-based API for running chains in sequence. Modern LangChain uses LCEL (the pipe operator) which is more composable, easier to debug, and supports async and streaming natively. Use LCEL for new projects.
When should I use RouterChain vs just an if/else in Python?
Use RouterChain when the routing decision itself should be made by the LLM based on semantic content, not just a fixed condition. If your routing logic is deterministic and rule-based, a plain if/else is simpler and faster.
Is LLMChain deprecated in LangChain?
LLMChain still works but is considered legacy in the modern LangChain ecosystem. The LCEL equivalent (prompt | llm | parser) is the recommended approach for new code. LLMChain may be removed in future major versions.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
7 AutoGen Termination Conditions (Max Rounds, Human Approval)
Master all 7 AutoGen termination conditions including is_termination_msg, max_turns, and human approval patterns to stop agent loops reliably and safely.
AutoGen Tutorial: Microsoft's Multi-Agent Framework (2026)
Learn Microsoft AutoGen from scratch in 2026 — install, first agent conversation, GroupChat, and a full comparison of AutoGen 0.2 vs 0.4 features.
AutoGen vs LangChain: Which for Multi-Agent Systems in 2026?
AutoGen vs LangChain for multi-agent systems in 2026 — feature comparison, same use case in both frameworks, and an honest verdict on when each wins.
How to Use AutoGen with Tools (Web Scraper, Calculator, File)
Learn how to equip AutoGen agents with custom tools like web scrapers, calculators, and file handlers using register_for_llm and register_for_execution.