This is one of the most important concepts in LangChain.
What is a Chain?
A chain is a sequence of steps where the output of one step becomes the input to the next step.
In simple terms:
A chain connects multiple components together to accomplish a task.
Real-World Example of chain
1) AI research assistant.
User gives the query, then we search the arXiv, retrieve the necessary documents related to query. Suppose it return 40 pages of documents. Then we give this 40 pages to LLM to summarize. Then we translate into a language that user can understand.
The flow is
User Query -> Search arXiv -> Retrieve Abstract -> LLM Summary -> Translate to Hindi/Spanish -> Return Result
2) RAG:
Suppose you want a chatbot that answers questions from personal PDFs.
Question -> Retriever -> Relevant Documents -> Prompt (Question + Relevant Documents) -> LLM -> Parser (parse the output) -> Answer

LangChain Chains — Complete Tutorial¶
Connect LLM steps like LEGO blocks¶
LangChain version: 1.3.6 | Python: 3.11
Prerequisites: OpenAI API key, basic Python knowledge
What We’ll Cover¶
| # | Concept | What You’ll Build |
|---|---|---|
| 1 | Why Chains? | The problem they solve |
| 2 | The \| Pipe Operator |
Your first chain |
| 3 | Output Parsers | Clean, usable output |
| 4 | Sequential Chains | Output of A → Input of B |
| 5 | Parallel Chains | Multiple chains at once |
| 6 | Branching Chains | Different paths based on input |
| 7 | Real-world Mini Project | Movie review analyzer |
# !pip install langchain==1.3.6 langchain-openai==1.2.2 langchain-community==0.4.2 openai==2.54.0
!pip install langchain-groq==1.1.3
Collecting langchain-groq==1.1.3 Downloading langchain_groq-1.1.3-py3-none-any.whl.metadata (2.9 kB) Collecting groq<1.0.0,>=0.30.0 (from langchain-groq==1.1.3) Downloading groq-0.37.1-py3-none-any.whl.metadata (16 kB) Requirement already satisfied: langchain-core<2.0.0,>=1.4.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-groq==1.1.3) (1.6.2) Requirement already satisfied: anyio<5,>=3.5.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (4.15.1) Requirement already satisfied: distro<2,>=1.7.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (1.9.0) Requirement already satisfied: httpx<1,>=0.23.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (0.28.1) Requirement already satisfied: pydantic<3,>=1.9.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (2.13.5) Requirement already satisfied: sniffio in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (1.3.1) Requirement already satisfied: typing-extensions<5,>=4.10 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (4.16.0) Requirement already satisfied: jsonpatch<2.0.0,>=1.33.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (1.33) Requirement already satisfied: langchain-protocol>=0.0.17 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (0.0.19) Requirement already satisfied: langsmith<1.0.0,>=0.3.45 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (0.12.1) Requirement already satisfied: packaging>=23.2.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (26.3) Requirement already satisfied: pyyaml<7.0.0,>=5.3.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (6.0.3) Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (9.1.4) Requirement already satisfied: uuid-utils<1.0,>=0.12.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (0.17.0) Requirement already satisfied: idna>=2.8 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from anyio<5,>=3.5.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (3.19) Requirement already satisfied: certifi in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from httpx<1,>=0.23.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (2026.7.22) Requirement already satisfied: httpcore==1.* in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from httpx<1,>=0.23.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (1.0.9) Requirement already satisfied: h11>=0.16 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from httpcore==1.*->httpx<1,>=0.23.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (0.16.0) Requirement already satisfied: jsonpointer>=1.9 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from jsonpatch<2.0.0,>=1.33.0->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (3.1.1) Requirement already satisfied: httpx2<3,>=2 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (2.12.0) Requirement already satisfied: orjson>=3.9.14 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (3.12.0) Requirement already satisfied: requests-toolbelt>=1.0.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (1.0.0) Requirement already satisfied: requests>=2.0.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (2.34.2) Requirement already satisfied: websockets>=15.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (16.1.1) Requirement already satisfied: xxhash>=3.0.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (4.0.1) Requirement already satisfied: zstandard>=0.23.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (0.25.0) Requirement already satisfied: annotated-types>=0.6.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from pydantic<3,>=1.9.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (0.8.0) Requirement already satisfied: pydantic-core==2.46.5 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from pydantic<3,>=1.9.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (2.46.5) Requirement already satisfied: typing-inspection>=0.4.2 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from pydantic<3,>=1.9.0->groq<1.0.0,>=0.30.0->langchain-groq==1.1.3) (0.4.4) Requirement already satisfied: httpcore2==2.12.0 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from httpx2<3,>=2->langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (2.12.0) Requirement already satisfied: truststore>=0.10 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from httpx2<3,>=2->langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (0.10.4) Requirement already satisfied: charset_normalizer<4,>=2 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from requests>=2.0.0->langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (3.5.1) Requirement already satisfied: urllib3<3,>=1.26 in c:\users\ash32\desktop\education\play_langchain_langgraph\.venv\lib\site-packages (from requests>=2.0.0->langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.4.0->langchain-groq==1.1.3) (2.7.0) Downloading langchain_groq-1.1.3-py3-none-any.whl (20 kB) Downloading groq-0.37.1-py3-none-any.whl (137 kB) ---------------------------------------- 0.0/137.5 kB ? eta -:--:-- -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 -------------------------------- ------- 112.6/137.5 kB 3.2 MB/s eta 0:00:01 ------------------------------------ - 133.1/137.5 kB 224.6 kB/s eta 0:00:01 -------------------------------------- 137.5/137.5 kB 220.2 kB/s eta 0:00:00 Installing collected packages: groq, langchain-groq Successfully installed groq-0.37.1 langchain-groq-1.1.3
[notice] A new release of pip is available: 24.0 -> 26.2.1 [notice] To update, run: python.exe -m pip install --upgrade pip
# It is good idea to know which libraries were used for this application
import importlib.metadata
# List the distribution package names
packages = ["langchain", "langchain-community", "langchain-openai", "openai",
"langchain_core", "langchain-groq",]
for package in packages:
try:
version = importlib.metadata.version(package)
print(f"{package} version: {version}")
except importlib.metadata.PackageNotFoundError:
print(f"{package} is not installed in this environment.")
# langchain version: 1.3.6
# langchain-community version: 0.4.2
# langchain-openai version: 1.2.2
# openai version: 2.54.0
# langchain_core version: 1.6.2
# langchain-groq version: 1.1.3
langchain version: 1.3.6 langchain-community version: 0.4.2 langchain-openai version: 1.2.2 openai version: 2.54.0 langchain_core version: 1.6.2 langchain-groq version: 1.1.3
# Load the secret variables (like API key) from .env file
import os
from dotenv import load_dotenv
load_dotenv() # read .env file
# TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
True
# Lets check list of available models from groq
import os
from groq import Groq
GROQ_API_KEY = os.environ["GROQ_API_KEY"]
client = Groq(api_key=GROQ_API_KEY)
# Lets check connection by listing all GROQ models
models = client.models.list()
for model in models.data:
print(model.id)
qwen/qwen3.8-27b allam-2-7b canopylabs/orpheus-arabic-saudi groq/compound-mini meta-llama/llama-prompt-guard-2-86m whisper-large-v3-turbo groq/compound meta-llama/llama-prompt-guard-2-22m openai/gpt-oss-120b whisper-large-v3 openai/gpt-oss-20b canopylabs/orpheus-v1-english openai/gpt-oss-safeguard-20b qwen/qwen3.6-27b
import os
from langchain_openai import ChatOpenAI
from langchain_groq import ChatGroq
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# llm = ChatOpenAI(api_key=OPENAI_API_KEY, model="gpt-3.5-turbo", temperature=0.7)
GROQ_API_KEY = os.environ["GROQ_API_KEY"]
llm = ChatGroq(api_key=GROQ_API_KEY, model="openai/gpt-oss-120b", temperature=0.7)
# Quick check
response = llm.invoke("Say hello in one word.")
print(" Connected! Test response:", response.content)
Connected! Test response: Hello
1️. Why Do We Need Chains?¶
The Problem¶
Imagine you want to:
- Take a topic from the user
- Generate a blog post about it
- Translate that blog post to French
- Trim the blog to under 300 words
Without chains, you’d write this:
# BAD : Manual, messy, hard to reuse
response1 = llm.invoke(f"Write a blog post about {topic}")
blog_post = response1.content
response2 = llm.invoke(f"Translate this to French: {blog_post}")
translated = response2.content
response3 = llm.invoke(f"Shorten this to under 300 words {translated}")
final_text = response3.content
With chains, the same thing becomes:
# Clean, readable, reusable
result = (write_chain | translate_chain | summarize_chain).invoke({"topic": topic})
What is a Chain?¶
A Chain is a sequence of steps connected together, where the output of one step becomes the input of the next.
Input
↓
Step 1: Prompt Template (formats your input)
↓
Step 2: LLM (generates a response)
↓
Step 3: Output Parser (cleans the response)
↓
Output
LangChain uses the | pipe operator to connect steps — just like Unix terminal pipes!
# Let's see the PROBLEM first — manual, no chains
# from langchain_openai import ChatOpenAI
# llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
topic = "black holes"
# Step 1: manual call
response1 = llm.invoke(f"Give one interesting fact about {topic}.")
fact = response1.content
# Step 2: manual call — using output of step 1
response2 = llm.invoke(f"Make this fact sound more exciting: {fact}")
exciting_fact = response2.content
print("Original fact:")
print(fact)
print("---------------------")
print("\nExciting version:")
print(exciting_fact)
print("\n Works, but imagine doing this for 10 steps...")
Original fact: One of the most mind‑bending facts about black holes is that **time literally slows down near them**. According to Einstein’s theory of general relativity, the stronger the gravitational field, the slower time passes relative to a distant observer. Near the event horizon of a black hole, this effect becomes extreme: for someone watching from far away, an object falling toward the horizon appears to freeze and its clock ticks ever more slowly, while the falling object itself experiences only a few moments before crossing the horizon. In other words, a minute for you could be years (or even centuries) for someone far away—making black holes natural “time dilators.” --------------------- Exciting version: Imagine standing on the edge of the universe’s ultimate time‑machine. A black hole isn’t just a cosmic vacuum cleaner—it’s a **real‑life warp‑drive** that bends the very flow of time itself. Einstein’s general relativity tells us that the deeper you plunge into a gravity well, the slower your clock ticks. But near a black hole’s event horizon, this isn’t a gentle slowdown—it’s a **mind‑blowing, almost cinematic freeze‑frame**. To a distant observer, a daring astronaut spiraling toward the abyss appears to crawl in slow motion, their every heartbeat stretched into an eternity. Their watch seems to crawl, their voice drags, and the final plunge looks like a frozen statue hanging on the brink of oblivion. Inside the ship, however, the crew feels only a few heartbeats pass before they cross the point of no return. For them, the universe outside races ahead—what feels like a single minute to them could be **years, decades, or even centuries** for anyone watching from a safe distance. In other words, black holes are nature’s most extreme **time dilators**, turning seconds into ages and letting the cosmos play out in fast‑forward while you’re stuck in slow‑motion. It’s the ultimate sci‑fi plot twist that actually lives in the real universe. Works, but imagine doing this for 10 steps...
2️) The | Pipe Operator — Your First Chain¶
LCEL: LangChain Expression Language¶
LangChain introduced LCEL (LangChain Expression Language) — a clean way to build chains using the | pipe.
prompt | llm | parser
Reads left to right:
promptformats the input into a proper messagellmsends it to the model and gets a responseparserconverts the response into a clean string
The 3 Core Building Blocks¶
| Block | Import | What it does |
|---|---|---|
ChatPromptTemplate |
langchain_core.prompts |
Formats your input with variables |
ChatOpenAI |
langchain_openai |
Calls the LLM |
StrOutputParser |
langchain_core.output_parsers |
Extracts just the text string |
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# ── Block 1: Prompt Template
# {topic} is a variable — gets filled in when you invoke the chain
prompt = ChatPromptTemplate.from_template(
"Give one interesting fact about {topic} in exactly 2 sentences."
)
# See what the prompt looks like BEFORE sending to LLM
rendered = prompt.format_messages(topic="black holes")
# rendered = prompt.format_messages(topic="RAG system used by AI")
# rendered = prompt.format_messages(topic="Quantum Computing")
print("Rendered prompt:")
for msg in rendered:
print(f" [{msg.type}]: {msg.content}")
Rendered prompt: [human]: Give one interesting fact about black holes in exactly 2 sentences.
# ── Block 2: The Parser
parser = StrOutputParser()
# Without parser: response is an AIMessage object: You see lot of details
raw_response = llm.invoke(rendered)
print("Without parser:")
print(f" Type: {type(raw_response)}")
print(f" Value: {raw_response}")
print()
# With parser: response is a plain string
clean_response = parser.invoke(raw_response)
print("With parser:")
print(f" Type: {type(clean_response)}")
print(f" Value: {clean_response}")
Without parser:
Type: <class 'langchain_core.messages.ai.AIMessage'>
Value: content='A black hole’s event horizon isn’t a solid surface but a one‑way boundary where space‑time is so warped that even light cannot escape, effectively freezing the appearance of anything that crosses it to an outside observer. Remarkably, the information about the infalling matter is thought to be encoded on this horizon itself, a principle known as the holographic principle.' additional_kwargs={'reasoning_content': 'User asks for one interesting fact about black holes in exactly 2 sentences. Provide concise answer. Must be 2 sentences.'} response_metadata={'token_usage': {'completion_tokens': 108, 'prompt_tokens': 84, 'total_tokens': 192, 'completion_time': 0.224647627, 'completion_tokens_details': {'reasoning_tokens': 26}, 'prompt_time': 0.003773034, 'prompt_tokens_details': None, 'queue_time': 0.211015786, 'total_time': 0.228420661}, 'model_name': 'openai/gpt-oss-120b', 'system_fingerprint': 'fp_5a93aea882', 'service_tier': 'on_demand', 'finish_reason': 'stop', 'logprobs': None, 'model_provider': 'groq'} id='lc_run--01a072d5-076f-7e02-aaaa-d34e3da7b54f-0' tool_calls=[] invalid_tool_calls=[] usage_metadata={'input_tokens': 84, 'output_tokens': 108, 'total_tokens': 192, 'output_token_details': {'reasoning': 26}}
With parser:
Type: <class 'langchain_core.messages.base.TextAccessor'>
Value: A black hole’s event horizon isn’t a solid surface but a one‑way boundary where space‑time is so warped that even light cannot escape, effectively freezing the appearance of anything that crosses it to an outside observer. Remarkably, the information about the infalling matter is thought to be encoded on this horizon itself, a principle known as the holographic principle.
# ── Block 3: Connect with | to make a Chain
# This is the magic moment — three objects connected with |
chain = prompt | llm | StrOutputParser()
print("Chain type:", type(chain))
print()
# Invoke the chain — just pass the variable values
result = chain.invoke({"topic": "black holes"})
print("Result type:", type(result)) # plain string!
print("Result:", result)
Chain type: <class 'langchain_core.runnables.base.RunnableSequence'> Result type: <class 'langchain_core.messages.base.TextAccessor'> Result: Black holes can “evaporate” over time through a process called Hawking radiation, where quantum effects near the event horizon cause them to emit particles and lose mass. For a stellar‑mass black hole, this evaporation would take far longer than the current age of the universe, but for tiny, hypothetical micro‑black holes it could happen in a fraction of a second.
# ──Advantage of above: Reusability — same chain, different inputs
topics = ["quantum computers", "Monte carlo Method", "topology"]
print(" Interesting Facts:\n")
for topic in topics:
fact = chain.invoke({"topic": topic})
print(f" {topic.title()}:")
print(f" {fact}")
print()
Interesting Facts: Quantum Computers: Quantum computers can solve certain problems—like factoring large numbers or simulating molecular interactions—in polynomial time, which would take classical supercomputers an impractically long time. This speedup arises from quantum bits (qubits) being able to exist in superpositions of 0 and 1 simultaneously, enabling massive parallelism through entanglement. Monte Carlo Method: The Monte Carlo method was named after the gambling hub of Monaco because it relies on random sampling, much like rolling dice. During the Manhattan Project, it was first used to solve complex neutron‑diffusion problems that were otherwise intractable analytically. Topology: The Möbius strip is a surface with only one side and one edge, so if you travel along it you return to your starting point having traversed both “sides” without ever crossing an edge. This counter‑intuitive property exemplifies how topology studies properties that remain unchanged under continuous deformations, ignoring notions like distance or angle.
# ── Multiple variables in a prompt
# Prompts can have as many {variables} as you need
explain_chain = (
ChatPromptTemplate.from_template(
"Explain {concept} to someone who is {audience}. "
"Keep it under {sentences} sentences."
)
| llm
| StrOutputParser()
)
# Invoke with all three variables
result = explain_chain.invoke({
"concept": "machine learning",
"audience": "a 12-year-old",
"sentences": 3
})
print(result)
Machine learning is like teaching a computer to learn from lots of examples—just as you figure out what a cat looks like by seeing many pictures of cats. The computer spots patterns in those examples and then can guess or decide things about new data it hasn’t seen before.
# YOUR TURN
# Change the concept, audience, and sentences below and run!
result = explain_chain.invoke({
"concept": "the stock market", # ← change this
"audience": "a curious grandparent", # ← change this
"sentences": 3 # ← change this
})
print(result)
Think of a company as a big pie; when it “sells slices” of that pie—called shares—to the public, anyone can buy a slice and become a part‑owner. The stock market is simply the bustling marketplace where people trade those slices, with prices rising when many want a piece (the company looks promising) and falling when interest wanes.
3️) Output Parsers — Getting Useful Data Back¶
The LLM always returns raw text. Output parsers convert that text into something your code can actually use.
| Parser | Output type | Use when you need |
|---|---|---|
StrOutputParser |
str |
Plain text — most common |
JsonOutputParser |
dict |
Structured data |
CommaSeparatedListOutputParser |
list |
A list of items |
PydanticOutputParser |
Pydantic model | Typed, validated data |
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.output_parsers import CommaSeparatedListOutputParser
# ── Parser 1: StrOutputParser (you've seen this)
str_chain = (
ChatPromptTemplate.from_template("Name the capital of {country}.")
| llm
| StrOutputParser()
)
result = str_chain.invoke({"country": "Japan"})
# result = str_chain.invoke({"country": "India"})
# result = str_chain.invoke({"country": "Germany"})
print(f"StrOutputParser → type: {type(result).__name__}")
print(f"value: {result}")
StrOutputParser → type: TextAccessor value: The capital of Japan is **Tokyo**.
# ── Parser 2: CommaSeparatedListOutputParser ─
list_parser = CommaSeparatedListOutputParser()
list_chain = (
ChatPromptTemplate.from_template(
"List 5 {category}. Return ONLY a comma-separated list, nothing else."
)
| llm
| list_parser
)
result = list_chain.invoke({"category": "programming languages"})
# result = list_chain.invoke({"category": "Agentic AI tools"})
# result = list_chain.invoke({"category": "Nobel Prize winning Scientists"})
print(f"Type: {type(result).__name__}")
print(f"Value: {result}")
print(f"First item: {result[0]}")
print(f"Last item: {result[-1]}")
print(f"Count: {len(result)}")
Type: list Value: ['Python', 'JavaScript', 'Java', 'C++', 'Ruby'] First item: Python Last item: Ruby Count: 5
# ── Parser 3: JsonOutputParser
# Tell the LLM exactly what JSON shape you want back
json_chain = (
ChatPromptTemplate.from_template(
"""
Return information about {city} as JSON with these exact 4 keys:
name, country, population_millions, famous_for (list of 3 things).
Return ONLY valid JSON, no extra text.
"""
)
| llm
| JsonOutputParser()
)
result = json_chain.invoke({"city": "Tokyo"})
# result = json_chain.invoke({"city": "Berlin"})
# result = json_chain.invoke({"city": "New York"})
print(f"Type: {type(result).__name__}")
print(f"City: {result['name']}")
print(f"Country: {result['country']}")
print(f"Population: {result['population_millions']}M")
print(f"Famous for: {result['famous_for']}")
Type: dict City: Tokyo Country: Japan Population: 14.0M Famous for: ['Shibuya Crossing', 'Sushi', 'Tokyo Tower']
# ── Parser 4: Pydantic — typed and validated output
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from typing import List
# Define the exact shape of data you want
class MovieReview(BaseModel):
title: str = Field(description="Movie title")
year: int = Field(description="Release year")
rating: float = Field(description="Rating out of 10")
pros: List[str] = Field(description="List of 2 things done well")
cons: List[str] = Field(description="List of 2 weaknesses")
verdict: str = Field(description="One sentence summary")
parser = JsonOutputParser(pydantic_object=MovieReview)
review_chain = (
ChatPromptTemplate.from_messages([
("system", "You are a film critic. Always respond in valid JSON."),
("human", "Review the movie '{title}'. {instructions}"),
])
| llm
| parser
)
result = review_chain.invoke({
"title": "Inception",
"instructions": parser.get_format_instructions()
})
# result = review_chain.invoke({
# "title": "Avatar",
# "instructions": parser.get_format_instructions()
# })
print(f"{result['title']} ({result['year']}) — {result['rating']}/10")
print(f"Pros: {result['pros']}")
print(f"Cons: {result['cons']}")
print(f"Verdict: {result['verdict']}")
Inception (2010) — 8.8/10 Pros: ['Innovative multi-layered storytelling', 'Stunning visual effects and practical set pieces'] Cons: ['Complex plot can be confusing on first watch', 'Emotional depth sometimes sacrificed for spectacle'] Verdict: A mind-bending thriller that rewards repeat viewings despite its occasional narrative opacity.
4️) Sequential Chains — Chaining LLM Calls Together¶
This is where chains get powerful. The output of one LLM call becomes the input of the next.
Topic
↓
Chain A: "Write a blog post about {topic}"
↓ (blog post text)
Chain B: "Summarize this into 3 bullet points: {blog_post}"
↓ (bullet points)
Chain C: "Translate these bullet points to Spanish: {bullets}"
↓
Final Output
LangChain uses RunnablePassthrough to route values between steps.
from langchain_core.runnables import RunnablePassthrough
# ── Simple 2-step sequential chain
# Step 1: Write a poem
write_poem = (
ChatPromptTemplate.from_template("Write a short 4-line poem about {topic}.")
| llm
| StrOutputParser()
)
# Step 2: Analyse the poem (receives poem as input)
analyse_poem = (
ChatPromptTemplate.from_template(
"Analyse this poem in one sentence — identify its mood and style:\n\n{poem}"
)
| llm
| StrOutputParser()
)
# Connect them: output of write_poem becomes "poem" in analyse_poem
sequential = (
{"poem": write_poem} # write_poem runs first, result stored as "poem"
| RunnablePassthrough.assign(analysis=analyse_poem) # analyse_poem runs second
)
result = sequential.invoke({"topic": "autumn leaves"})
# result = sequential.invoke({"topic": "dandelion"})
# result = sequential.invoke({"topic": "volcanoe"})
print("POEM:")
print(result["poem"])
print("\nANALYSIS:")
print(result["analysis"])
POEM: Crimson whispers drift on mellow air, Golds cascade where branches once stood bare. Crisp sighs of wind turn silence to sighs, Autumn’s quilt unfolds beneath amber skies. ANALYSIS: In a single, lushly lyrical sentence, the poem’s mood is wistful and reverent, evoking a nostalgic melancholy for the fleeting beauty of fall, while its style is richly imagistic and musical, employing alliteration, synesthetic color imagery, and a gentle, flowing rhythm that mirrors the season’s soft, descending transition.
# ── 3-step pipeline: generate → improve → translate
# Step 1: Generate a joke
generate_joke = (
ChatPromptTemplate.from_template("Write a short, clean joke about {topic}.")
| llm | StrOutputParser()
)
# Step 2: Improve the joke (uses output of step 1)
improve_joke = (
ChatPromptTemplate.from_template(
"Make this joke funnier by adding a punchline twist:\n{joke}"
)
| llm | StrOutputParser()
)
# Step 3: Rate the improved joke (uses output of step 2)
rate_joke = (
ChatPromptTemplate.from_template(
"""Rate this joke on a scale of 1-10 for funniness and explain why in one sentence:
{improved_joke}"""
)
| llm | StrOutputParser()
)
# Wire them together
pipeline = (
{"joke": generate_joke, "topic": RunnablePassthrough()}
| RunnablePassthrough.assign(improved_joke=lambda x: improve_joke.invoke({"joke": x["joke"]}))
| RunnablePassthrough.assign(rating=lambda x: rate_joke.invoke({"improved_joke": x["improved_joke"]}))
)
result = pipeline.invoke({"topic": "programmers"})
# result = pipeline.invoke({"topic": "mathematician"})
# result = pipeline.invoke({"topic": "cats and dogs"})
print("ORIGINAL JOKE:")
print(result["joke"])
print("\n IMPROVED JOKE:")
print(result["improved_joke"])
print("\n RATING:")
print(result["rating"])
ORIGINAL JOKE: Why do programmers prefer dark mode? Because light attracts bugs! IMPROVED JOKE: **Why do programmers prefer dark mode?** Because light attracts bugs… and in the dark they finally get a chance to *debug* their social life. RATING: **Rating: 7/10** – It cleverly blends a classic programmer pun about “bugs” with a witty twist on “debugging” one’s social life, making it both relatable and unexpectedly self‑deprecating.
# ── Real use case: Study notes pipeline ─
# Input: a topic → Output: explanation + quiz question + memory tip
# Chain 1: Explain the topic
explain = (
ChatPromptTemplate.from_template(
"Explain {topic} clearly in 3 bullet points for a student."
)
| llm | StrOutputParser()
)
# Chain 2: Create a quiz question from the explanation
make_quiz = (
ChatPromptTemplate.from_template(
"Based on this explanation:\n{explanation}\n\n"
"Write ONE multiple choice question (A/B/C/D) with the answer marked."
)
| llm | StrOutputParser()
)
# Chain 3: Create a memory trick from the explanation
memory_trick = (
ChatPromptTemplate.from_template(
"Based on this explanation:\n{explanation}\n\n"
"Give ONE creative memory trick or acronym to remember this."
)
| llm | StrOutputParser()
)
# Full pipeline
study_pipeline = (
{"explanation": explain, "topic": RunnablePassthrough()}
| RunnablePassthrough.assign(quiz=lambda x: make_quiz.invoke({"explanation": x["explanation"]}))
| RunnablePassthrough.assign(tip=lambda x: memory_trick.invoke({"explanation": x["explanation"]}))
)
result = study_pipeline.invoke({"topic": "photosynthesis"})
print("📚 EXPLANATION:")
print(result["explanation"])
print("\n❓ QUIZ QUESTION:")
print(result["quiz"])
print("\n🧠 MEMORY TRICK:")
print(result["tip"])
📚 EXPLANATION: Here are three bullet points explaining photosynthesis clearly for a student: * **What is photosynthesis?**: Photosynthesis is the process by which plants, algae, and some bacteria use energy from sunlight to convert carbon dioxide and water into glucose (a type of sugar) and oxygen. This process is essential for life on Earth as it provides energy and organic compounds for plants to grow and develop. * **How does it work?**: During photosynthesis, plants absorb carbon dioxide from the air through their leaves and water from the soil through their roots. They then use energy from sunlight, which is absorbed by a green pigment called chlorophyll, to convert the carbon dioxide and water into glucose and oxygen. The glucose is used by the plant as food, while the oxygen is released into the air as a byproduct. * **Why is it important?**: Photosynthesis is crucial for life on Earth because it provides oxygen for animals to breathe and serves as the basis of the food chain. Without photosynthesis, plants would not be able to produce the energy they need to grow, and animals would not have the oxygen they need to survive. Additionally, photosynthesis helps to regulate the Earth's climate by removing carbon dioxide from the atmosphere and producing oxygen, which helps to maintain a healthy balance of gases in the air. ❓ QUIZ QUESTION: What is the byproduct released into the air during the process of photosynthesis? A) Glucose B) Carbon dioxide C) Water D) Oxygen Answer: D) Oxygen 🧠 MEMORY TRICK: To remember the key points of photosynthesis, you can use the acronym "PGO" which stands for: P - Plants (and other organisms) use energy from sunlight G - Glucose (and oxygen) are produced from carbon dioxide and water O - Oxygen is released into the air, supporting life on Earth Alternatively, you can also use a sentence like "Pete's Gorilla Owns" to associate with the first letter of each word, making it more memorable.
5️. Parallel Chains — Run Multiple Chains Simultaneously¶
Sometimes you don’t need the output of one chain to feed into another.
You just want to run several chains on the same input at the same time.
RunnableParallel does exactly that — all chains run concurrently, saving time.
┌─── Chain A: pros ────┐
Input ────────┼─── Chain B: cons ────┼──→ Combined Result
└─── Chain C: summary ─┘
from langchain_core.runnables import RunnableParallel
# Three chains that all take {topic} as input
pros_chain = (
ChatPromptTemplate.from_template("List 3 advantages of {topic}. Be concise.")
| llm | StrOutputParser()
)
cons_chain = (
ChatPromptTemplate.from_template("List 3 disadvantages of {topic}. Be concise.")
| llm | StrOutputParser()
)
verdict_chain = (
ChatPromptTemplate.from_template(
"Give a one-sentence balanced verdict on {topic}: is it worth it overall?"
)
| llm | StrOutputParser()
)
# Run all three at the same time
parallel = RunnableParallel(
pros=pros_chain,
cons=cons_chain,
verdict=verdict_chain
)
result = parallel.invoke({"topic": "remote work"})
print("PROS:")
print(result["pros"])
print("\nCONS:")
print(result["cons"])
print("\n VERDICT:")
print(result["verdict"])
PROS: 1. Increased flexibility 2. Reduced commuting time 3. Improved work-life balance CONS: 1. Social isolation 2. Difficulty in separating work and personal life 3. Limited face-to-face interaction and communication VERDICT: Remote work is worth it overall for many individuals and organizations, as it offers numerous benefits such as increased flexibility, productivity, and work-life balance, but it also presents challenges like social isolation, communication barriers, and blurred boundaries, requiring intentional effort to strike a balance and maximize its potential.
# ── Parallel saves time — let's measure it
import time
topic = "electric vehicles"
# Sequential (one by one)
start = time.time()
p1 = pros_chain.invoke({"topic": topic})
c1 = cons_chain.invoke({"topic": topic})
v1 = verdict_chain.invoke({"topic": topic})
sequential_time = time.time() - start
# Parallel (all at once)
start = time.time()
result = parallel.invoke({"topic": topic})
parallel_time = time.time() - start
print(f"Sequential: {sequential_time:.2f}s")
print(f"Parallel: {parallel_time:.2f}s")
print(f"Speedup: {sequential_time/parallel_time:.1f}x faster")
Sequential: 0.79s Parallel: 0.41s Speedup: 1.9x faster
# ── Parallel + Sequential combined ────────────────────────────────
# First run parallel chains, then feed combined results into a final chain
summary_chain = (
ChatPromptTemplate.from_template(
"""You have analysed {topic}. Here is the analysis:
PROS: {pros}
CONS: {cons}
VERDICT: {verdict}
Write a 3-sentence recommendation for whether someone should adopt {topic}."""
)
| llm | StrOutputParser()
)
# Step 1: parallel analysis → Step 2: final recommendation
full_analysis = parallel | RunnablePassthrough.assign(
recommendation=lambda x: summary_chain.invoke({
"topic": topic,
"pros": x["pros"],
"cons": x["cons"],
"verdict": x["verdict"]
})
)
result = full_analysis.invoke({"topic": "social media for teenagers"})
print("📋 FINAL RECOMMENDATION:")
print(result["recommendation"])
📋 FINAL RECOMMENDATION: It appears there's been a mix-up, as the provided analysis pertains to social media for teenagers, not electric vehicles. However, to provide a recommendation on electric vehicles, I would suggest that individuals consider adopting electric vehicles due to their environmental benefits, lower operating costs, and improving technology. Overall, electric vehicles can be a worthwhile investment for those looking for a sustainable and cost-effective transportation option, but it's essential to weigh the pros and cons, such as limited charging infrastructure and higher upfront costs, before making a decision.
6️) Branching Chains — Different Paths Based on Input¶
Sometimes you want the chain to take different routes depending on the input.
This is called a Router Chain.
Input: "What is 2+2?" → Route: MATH → Math explanation chain
Input: "Why is sky blue?" → Route: SCIENCE → Science explanation chain
Input: "Who wrote Hamlet?" → Route: HISTORY → History explanation chain
We use RunnableLambda to write custom routing logic in plain Python.
from langchain_core.runnables import RunnableLambda
# ── Define specialist chains ──────────────────────────────────────
math_chain = (
ChatPromptTemplate.from_template(
"You are a math tutor. Answer this clearly with steps: {question}"
)
| llm | StrOutputParser()
)
science_chain = (
ChatPromptTemplate.from_template(
"You are a science teacher. Explain this with a real-world example: {question}"
)
| llm | StrOutputParser()
)
history_chain = (
ChatPromptTemplate.from_template(
"You are a history professor. Answer this with historical context: {question}"
)
| llm | StrOutputParser()
)
general_chain = (
ChatPromptTemplate.from_template(
"You are a helpful assistant. Answer this question: {question}"
)
| llm | StrOutputParser()
)
# ── Step 1: Classify the question ────────────────────────────────
classify_chain = (
ChatPromptTemplate.from_template(
"""Classify this question into exactly ONE category: math, science, history, or general.
Question: {question}
Reply with ONLY the category word, nothing else."""
)
| llm | StrOutputParser()
)
# ── Step 2: Route to the right chain ─────────────────────────────
def route(inputs: dict) -> str:
category = classify_chain.invoke({"question": inputs["question"]}).strip().lower()
print(f" 📂 Classified as: {category}")
if "math" in category:
return math_chain.invoke(inputs)
elif "science" in category:
return science_chain.invoke(inputs)
elif "history" in category:
return history_chain.invoke(inputs)
else:
return general_chain.invoke(inputs)
# ── Step 3: Wrap in a chain ────────────────────────────────────────
router_chain = RunnableLambda(route)
# Test it with different questions
questions = [
"What is the quadratic formula?",
"Why do leaves change colour in autumn?",
"Who was the first President of the United States?",
"What are some tips for better sleep?",
]
for q in questions:
print(f"\n❓ Question: {q}")
answer = router_chain.invoke({"question": q})
print(f"💬 Answer: {answer[:200]}...")
❓ Question: What is the quadratic formula? 📂 Classified as: math 💬 Answer: The quadratic formula is a mathematical formula used to find the solutions (or roots) of a quadratic equation. A quadratic equation is in the form of: ax² + bx + c = 0 where a, b, and c are constant... ❓ Question: Why do leaves change colour in autumn? 📂 Classified as: science 💬 Answer: What a wonderful topic to explore. The changing colors of leaves in autumn is a fascinating phenomenon that's not only aesthetically pleasing but also rooted in science. You see, during the spring an... ❓ Question: Who was the first President of the United States? 📂 Classified as: history 💬 Answer: The first President of the United States was George Washington, a man of great character and leadership who played a pivotal role in shaping the country's early history. Born on February 22, 1732, in ... ❓ Question: What are some tips for better sleep? 📂 Classified as: general 💬 Answer: Improving sleep quality can have a significant impact on overall health and well-being. Here are some valuable tips for better sleep: 1. **Establish a bedtime routine**: Develop a calming pre-sleep r...
7️) Mini Project — Movie Review Analyzer¶
Let’s put everything together into a real, useful pipeline.
What it does:¶
- Takes a movie title as input
- Parallel: fetches plot summary + gets critical reception (two chains at once)
- Sequential: uses both outputs to generate a structured review
- Structured output: returns a typed Python object you can use in your app
This is exactly the kind of pipeline you’d build in a real product.
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from typing import List
# ── Output schema ─────────────────────────────────────────────────
class FullReview(BaseModel):
title: str
genre: List[str]
one_line_summary: str
what_works: List[str] = Field(description="3 things done well")
what_doesnt: List[str] = Field(description="2 weaknesses")
similar_movies: List[str] = Field(description="3 movies to watch if you liked this")
score: float = Field(description="Score out of 10")
recommended_for: str = Field(description="Who would enjoy this most")
parser = JsonOutputParser(pydantic_object=FullReview)
# ── Parallel chains ────────────────────────────────────────────────
plot_chain = (
ChatPromptTemplate.from_template(
"Write a 2-sentence spoiler-free plot summary of the movie '{title}'."
)
| llm | StrOutputParser()
)
reception_chain = (
ChatPromptTemplate.from_template(
"Describe in 2 sentences how '{title}' was received by critics and audiences."
)
| llm | StrOutputParser()
)
parallel_research = RunnableParallel(
plot=plot_chain,
reception=reception_chain,
title=RunnablePassthrough() # pass title through unchanged
)
# ── Final review chain ─────────────────────────────────────────────
final_review_chain = (
ChatPromptTemplate.from_messages([
("system", "You are a film critic. Always respond in valid JSON only."),
("human", """Based on this research, write a full review of '{title}'.
Plot: {plot}
Critical Reception: {reception}
{format_instructions}"""),
])
| llm
| parser
)
# ── Full pipeline ──────────────────────────────────────────────────
def analyze_movie(title: str) -> dict:
# Step 1: parallel research
research = parallel_research.invoke({"title": title})
# Step 2: final review using research
review = final_review_chain.invoke({
"title": title,
"plot": research["plot"],
"reception": research["reception"],
"format_instructions": parser.get_format_instructions()
})
return review
# Run it!
review = analyze_movie("The Dark Knight")
print(f"🎬 {review['title']}")
print(f"🎭 Genre: {', '.join(review['genre'])}")
print(f"⭐ Score: {review['score']}/10")
print(f"\n📖 Summary: {review['one_line_summary']}")
print(f"\n✅ What Works:")
for w in review['what_works']:
print(f" • {w}")
print(f"\n❌ What Doesn't:")
for w in review['what_doesnt']:
print(f" • {w}")
print(f"\n🎯 Recommended For: {review['recommended_for']}")
print(f"\n🎞️ Watch Next: {', '.join(review['similar_movies'])}")
🎬 The Dark Knight 🎭 Genre: Action, Thriller, Superhero ⭐ Score: 9.5/10 📖 Summary: Batman faces off against the chaotic and unpredictable Joker in a battle to protect Gotham City ✅ What Works: • Heath Ledger's posthumous Oscar-winning performance as the Joker • The film's thought-provoking themes of justice, morality, and heroism • The well-directed action sequences and intense plot ❌ What Doesn't: • The movie's dark and intense tone may not be suitable for all audiences • Some viewers may find the Joker's chaotic actions and violence disturbing 🎯 Recommended For: Fans of superhero movies and those who enjoy intense, thought-provoking action films 🎞️ Watch Next: The Avengers, The Dark Knight Rises, Spider-Man: Into the Spider-Verse
# Try with any movie!
review = analyze_movie("Parasite") # ← change this
print(f"🎬 {review['title']} | ⭐ {review['score']}/10")
print(f"📖 {review['one_line_summary']}")
🎬 Parasite | ⭐ 9/10 📖 A poor family infiltrates the lives of a wealthy family with unexpected consequences, blurring the boundaries of class, loyalty, and morality
📖 Summary — Everything We Covered¶
# ── 1. BASIC CHAIN ──────────────────────────────────────────────
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
chain = ChatPromptTemplate.from_template("...{var}...") | llm | StrOutputParser()
result = chain.invoke({"var": "value"}) # result is a plain string
# ── 2. OUTPUT PARSERS ───────────────────────────────────────────
StrOutputParser() # → str
CommaSeparatedListOutputParser() # → list
JsonOutputParser(pydantic_object=MyModel) # → dict (typed)
# ── 3. SEQUENTIAL CHAIN ─────────────────────────────────────────
from langchain_core.runnables import RunnablePassthrough
pipeline = (
{"step1_output": chain_a, "original": RunnablePassthrough()}
| RunnablePassthrough.assign(step2_output=chain_b)
)
# ── 4. PARALLEL CHAIN ───────────────────────────────────────────
from langchain_core.runnables import RunnableParallel
parallel = RunnableParallel(result_a=chain_a, result_b=chain_b, result_c=chain_c)
result = parallel.invoke({"input": "..."})
# result = {"result_a": "...", "result_b": "...", "result_c": "..."}
# ── 5. BRANCHING CHAIN ──────────────────────────────────────────
from langchain_core.runnables import RunnableLambda
def route(inputs):
if condition:
return chain_a.invoke(inputs)
else:
return chain_b.invoke(inputs)
router = RunnableLambda(route)
Coming Up Next¶
| Topic | What You’ll Learn |
|---|---|
| 🔧 Tools | Give chains the ability to search the web, run code, call APIs |
| 🧠 Memory | Make chains remember previous conversations |
| 🤖 Agents | Let the LLM decide WHICH chain/tool to use |
