datarekha

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.

6 min read Beginner Agentic AI Lesson 29 of 71

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 .content as 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 ChatOpenAI for ChatAnthropic for ChatGoogleGenerativeAI. 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), not OpenAI (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 Runnable interface (.invoke / .batch / .stream / .ainvoke), which is why you can pipe them with |.
  • A ChatModel behaves identically across providers (swap ChatOpenAI for ChatAnthropic by changing one import); ChatPromptTemplate gives named-slot prompts; parsers turn the AIMessage into 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.create call doesn’t need it.

Quick check

Quick check

0/2
Q1What does `prompt | model | parser` produce?
Q2When does LangChain NOT pay for itself?

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.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What prompt engineering techniques should every LLM practitioner know?

The 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.

What is chain-of-thought prompting and when does it help?

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.

What techniques reduce LLM cost and latency in production?

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.

How do you reliably get structured outputs (JSON, typed objects) from an LLM?

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.

Related lessons

Explore further

Skip to content