Module 8 gave you the mechanics: Runnables, the | operator, and a handful of composition tools like RunnableParallel and RunnableLambda. This module has a different, more practical job: actually building the kinds of real, useful pipelines you’ll reach for constantly, and clearing up a word that trips up a lot of people learning LangChain — “chain” itself.
What “chain” actually means — and what it doesn’t
Here’s something worth stating directly, because it’s an extremely common misconception: a “chain” is not a special LangChain class, and it’s not some magical AI object. A chain is simply the name people use, in conversation and in documentation, for any pipeline of Runnables connected together with |.
chain = prompt | model | parser
That’s it. That’s a chain. It’s not fundamentally different from this:
Function A → Function B → Function C
A chain is a pipeline — one step’s output becomes the next step’s input, exactly as you learned properly in Module 8. There’s no hidden intelligence living inside the word “chain” itself. The actual intelligence, if any, lives inside whichever specific steps happen to be a model call. A chain that’s just RunnableLambda(clean_text) | RunnableLambda(count_words) is every bit as much a “chain” as one involving three separate model calls — it just doesn’t happen to use any AI at all.
A quick, honest note on LangChain’s own history here
You’ll see the word “Chain” used differently in older tutorials — specifically, as an actual Python class you’d import and instantiate, like LLMChain or SequentialChain. That older approach predates the Runnable and | composition you learned in Module 8, and it’s now considered legacy, moved into the langchain-classic package mentioned back in Module 2 — kept available only so old projects don’t break. Every “chain” you build in this course is the modern kind: a pipeline of Runnables, built with |, not an instance of some special Chain class. If you see LLMChain in an older tutorial, mentally translate it to “an old-style version of what we’re about to build with |.”
Example 1: a translation chain
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
translation_prompt = ChatPromptTemplate.from_messages([
("system", "Translate the given text into {target_language}. Reply with only the translation."),
("human", "{text}"),
])
model = init_chat_model("openai:gpt-4o-mini")
parser = StrOutputParser()
translate = translation_prompt | model | parser
result = translate.invoke({"text": "Good morning, how are you?", "target_language": "French"})
print(result)
Notice this is nothing new mechanically — it’s the exact same prompt | model | parser shape from Module 8, applied to a genuinely useful, real task. That’s honestly the point of this whole module: you already have every tool you need. What’s left is practice recognizing which real problems fit this shape.
Example 2: a summarization chain
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
summarize_prompt = ChatPromptTemplate.from_template(
"Summarize the following text in exactly {sentence_count} sentences:\n\n{text}"
)
model = init_chat_model("google_genai:gemini-2.0-flash")
parser = StrOutputParser()
summarize = summarize_prompt | model | parser
article = """
Retrieval-augmented generation, or RAG, combines a language model with an external
knowledge base. Instead of relying purely on what the model memorized during training,
RAG retrieves relevant documents at the moment of answering, and feeds that content
into the model alongside the user's question. This grounds the model's answer in real,
current, verifiable information, and substantially reduces — though does not eliminate —
the model's tendency to hallucinate confident-sounding but incorrect answers.
"""
result = summarize.invoke({"text": article, "sentence_count": 1})
print(result)
Same shape again, this time on Gemini, and this time with a genuinely long block of input text. Chains don’t care how long their input is — the prompt template simply fills {text} with whatever you hand it.
Example 3: a classification chain with a genuinely clean output
Recall the classification prompt from Module 7, Example 5 — it formatted a prompt, but never actually ran it through a model. Let’s finish that job properly, and make the output genuinely reliable using structured output from Module 4.
from typing import Literal
from pydantic import BaseModel
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
class SentimentResult(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
classification_prompt = ChatPromptTemplate.from_messages([
("system", "Classify the sentiment of the given text."),
("human", "{text}"),
])
model = init_chat_model("openai:gpt-4o-mini").with_structured_output(SentimentResult)
classify = classification_prompt | model
result = classify.invoke({"text": "This laptop completely changed how I work — I love it."})
print(result)
Notice Literal["positive", "negative", "neutral"] — this is a genuinely useful upgrade over a plain str field. It tells Pydantic, and therefore the model, that this field can only be one of these three exact values, not just “probably something like these words.” This chain now returns a real, guaranteed-valid SentimentResult object every time, not a sentence you’d have to hope was phrased consistently.
Example 4: an extraction chain
Let’s finish Module 7’s extraction example the same way — connecting it to a real model call with structured output, rather than leaving it as just a formatted prompt.
from pydantic import BaseModel
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
class ExtractedInfo(BaseModel):
name: str
amount_mentioned: float | None
extraction_prompt = ChatPromptTemplate.from_messages([
("system", "Extract the person's name and the amount of money mentioned, if any."),
("human", "{text}"),
])
model = init_chat_model("openai:gpt-4o-mini").with_structured_output(ExtractedInfo)
extract = extraction_prompt | model
result = extract.invoke({"text": "Rahul mentioned he'd spent about $45 on the new keyboard."})
print(result)
Notice something worth pointing out explicitly: when a model is wrapped with .with_structured_output(...) before it goes into the chain, the entire chain’s final output is that structured object — you don’t need a separate parser step at all here, since the model itself is already returning genuinely structured data, not raw text needing to be parsed.
Example 5: a multi-stage chain — outline, then full content
Real content generation often benefits from breaking a task into genuine stages, rather than asking for a finished result in one shot. Let’s build a chain that first drafts an outline, then writes full content from that outline.
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
model = init_chat_model("openai:gpt-4o-mini")
parser = StrOutputParser()
outline_prompt = ChatPromptTemplate.from_template(
"Create a 3-point outline for a short article about {topic}."
)
outline_chain = outline_prompt | model | parser
write_prompt = ChatPromptTemplate.from_template(
"Using this outline:\n{outline}\n\nWrite a short, complete article about {topic}."
)
full_chain = (
RunnablePassthrough.assign(outline=outline_chain)
| write_prompt
| model
| parser
)
result = full_chain.invoke({"topic": "why sleep matters for memory"})
print(result)
This is worth slowing down on, because it introduces something genuinely new: RunnablePassthrough.assign(outline=outline_chain). Recall from Module 8 that plain RunnablePassthrough() hands its input through unchanged. .assign(...) does something related but more useful: it keeps the original input dictionary intact, while adding a new key to it — here, outline, computed by actually running outline_chain. So after that first step, your data going into write_prompt isn’t just the outline — it’s {"topic": "...", "outline": "..."}, meaning write_prompt’s two placeholders, {topic} and {outline}, are both genuinely available, even though only outline was newly computed. This exact pattern — carrying forward the original input while adding a computed result alongside it — is one you’ll use constantly once you start building RAG pipelines later in this course.
Example 6: a research-then-summarize chain
One more multi-stage pattern, worth seeing in its own right: gathering several separate pieces of information first, then synthesizing them together in a second pass.
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
model = init_chat_model("openai:gpt-4o-mini")
parser = StrOutputParser()
research_step = RunnableParallel(
history=ChatPromptTemplate.from_template("In two sentences, what is the history of {topic}?") | model | parser,
modern_use=ChatPromptTemplate.from_template("In two sentences, how is {topic} used today?") | model | parser,
)
synthesis_prompt = ChatPromptTemplate.from_template(
"Combine this research into one flowing paragraph about {topic}:\n\n"
"History: {history}\nModern use: {modern_use}"
)
full_chain = research_step | synthesis_prompt | model | parser
result = full_chain.invoke({"topic": "the printing press"})
print(result)
Notice the shape here: research_step, built with RunnableParallel from Module 8, gathers two separate pieces of information concurrently, and its combined output — a dictionary with history and modern_use — flows directly into synthesis_prompt’s own placeholders. This is a genuinely real pattern for research-style applications: gather relevant information in parallel first, then synthesize it into one coherent final answer in a second, separate pass.
Common mistakes worth avoiding
Reaching for LLMChain or another langchain-classic class because an old tutorial used it. Recall the honest history note earlier in this module — those older classes still technically work, but they predate the Runnable composition this entire course is built on, and mixing old-style Chain objects with modern |-built chains creates genuinely confusing, inconsistent code. If you see LLMChain anywhere, translate it into the equivalent prompt | model | parser pattern instead.
Forgetting that .with_structured_output(...) changes what a chain’s final output actually is. Recall Examples 3 and 4 — once a model is wrapped this way, the chain no longer produces plain text or needs a parser; it produces your real, typed object directly. Adding a StrOutputParser after a structured-output model, out of habit, will either error or silently produce something you didn’t intend.
Building a multi-stage chain without checking what keys are actually available at each step. Recall Example 5’s RunnablePassthrough.assign(...) — it’s easy to lose track of exactly which dictionary keys exist by the time a later prompt template tries to use them. If a placeholder like {outline} doesn’t have a matching key in the data flowing through the chain at that point, you’ll get a clear error — but only if you’re watching for it, rather than assuming your earlier .assign() call worked exactly as intended.
What you should take away from this module
- A “chain” is not a special object or class — it’s simply the everyday name for a pipeline of Runnables connected with
|. The intelligence lives in the individual steps, not in the word “chain” itself. - Older tutorials using
LLMChainorSequentialChainare using LangChain’s legacy, pre-Runnable approach, now preserved only in thelangchain-classicpackage. - Structured output, from Module 4, combines directly with chains — when a model is wrapped with
.with_structured_output(...)before entering a chain, no separate parsing step is needed afterward. RunnablePassthrough.assign(...)carries the original input forward while adding new, computed keys alongside it — the pattern behind nearly every real multi-stage chain you’ll build.- Multi-stage chains — outline-then-write, research-then-synthesize — are just chains whose steps happen to be other chains, composed together exactly like any simpler pipeline.
Where this goes next
The next module goes deep on something you’ve touched in almost every example so far without slowing down on it: Streaming. You’ll understand precisely why token-by-token output feels so much better to a real user than waiting in silence, and how to stream not just a single model call, but an entire multi-stage chain like the ones you just built.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed