Chains & Prompt Templates
Prompt templates, a simple prompt-model-parser chain, and why chaining steps together is useful.
Prompt templates
A prompt template is a reusable prompt string with placeholders for variables, so application code can fill in the specifics without rebuilding the prompt's structure and instructions every time.
from langchain_core.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_template(
"Summarize the following {document_type} in {sentence_count} sentences:\n\n{content}"
)
prompt = template.invoke({
"document_type": "support ticket",
"sentence_count": 2,
"content": "Customer reports the app crashes on launch after the latest update...",
})
This keeps the instructional wrapper (tone, format, constraints) defined once, in one place, while the actual variable content changes per call — the same benefit a function signature gives you over inlining literal values everywhere.
A simple chain: prompt → model → output parser
A chain connects these pieces together so data flows from one step into the next automatically: a prompt template formats the input, the model generates a response, and an output parser converts the raw model output into a usable shape (plain text, a list, structured JSON).
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("Translate this to French: {text}")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
chain = prompt | model | parser # the pipe operator composes the steps
result = chain.invoke({"text": "Where is the nearest train station?"})
# result -> "Où se trouve la gare la plus proche ?"
The | operator here builds a pipeline: the dictionary passed to .invoke() flows into the prompt template, the formatted prompt flows into the model, and the model's raw response flows into the parser, which returns a clean string instead of a raw API response object.
Why chaining is useful
The real value shows up once a task needs more than one step, where each step's output is the next step's input. Consider "summarize a document, then translate that summary into French" — two genuinely separate operations, each best done as its own focused prompt rather than one prompt trying to do both at once:
summarize_prompt = ChatPromptTemplate.from_template(
"Summarize this document in 2 sentences:\n\n{document}"
)
translate_prompt = ChatPromptTemplate.from_template(
"Translate this text to French:\n\n{summary}"
)
summarize_chain = summarize_prompt | model | parser
translate_chain = translate_prompt | model | parser
summary = summarize_chain.invoke({"document": long_english_document})
french_summary = translate_chain.invoke({"summary": summary})
Splitting this into two composed chains, rather than one prompt asking for "a 2-sentence French summary" in a single pass, makes each step individually easier to test, debug, and improve — if the French output looks wrong, you can inspect summary on its own and immediately tell whether the problem was in summarizing or in translating.
Common mistakes
- Cramming multiple distinct operations into a single mega-prompt instead of composing focused chains — it's harder to debug (which part went wrong?) and harder to reuse (the summarization prompt alone can't be reused elsewhere if it's fused with translation).
- Ignoring the output parser step and manually string-parsing the model's raw response in application code — brittle, and exactly the boilerplate output parsers exist to standardize.
- Hardcoding literal values into a prompt string instead of using template variables — makes the prompt impossible to reuse across different inputs without editing the template itself each time.