Chains with LCEL & Runnables
A chain joins steps so the output of one becomes the input of the next. LCEL (LangChain Expression Language) writes it with the pipe |, exactly like Linux: prompt | llm | parser. In this lab you build sequential blog writers, a review analyser that runs three chains at once, a router that sends coding questions to a coding chain, and a fallback for when a model fails.
In this session
- What a chain is and why the | pipe
- prompt | llm | StrOutputParser()
- Sequential chains: output of one → input of next
- Passthrough, Parallel, Lambda and Branch
- Fallbacks and retry for production
- Stream and batch a whole chain for free
01The big idea: a factory assembly line
Think of a dosa counter. One person pours batter, the next adds masala, the last folds and serves. Each person does one job and passes the plate on. A chain is that line: prompt fills the template, the LLM writes, the parser cleans up. You can add, remove or swap one station without touching the others.
This is the real graph LangChain builds for that chain (cell chain.get_graph().print_ascii() from the notebook, run here):
02The five runnables you will use every day
| Runnable | What it does | Notebook example |
|---|---|---|
a | b | run a, give its output to b | prompt | llm | StrOutputParser() |
{"title": chain1} | chain2 | a dict becomes RunnableParallel: build the next input | title chain → blog intro chain |
RunnablePassthrough.assign(x=chain) | keep the input and add a new key | keep topic, add title |
RunnableParallel(a=…, b=…) | run branches at the same time on the same input | summary + sentiment + keywords of a hotel review |
RunnableLambda(fn) or a plain function | any Python step inside a chain | count words, upper-case |
RunnableBranch((cond, chain), default) | if / else routing | coding question → coding chain |
.with_fallbacks([b]), .with_retry() | production safety | bad model name → backup model |
03Hands-on: run it here
Every runnable from the notebook, with real outputs. The stand-in LLM answers by keyword, so even batch and parallel runs are repeatable.
from standins import RuleModel
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel, RunnableLambda, RunnableBranch
llm = RuleModel(rules={
"blog title": "Smart Farms: How AI Is Changing Indian Agriculture",
"blog intro": "From Punjab's wheat fields to Kerala's spice gardens, AI is quietly helping farmers...",
"summarise": "Clean room and polite staff, but unreliable WiFi.",
"sentiment": "neutral",
"keywords": "clean room, polite staff, WiFi",
"'coding' or 'general'": "coding",
"python code": "text[::-1] # slicing reverses a string",
"2 lines": "Sunlight scatters off air molecules. Blue light scatters most, so the sky looks blue.",
"tweet": "Monsoon is here! Chai, pakoras and the smell of first rain. #Monsoon",
}, default="OK")
chain = PromptTemplate.from_template("Write a tweet about {topic}") | llm | StrOutputParser()
print("1 chain :", chain.invoke({"topic": "monsoon"}))
print("2 + lambda :", (chain | RunnableLambda(lambda t: {"words": len(t.split())})).invoke({"topic": "monsoon"}))
title_chain = PromptTemplate.from_template("Give one catchy blog title about {topic}. Only the title.") | llm | StrOutputParser()
blog_chain = PromptTemplate.from_template("Write a 100-word blog intro for the title: {title}") | llm | StrOutputParser()
print("3 sequential:", ({"title": title_chain} | blog_chain).invoke({"topic": "AI in Indian agriculture"}))
print("4 assign :", RunnablePassthrough.assign(title=title_chain).invoke({"topic": "AI in Indian agriculture"}))
p = lambda t: PromptTemplate.from_template(t) | llm | StrOutputParser()
analyzer = RunnableParallel(summary=p("Summarise in 1 line: {text}"), sentiment=p("Sentiment (positive/negative/neutral): {text}"),
keywords=p("Give 3 keywords, comma separated: {text}"))
print("5 parallel :", analyzer.invoke({"text": "The hotel room was clean and staff were very polite, but the WiFi kept disconnecting."}))
class_llm = RuleModel(rules={"reverse": "coding"}, default="general")
classify = PromptTemplate.from_template("Is this question about 'coding' or 'general'? One word.\nQuestion: {question}") | class_llm | StrOutputParser()
router = RunnablePassthrough.assign(kind=classify) | RunnableBranch(
(lambda x: "coding" in x["kind"].lower(), p("Answer with a short Python code example: {question}")),
p("Answer simply in 2 lines: {question}"))
for q in ["How to reverse a string in Python?", "Why is the sky blue?"]:
print("6 router :", q, "->", router.invoke({"question": q}))
class Broken(RuleModel):
def _generate(self, *a, **k): raise ConnectionError("model-that-does-not-exist")
safe = Broken().with_fallbacks([RuleModel(default="fallback worked")])
print("7 fallback :", safe.invoke("Say: fallback worked").content)RuleModel or ScriptedModel from standins.py), so every output below is from a real run of this code. In Colab you swap one line: llm = ChatGroq(model="openai/gpt-oss-20b").04Hands-on: Ajeetkumar’s Colab notebook
groq_key with your free key from console.groq.com, switch on Notebook access, then run the cells from the top.05Benefits and the one limit
06Practice tasks
- Chain: topic → 5 interview questions → answers for each (sequential).
- Parallel chain: from a product name create a tagline, an Instagram caption and a tweet.
- Router: send Hindi questions to a Hindi chain, English to an English chain.
Try these in the same Colab notebook after you finish the lab. Change one thing at a time and look at the output again.
07Summary
- LCEL joins runnables with
|:prompt | llm | parser. - Every chain gets
invoke,stream,batchfor free. - Dict /
RunnableParallelrun branches together;Passthrough.assignkeeps input and adds keys. RunnableLambdafor Python steps,RunnableBranchfor routing.with_fallbacksandwith_retrymake chains production-safe.
08Quick quiz
1. What does | do in LCEL?
2. Which keeps the input AND adds a new key?
3. Summary, sentiment and keywords of the same text, fastest way?
4. Primary model name is wrong. What saves you?
5. When is a chain NOT enough?