This is the first post in our LangChain fundamentals series. Later posts cover RAG applications, memory, agents and tools, and debugging.
A single call to a model's chat completions API, covered directly in our OpenAI series, handles a huge share of real use cases on its own — plenty of applications never need anything more. LangChain earns its place once an application needs to compose several steps together: format a prompt from a template, call a model, parse the structured result, feed it into a second call — as one coherent, reusable pipeline, rather than hand-wiring that sequence with custom glue code every time.
Prompt templates: parameterizing prompts properly
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are a technical writer explaining {topic} to {audience}."),
("user", "{question}"),
])
formatted = prompt.invoke({
"topic": "Kubernetes",
"audience": "engineers new to containers",
"question": "What's the difference between a Pod and a container?",
})This is the same instinct as a Jinja2 template in Ansible, or a parameterized Terraform module, applied to prompts specifically: the structure of the prompt is defined once, and the actual variable content is supplied per call — rather than string-concatenating a prompt by hand throughout an application's code, with the risk of subtle formatting inconsistencies creeping in at each call site.
Models: a consistent interface across providers
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o", temperature=0.2)
response = model.invoke(formatted)
print(response.content)LangChain wraps a given provider's actual API (OpenAI, Anthropic, and many others each have their own integration package) behind a consistent .invoke() interface. The genuine value here isn't hiding complexity that wasn't there — it's that swapping the underlying model provider, or comparing two providers on the same task, means changing one line (ChatOpenAI to, say, ChatAnthropic) rather than rewriting every call site's provider-specific API calls throughout an application.
Output parsers: turning a response into a usable value
from langchain_core.output_parsers import StrOutputParser
chain = prompt | model | StrOutputParser()
result = chain.invoke({
"topic": "Kubernetes",
"audience": "engineers new to containers",
"question": "What's the difference between a Pod and a container?",
})
print(result)A Pod is Kubernetes' smallest deployable unit — a thin wrapper around
one or more containers that are always scheduled together and share a
network namespace...
That | operator is LangChain's LCEL (LangChain Expression Language) — chaining a prompt template, a model, and an output parser into a single callable pipeline. Each stage's output becomes the next stage's input automatically: the prompt template produces formatted messages, the model consumes them and produces a response object, StrOutputParser extracts just the plain text string from that response object. Three separate concerns, composed into one reusable, callable chain.
Structured output parsing
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel
class CourseRecommendation(BaseModel):
course_name: str
reason: str
urgency: str
prompt = ChatPromptTemplate.from_messages([
("system", "Recommend a course based on the user's stated need. "
"Respond with JSON matching: course_name, reason, urgency."),
("user", "{request}"),
])
chain = prompt | model | JsonOutputParser(pydantic_object=CourseRecommendation)
result = chain.invoke({"request": "My team needs to learn container orchestration fast"})
print(result){'course_name': 'Kubernetes Fundamentals', 'reason': 'Directly addresses container orchestration', 'urgency': 'high'}
This is the same structured-output guarantee covered in our OpenAI series, expressed through LangChain's chain composition instead of calling the provider's structured-output parameter directly — useful specifically when this step is one part of a larger, multi-stage chain, rather than a single standalone call.
Where a chain actually earns its complexity
A single prompt-model-parser chain, on its own, is genuinely not much simpler than calling the OpenAI SDK directly — and for a single call, calling the SDK directly is often the more transparent, more debuggable choice. LangChain's real value shows up in composing multiple chains together — the output of one becomes the input to constructing the next, conditionally, with retrieved context injected in the middle. That's exactly the shape RAG pipelines and multi-step agents take, covered in the next posts in this series.
What to actually remember from this post
- A single LLM call rarely needs LangChain — it earns its complexity once you're composing multiple steps into a pipeline.
- Prompt templates parameterize prompts the same way a Terraform module or Ansible role parameterizes infrastructure — structure defined once, values supplied per call.
- LCEL's
|operator chains prompt → model → parser into one reusable, callable pipeline, each stage's output feeding the next automatically. - The consistent model interface is what makes swapping providers cheap — a genuine advantage once an application might need to compare or switch between them.
Next in the series: Building RAG Applications with LangChain, where this chain-composition pattern becomes a full retrieval-augmented pipeline.
