NovuSpark
All articles

September 26, 2025 · NovuSpark Team

LangChain 101: Chains, Prompts, and Models

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.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.