What a chain actually is
Strip the branding away and a chain is a list of steps where each one hands its output to the next. Build one from plain functions before any framework is involved.
A pipeline with a name
A chain is a sequence of steps: the first takes the input, the second takes what the first returned, and the last one produces the answer. Nothing about that needs a library. A function that cleans a string, a second that turns it into a prompt, and a third that calls a model is already a chain — three calls in a row, each using the previous result.
The reason the word exists is that the interesting part of an AI application is rarely one call. It is the arrangement: trim the question, fetch some context, build a prompt, ask the model, parse the answer, check it. Each of those is small and testable on its own; the chain is the shape you give them.
The six-step version is worth picturing because it is the shape most real applications take. Clean the question, retrieve context, build the prompt, call the model, parse the reply, validate it. Only one of those steps costs money or can return something surprising; the other five are ordinary code that can be tested without a network.
Every step has the same shape
The property that makes steps composable is a shared signature: one input in, one output out. A step that takes a string and returns a string can be followed by any other step that takes a string. That is all the pipe operator in LangChain expresses — prompt | model | parser is three callables joined end to end.
Once every step has that shape, the pieces become interchangeable. You can put a fake model in place of a real one in a test, or a different parser at the end, without touching the steps around it. The framework is mostly a vocabulary for that one idea.
The same pipeline in LangChain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("Explain {topic} in one sentence.")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
chain = prompt | model | parser
print(chain.invoke({"topic": "a vector database"}))The framework is the wiring
That snippet hides three ordinary calls. prompt.invoke turns the dictionary into a list of messages, model.invoke sends them and returns a message, and parser.invoke pulls the text out of it. The pipe is convenience, not magic: it calls invoke on each piece in turn.
Which is why you can debug a chain by taking it apart. If the answer is wrong, print what the prompt produced, then what the model returned, then what the parser made of it. The bug is always in one of those three places, and a framework that hides the boundaries makes that harder, not easier.
Three steps, no framework
Run the pipeline, then add a fourth step that wraps the sentence in quotes. Notice that every step takes one value and returns one value.
Name the boundaries
A chain is easiest to debug when each boundary has a name. Print the cleaned question, the rendered prompt, the raw reply and the parsed value; the first one that looks wrong is the step to inspect. In the framework, callbacks and tracing do this for you, but the habit is the same and it works without any library.
A common mistake is wrapping the whole chain in one try block and logging only the exception. The traceback tells you which function raised, not which value was wrong when it did. The symptom is a fix applied to the parser when the prompt was already malformed.
Where the model sits
A model is the one step whose output you cannot predict, so it is worth keeping it in the middle and keeping everything else deterministic. Cleaning the input and parsing the output are ordinary code: they can be unit-tested without a network, and they should be.
That split is also what makes cost visible. Every step before and after the model is free and instant. If a chain is slow or expensive, the reason is the number of model calls, not the number of functions.
That is also where a fake model earns its place. Replace the real call with a function that returns a fixed string, and the rest of the chain becomes a deterministic program you can test.
Write the chain as plain functions first and add the framework afterwards. If the plain version is clear, the framework version will be too; if it is not, the framework will only hide that.
Swap the model, keep the chain
The chain works with a dictionary of canned answers. Point `answer` at a topic that is missing from the dictionary and see the fallback, then add an entry so it answers properly.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What makes two steps composable into a chain?
