LangChain models, prompts, and parsers
The minimum-viable LangChain — chat models behind a unified interface, prompt templates, output parsers, and when you actually need any of it.
What you'll learn
- The model + prompt + parser triple that almost every LangChain app starts with
- Why LangChain abstracts the provider SDKs and when that pays off
- When LangChain is overkill and a single `client.responses.create` is enough
Before you start
LangChain has been through enough rewrites that “what is LangChain” has a different answer every year. The honest one today is: it’s a thin uniform layer over the provider SDKs, plus prompt templates, plus output parsers, plus a composition operator (LCEL). That’s it. Everything else — agents, RAG, memory — is built on top.
You’ll spend most of your LangChain time on three primitives. They
all implement the same Runnable interface — meaning every primitive
exposes .invoke(), .batch(), .stream(), and .ainvoke(). That
uniform contract is why you can pipe them together with |.
Chat models — one interface, many providers
The core abstraction is a ChatModel. You import the provider you
want and it behaves the same way as every other provider’s model.
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
gpt = ChatOpenAI(model="gpt-4.1", temperature=0)
claude = ChatAnthropic(model="claude-opus-4.7", temperature=0)
# Same interface
gpt.invoke("Say hi.")
claude.invoke("Say hi.")
Both return an AIMessage. Both accept the same kind of input.
Both have .invoke(), .batch(), .stream(), and .ainvoke().
This uniformity is the whole point — when you want to swap from
OpenAI to Anthropic for a single chain, you change one import line.
Prompts — templates with named slots
You almost never want to concatenate strings to build prompts.
ChatPromptTemplate gives you a typed, named-slot version of the
“messages” array.
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Be concise."),
("user", "{question}"),
])
messages = prompt.invoke({"role": "SQL tutor", "question": "What is a JOIN?"})
# → [SystemMessage("You are a SQL tutor. Be concise."),
# HumanMessage("What is a JOIN?")]
The point: prompts become a unit of code you can test, version, and reuse — not a smear of f-strings spread across your app.
Output parsers — typed results
By default a chat model returns an AIMessage with a .content
string. Parsers convert that into something you can program with.
StrOutputParser— pulls out.contentas a plain string.JsonOutputParser— parses the content as JSON, errors loudly if the model returns garbage.PydanticOutputParser— validates against a Pydantic model.
The minimum-viable chain
Compose all three with the pipe (|) operator — each step’s output
becomes the next step’s input, and the result is itself a Runnable
you can .invoke() or .stream(). This is LCEL — covered properly
in the next lesson, but you’ll see it everywhere so it’s worth
meeting now.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Be concise."),
("user", "{question}"),
])
model = ChatOpenAI(model="gpt-4.1", temperature=0)
parser = StrOutputParser()
# LCEL: pipe the prompt into the model into the parser.
chain = prompt | model | parser
result = chain.invoke({"role": "SQL tutor", "question": "What is a JOIN?"})
print(result)
# -> "A JOIN combines rows from two tables based on a related column."
The shape prompt | model | parser is the LangChain “hello world.”
You’ll write hundreds of these, all variations on this triple.
Why bother — when does the abstraction pay off?
LangChain costs you:
- An extra dependency tree that moves quickly.
- A layer of indirection between you and the model API.
- Idioms that obscure what’s actually being sent to the LLM.
It pays for itself when:
- You compare providers. Same chain, swap
ChatOpenAIforChatAnthropicforChatGoogleGenerativeAI. Measure quality, cost, latency on identical inputs. Without LangChain, you’d write three glue layers. - You want batch / stream / async for free. Every Runnable has all four execution modes. Building these yourself is plumbing you’ll get wrong.
- You compose multiple steps. The moment you have prompt → model → parser → second prompt → second model, the pipe operator earns its keep. We’ll see this in the LCEL lesson.
A common newcomer trap
You’ll see code like this in old tutorials:
# Pre-2024 style — deprecated
from langchain.llms import OpenAI
llm = OpenAI(openai_api_key="...")
llm("What is SQL?")
Today, that’s all gone. The modern style is:
from langchain_openai import ChatOpenAI(provider lives in its own package).ChatOpenAI(chat models), notOpenAI(completion models — mostly retired)..invoke(...), not__call__.
If you copy-paste from a 2023 blog post, you’ll fight import errors for half an hour. The package boundary moved.
In one breath
- LangChain today is a thin uniform layer over provider SDKs + prompt templates + output parsers + a composition operator (LCEL).
- The three primitives all implement the
Runnableinterface (.invoke/.batch/.stream/.ainvoke), which is why you can pipe them with|. - A
ChatModelbehaves identically across providers (swapChatOpenAIforChatAnthropicby changing one import);ChatPromptTemplategives named-slot prompts; parsers turn theAIMessageinto a string, JSON, or Pydantic object. - The “hello world” is
prompt | model | parser— a composed Runnable you’ll write hundreds of variations of. - It pays off when you compare providers, want batch/stream/async for free, or compose multiple steps — but a single
responses.createcall doesn’t need it.
Quick check
Quick check
Next
prompt | model | parser was a teaser — the next lesson is LCEL
proper: branching, fan-out, streaming, and the pieces that make
LangChain more than a polite SDK wrapper.
Practice this in an interview
All questionsThe core toolkit is: system prompts (role and constraints), few-shot examples (format and tone anchoring), chain-of-thought (step-by-step reasoning), and output constraints (JSON schema, stop sequences). Combining these predictably closes the gap between a capable base model and a production-ready feature.
Chain-of-thought (CoT) prompting instructs the model to write out intermediate reasoning steps before producing a final answer, which improves accuracy on multi-step arithmetic, logic puzzles, and compositional questions. It is most impactful on models with at least ~10B parameters and on tasks where the answer space is large enough that guessing is hard.
Cost scales with input plus output tokens; latency scales with output tokens and model size. The highest-leverage levers are: model routing (use a small model when the task is simple), prompt caching (reuse expensive prefix computation), output length control, and batching. Together these can cut spend 60–90% without quality regression.
Modern APIs offer constrained decoding — the model's token sampling is restricted to only produce tokens that are valid continuations of a JSON schema. Combined with Pydantic validation in application code, this eliminates the JSON-parsing errors that plagued earlier prompt-only approaches. When constrained decoding is unavailable, few-shot examples plus output parsing with retry is the fallback.