Prompt Chaining: A Practical Guide (LangChain and Alternatives)
Prompt chaining with LangChain (LCEL): compose with the pipe operator, pass one call's output into the next, and when plain Python is enough.
Prompt chaining means feeding the output of one model call into the next, so you can break a complex task into reliable steps instead of asking for everything in one giant prompt. It is the backbone of most serious LLM workflows, and LangChain made it the core primitive with LCEL (LangChain Expression Language). This guide shows how to chain prompts with LangChain, how to pass one call's output into the next, and when a plain Python function is all you need.
Why chain instead of one big prompt
A single prompt that has to "summarize, then translate, then extract entities" asks the model to hold everything at once and get it all right in one shot. Splitting it into steps buys three things: each step is simpler and more reliable, you can inspect the intermediate output when something looks off, and you can run code — validation, an API call, filtering — between calls. The cost is more model calls, so more latency and spend. You chain when reliability matters more than speed.
Chaining with LangChain (LCEL)
In modern LangChain you compose a prompt, a model, and an output parser with the pipe operator |, then call .invoke():
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template("Summarize in one sentence: {text}")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
chain = prompt | model | parser
summary = chain.invoke({"text": my_text})
The | builds a RunnableSequence: each link's output becomes the next link's input. StrOutputParser turns the model's message into a plain string.
Passing one prompt's output into the next
This is the heart of chaining. To feed a first call's result into a variable of a second prompt, inject the first link as the input value of the second:
summary = prompt | model | StrOutputParser()
translate_prompt = ChatPromptTemplate.from_template("Translate to French: {text}")
chain = {"text": summary} | translate_prompt | model | StrOutputParser()
translation = chain.invoke({"text": my_text})
Here {"text": summary} runs the summary chain first, puts its result in the text key, then hands it to the second prompt. The original input (my_text) flows through the whole sequence. This pattern — a dict whose value is a sub-chain — is the idiomatic way to wire one step into the next.
The alternative: plain Python
LangChain adds structure, but chaining two prompts does not require it. With a provider SDK, it is two calls and a variable:
summary = client.responses.create(
model="gpt-4o-mini",
input=f"Summarize in one sentence: {my_text}",
).output_text
translation = client.responses.create(
model="gpt-4o-mini",
input=f"Translate to French: {summary}",
).output_text
For two or three linear steps, this is more readable and dependency-free. LangChain earns its keep once the chain branches, or you want streaming, parallel runs, retries, or tracing — when orchestration itself becomes the problem. Below that, a Python function does the job.
Best practices
- Validate between steps. The point of chaining is being able to check the intermediate output before passing it on. Do not wire links blindly.
- Keep each prompt single-purpose. One link, one transformation. Easier to debug and reuse.
- Watch the cost. Every link is a billed call. Three steps is three times the spend — chain what needs it, no more.
- Treat prompts as code. Version them, test them on known inputs. A link that drifts breaks everything downstream.
Branching: run steps in parallel
Not every chain is a straight line. When two steps are independent — say, summarizing and extracting keywords from the same text — run them together with a dict of runnables, which LangChain executes in parallel and returns as a dict:
from langchain_core.runnables import RunnableParallel
fanout = RunnableParallel(
summary=prompt | model | StrOutputParser(),
keywords=keyword_prompt | model | StrOutputParser(),
)
result = fanout.invoke({"text": my_text})
# result == {"summary": "...", "keywords": "..."}
Both branches receive the same input and run concurrently, which is faster than chaining them one after another.
Streaming and inspecting
Every LCEL chain exposes the same interface: .invoke() for one input, .batch() for many, and .stream() to receive tokens as they are generated. When a chain misbehaves, the fastest way to debug it is to .invoke() each sub-chain on its own and look at the intermediate value — the whole point of chaining is that those seams are inspectable.
When not to chain
Chaining is not free. If a single well-written prompt already gives reliable results, splitting it into three calls only adds latency and cost for no gain. Reach for a chain when a step genuinely depends on the cleaned-up output of the previous one, or when you need to run code — a database lookup, a validation check — in between. Otherwise, keep it to one call and move on.
Where to go next
Prompt chaining is a pattern, not a tool: you can build it with LangChain, with a bare SDK, or with a skill that encodes the workflow for your assistant. For AI and data skills, see the data & AI category or the skills catalog. More practical guides are on the blog.