Menu

LangChain Crash Course — Chains, Models, and Output Parsers

Written by Selva Prabhakaran | 23 min read

You’ve called the OpenAI API on its own. You’ve built prompt strings by hand, parsed JSON, and written error-handling code. It works — until you need to swap models, chain steps together, or get clean structured output without a custom parser each time.

That’s where LangChain comes in. It gives you four snap-together building blocks: models, prompts, parsers, and chains. Link them with a single pipe operator, and you go from raw API calls to reusable LLM pipelines in minutes.

Before we write any code, here’s how the pieces fit together.

You start with a chat model — a wrapper around an LLM API like OpenAI. Instead of raw HTTP requests, you call .invoke() and get a structured response back. But you don’t want to hard-code prompts. So you build a prompt template with slots for dynamic values. The template fills in the blanks and sends formatted messages to the model. The model returns text, but your app needs data. An output parser takes that raw text and turns it into a Python dict or a typed Pydantic object. Last, the pipe operator (|) chains all three into one pipeline. Data flows left to right — template to model to parser — in a single line.

We’ll build each piece from scratch. By the end, you’ll chain them to pull structured meeting summaries from raw notes.

What Is LangChain and Why Does It Exist?

LangChain is an open-source Python framework for building apps powered by large language models. It wraps raw API calls into parts you can mix, match, and reuse.

Why not just use the OpenAI SDK? Three pain points push people to LangChain.

Model lock-in. With the raw SDK, your code is tied to one provider. LangChain’s ChatOpenAI shares the same interface as ChatAnthropic or ChatGoogle. Swap one import, and your whole pipeline runs on a new provider.

Prompt chaos. When prompts have dynamic values, string formatting gets messy fast. ChatPromptTemplate handles variable injection, role setup, and message formatting in one spot.

Parsing headaches. The raw API returns plain text. Want JSON? You call json.loads() and hope the model played along. LangChain’s output parsers handle format instructions, parsing, and type checks for you.

python
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# Set your OpenAI API key (or load from .env)
# os.environ["OPENAI_API_KEY"] = "your-key-here"

print("LangChain imports loaded successfully")
python
LangChain imports loaded successfully

Tip: You need an OpenAI API key to run this tutorial. Create one at platform.openai.com/api-keys. Store it as the OPENAI_API_KEY environment variable. Never hard-code keys in your scripts.

Prerequisites

  • Python version: 3.10+

  • Required libraries: langchain (0.3+), langchain-openai (0.3+), langchain-core (0.3+), pydantic (2.0+)

  • Install: pip install langchain langchain-openai langchain-core pydantic

  • API key: OpenAI API key stored as OPENAI_API_KEY environment variable

  • Time to complete: ~25 minutes

Chat Models — Your LLM Connection

What does it look like to call an LLM through LangChain? Here’s the simplest case. We make a ChatOpenAI instance and pass a string to .invoke().

python
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

response = llm.invoke("What is LangChain in one sentence?")
print(type(response))
print(response.content)
python
<class 'langchain_core.messages.ai.AIMessage'>
LangChain is an open-source framework that simplifies building applications powered by large language models by providing tools for prompt management, chaining, memory, and integrations.

Two things to notice. First, you don’t get a string back — you get an AIMessage object. The text lives in .content. Second, temperature=0 locks the output. Same input, same output, every time. I use temperature=0 in tutorials so results stay the same. For creative tasks, try 0.3 to 0.7.

Quick check: What if you printed response instead of response.content? You’d see the full AIMessage with metadata — token counts, model name, and more. Give it a try.

Key Insight: Every LangChain part uses the same .invoke() interface. Models, prompts, parsers, chains — they all take input through .invoke() and return output. This shared interface is what makes them snap together.

Prompt Templates — Reusable Prompts with Variables

Say you’re building a chatbot that explains topics across different fields. One user asks about machine learning. Another asks about chemistry. The prompt shape is the same — only the topic and question change.

Instead of formatting strings by hand each time, you define a template once and reuse it. ChatPromptTemplate.from_messages() takes a list of (role, message) tuples. Put variables inside curly braces.

python
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant that explains {topic} concepts simply."),
    ("human", "{question}")
])

formatted = prompt.invoke({
    "topic": "machine learning",
    "question": "What is overfitting?"
})
print(formatted.to_string())
python
System: You are a helpful assistant that explains machine learning concepts simply.
Human: What is overfitting?

The .invoke() call gives back a ChatPromptValue — formatted messages ready for the model. No manual dict building, no missing role keys.

Don’t need a system message? Use from_template() for simpler prompts.

python
simple_prompt = ChatPromptTemplate.from_template(
    "Translate '{text}' to {language}."
)
result = simple_prompt.invoke({"text": "Hello world", "language": "French"})
print(result.to_string())
python
Human: Translate 'Hello world' to French.

Use from_template() for single-message prompts. Use from_messages() when you need a system prompt to shape model behavior. That’s the only choice to make.

Output Parsers — Getting Structured Data from LLMs

LLMs return strings. Your app needs Python objects. Output parsers bridge that gap. LangChain gives you three parsers that cover most needs.

StrOutputParser — Extract Plain Text

Why do you need a parser just to get a string? Because without it, your chain returns an AIMessage object — not clean text. Watch the difference:

python
from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()

raw_response = llm.invoke("Say hello")
print(f"Without parser: {type(raw_response).__name__}")

parsed = parser.invoke(raw_response)
print(f"With parser:    {type(parsed).__name__} -> {parsed}")
python
Without parser: AIMessage
With parser:    str -> Hello! How can I assist you today?

On its own, StrOutputParser seems small. Its real value shows up in a chain — it’s the piece that gives you a clean string at the end of the pipeline.

JsonOutputParser — Get Python Dictionaries

When you need structured data, JsonOutputParser does two things: it creates format instructions for the prompt (telling the model to reply in JSON) and it parses the response into a Python dict.

The parser’s .get_format_instructions() method builds a string you inject into your prompt. This tells the model exactly what JSON shape to produce. We’ll use .partial() to plug those instructions into the template.

python
from langchain_core.output_parsers import JsonOutputParser

json_parser = JsonOutputParser()

prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract the requested information. {format_instructions}"),
    ("human", "Give me the name, population, and continent of Japan.")
])

chain = prompt.partial(
    format_instructions=json_parser.get_format_instructions()
) | llm | json_parser

result = chain.invoke({})
print(type(result))
print(result)

This produces:

python
<class 'dict'>
{'name': 'Japan', 'population': 125700000, 'continent': 'Asia'}

That chain line uses the pipe operator | — we’ll cover it fully in the chains section. The key point: the output is a Python dict, not a string. You can grab result["name"] right away.

PydanticOutputParser — Validated Structured Output

JsonOutputParser gives you dicts, but it doesn’t check the shape or types. What if the model returns "pop" instead of "population"? Your code breaks later with a confusing KeyError.

PydanticOutputParser stops this. You define a Pydantic model — a Python class with typed fields — and the parser checks the LLM’s response against that schema.

Here we make a CountryInfo model with three required fields. The Field(description=...) tells the parser (and the LLM) what each field should hold.

python
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

class CountryInfo(BaseModel):
    name: str = Field(description="Name of the country")
    population: int = Field(description="Approximate population")
    continent: str = Field(description="Continent the country is in")

pydantic_parser = PydanticOutputParser(pydantic_object=CountryInfo)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract country information. {format_instructions}"),
    ("human", "Tell me about {country}.")
])

chain = prompt.partial(
    format_instructions=pydantic_parser.get_format_instructions()
) | llm | pydantic_parser

result = chain.invoke({"country": "Brazil"})
print(type(result))
print(f"Name: {result.name}")
print(f"Population: {result.population}")
print(f"Continent: {result.continent}")
python
<class '__main__.CountryInfo'>
Name: Brazil
Population: 214000000
Continent: South America

The result isn’t a dict — it’s a CountryInfo object with dot access. Pydantic checks types on the fly. If the model sent a string for population, you’d get a ValidationError right away instead of a hidden bug.

Predict the output: What if you set population as float instead of int? The parser would accept both 214000000 and 214000000.0, because Pydantic can convert between them. But if the model sent "two hundred million" as a string, it would fail.

Warning: LLM output isn’t always going to match your schema. Even with format instructions, models can still return bad JSON. In production, always wrap chain calls in try/except and handle OutputParserException. Retry with a clearer prompt, or fall back to string output.

I suggest PydanticOutputParser over JsonOutputParser for anything past quick tests. The type safety catches bugs before they reach your users.

Parser Comparison

Here’s a quick look at which parser to use and when:

  • StrOutputParser — When you just need text. Simple and fast.
  • JsonOutputParser — When you need a dict but don’t care about strict types. Good for quick prototypes.
  • PydanticOutputParser — When you need typed, checked output. Best for production code.

Chains — Connecting Components with LCEL

Here’s where it all clicks together.

A chain links LangChain parts into a pipeline. Data flows from one to the next. The prompt fills in values, the model creates a response, and the parser pulls out structured results.

LangChain Expression Language (LCEL) uses the pipe operator | to connect parts. If you’ve used Unix pipes (cat file.txt | grep "error"), it works the same way. The output on the left becomes the input on the right.

The simplest useful chain has three steps: prompt, model, parser.

python
prompt = ChatPromptTemplate.from_template(
    "Explain {concept} in 2 sentences for a beginner."
)
chain = prompt | llm | StrOutputParser()

result = chain.invoke({"concept": "neural networks"})
print(result)
python
Neural networks are computational models inspired by the human brain, consisting of layers of interconnected nodes that process and learn patterns from data. They are widely used in tasks like image recognition, language processing, and prediction by adjusting the strength of connections during training.

Three parts. One pipe line. Clean string output.

What Happens Under the Hood

When you write prompt | llm | parser, Python doesn’t run anything yet. It calls the __or__ method on each part, building a RunnableSequence object. This stores the list of steps in order. Nothing runs until you call .invoke().

Let’s check by looking at the chain.

python
from langchain_core.runnables import RunnableSequence

chain = prompt | llm | StrOutputParser()

print(f"Chain type: {type(chain).__name__}")
print(f"Is RunnableSequence: {isinstance(chain, RunnableSequence)}")
print(f"Steps: {len(chain.steps)}")
for i, step in enumerate(chain.steps):
    print(f"  Step {i}: {type(step).__name__}")

Prints:

python
Chain type: RunnableSequence
Is RunnableSequence: True
Steps: 3
  Step 0: ChatPromptTemplate
  Step 1: ChatOpenAI
  Step 2: StrOutputParser

Key Insight: The pipe operator | doesn’t run anything. It builds a RunnableSequence — a blueprint for your pipeline. Code only runs when you call .invoke(), .batch(), or .stream().

Beyond .invoke() — Batch and Stream

Every chain supports three run modes out of the box. You don’t write extra code for any of them.

.batch() runs many inputs at once. Pass a list of dicts, get a list of results.

python
results = chain.batch([
    {"concept": "API"},
    {"concept": "REST"},
])
for r in results:
    print(r[:80] + "...")
    print()
python
An API (Application Programming Interface) is a set of rules and protocols that...

REST (Representational State Transfer) is an architectural style for designing n...

.stream() sends output tokens one at a time — great for showing responses in real time. You’d use this in a chatbot where users want to see text appear word by word.

python
for chunk in chain.stream({"concept": "machine learning"}):
    print(chunk, end="", flush=True)
print()
python
Machine learning is a subset of artificial intelligence that enables computers to learn patterns from data and make predictions or decisions without being explicitly programmed. It works by training algorithms on large datasets, allowing them to improve their performance over time as they are exposed to more data.

These three methods — .invoke(), .batch(), .stream() — work on every Runnable in LangChain. Models, parsers, chains, all of them. That’s the payoff of the shared interface.

Building a Real-World Chain

Toy examples teach syntax. Let’s build something you’d use at work.

Scenario: You’re building a tool that takes raw meeting notes and pulls out a structured summary with action items. The output must be a typed Python object you can send to a project management API.

We need three things: a Pydantic schema for the output, a prompt that tells the model what to extract, and a chain that ties it all together.

First, the output schema. A meeting summary has a title, a list of key decisions, and action items. Each action item pairs a task with a person.

python
from typing import List

class ActionItem(BaseModel):
    task: str = Field(description="Description of the action item")
    assignee: str = Field(description="Person responsible")

class MeetingSummary(BaseModel):
    title: str = Field(description="Brief meeting title")
    key_decisions: List[str] = Field(description="Main decisions made")
    action_items: List[ActionItem] = Field(description="Tasks assigned")

Next, the chain. We inject Pydantic format instructions into the prompt so the model knows what JSON shape to return. Then we pipe prompt to model to parser.

python
summary_parser = PydanticOutputParser(pydantic_object=MeetingSummary)

summary_prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a meeting notes assistant. Extract structured "
     "information from the meeting transcript.\n"
     "{format_instructions}"),
    ("human", "{transcript}")
])

summary_chain = summary_prompt.partial(
    format_instructions=summary_parser.get_format_instructions()
) | llm | summary_parser

Let’s feed it a sample transcript and see what we get.

python
meeting_notes = """
Team standup March 10. Present: Alice, Bob, Carol.
Alice said the data pipeline is done and ready for review.
Bob mentioned the API rate limits are causing issues in production.
We decided to implement exponential backoff for API calls.
Carol will write the retry logic by Friday.
Bob will set up monitoring dashboards by next Tuesday.
Alice will review Carol's PR once it's ready.
"""

summary = summary_chain.invoke({"transcript": meeting_notes})
print(f"Title: {summary.title}")
print(f"\nKey Decisions:")
for decision in summary.key_decisions:
    print(f"  - {decision}")
print(f"\nAction Items:")
for item in summary.action_items:
    print(f"  - {item.task} (Assigned to: {item.assignee})")
python
Title: Team Standup March 10
Key Decisions:
  - Implement exponential backoff for API calls
Action Items:
  - Write the retry logic by Friday (Assigned to: Carol)
  - Set up monitoring dashboards by next Tuesday (Assigned to: Bob)
  - Review Carol's PR once it's ready (Assigned to: Alice)

One chain call turned messy meeting notes into a typed Python object with clean fields. You could send summary.action_items straight to Jira, Asana, or any task API — no manual parsing needed.

Tip: Use nested Pydantic models for rich output. The ActionItem class nested inside MeetingSummary shows how to get deeply structured data. You can nest as many levels as you need. The parser creates format instructions for the whole tree.

LangChain vs Raw OpenAI API

Is LangChain worth the extra package? Here’s an honest look.

For the same “extract country info” task, the raw OpenAI way looks like this:

python
# Raw OpenAI approach (pseudocode for comparison)
# client = openai.OpenAI()
# response = client.chat.completions.create(
#     model="gpt-4o-mini",
#     messages=[
#         {"role": "system", "content": "Return JSON with: name, population, continent"},
#         {"role": "user", "content": "Tell me about France"}
#     ],
#     response_format={"type": "json_object"}
# )
# data = json.loads(response.choices[0].message.content)
# No type validation -- data["population"] could be a string

The LangChain way with PydanticOutputParser gives you type checks, reusable templates, and a chain you can extend — all in roughly the same lines of code.

Feature Raw OpenAI SDK LangChain
Swap to Claude/Gemini Rewrite all API calls Change one import
Reusable prompts Copy-paste strings Template objects
Output validation Write custom code Built-in parsers
Chain multiple steps Nested function calls Pipe operator \|
Streaming Manual chunk handling .stream() built-in
Batch processing Write a loop .batch() built-in

For a single API call in a script, the raw SDK works fine. But once you’re building multi-step pipelines, switching providers, or need typed output, LangChain saves real time. That’s the tradeoff.

Common Mistakes and How to Fix Them

Mistake 1: Missing the provider package

LangChain keeps its core separate from provider packages. If you only install langchain, you won’t have ChatOpenAI.

Wrong:

python
# pip install langchain
# from langchain_openai import ChatOpenAI  # ImportError!

Right:

python
# pip install langchain langchain-openai
from langchain_openai import ChatOpenAI
print("Import successful")
python
Import successful

Mistake 2: Passing a string to a chain that expects a dict

Chains expect dicts with keys that match your template variables. A plain string throws a TypeError.

Wrong:

python
# chain.invoke("What is Python?")
# TypeError: Expected mapping type as input

Right:

python
result = chain.invoke({"concept": "Python"})
print(result[:100])
python
Python is a high-level, versatile programming language known for its readability and simplicity, mak

Mistake 3: Not handling parser failures

LLMs don’t always produce valid JSON. Without error handling, your app crashes on the first bad response.

Wrong:

python
# result = pydantic_chain.invoke({"country": "..."})
# Crashes if model returns invalid JSON

Right:

python
from langchain_core.exceptions import OutputParserException

try:
    result = chain.invoke({"concept": "error handling in Python"})
    print(result[:100])
except OutputParserException as e:
    print(f"Parser failed: {e}")
python
Error handling in Python involves using try-except blocks to catch and manage exceptions that may oc

Mistake 4: Using the old LLMChain class

Older guides use LLMChain from langchain.chains. This class was dropped in LangChain 0.1.17. Always use LCEL with the pipe operator.

Wrong:

python
# from langchain.chains import LLMChain  # Deprecated!
# chain = LLMChain(llm=llm, prompt=prompt)

Right:

python
chain = prompt | llm | StrOutputParser()
print("LCEL chain created successfully")
python
LCEL chain created successfully

Note: LangChain 1.0 came out in November 2025. It moved old code to langchain-classic. The LCEL pipe syntax is the way forward. If you follow an older guide and see LLMChain, SequentialChain, or SimpleSequentialChain, swap them out for LCEL pipe chains.

Practice Exercises

Exercise 1: Build a Translation Chain

Create a LangChain chain that translates text between languages.

  1. Make a ChatPromptTemplate with variables {text} and {target_language}
  2. Chain it with the llm and StrOutputParser using the pipe operator
  3. Call the chain to translate “Good morning” to Spanish
  4. Print the result

Starter code:

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# Step 1: Create the prompt template
translate_prompt = ChatPromptTemplate.from_template(
    # Your template here -- include {text} and {target_language}
)

# Step 2: Build the chain with |
translate_chain = # prompt | llm | parser

# Step 3: Invoke and print
result = translate_chain.invoke({
    "text": "Good morning",
    "target_language": "Spanish"
})
print(result)

Hints:
– Template: "Translate the following text to {target_language}: {text}"
– Full chain: translate_prompt | llm | StrOutputParser()

Click to see the solution
python
translate_prompt = ChatPromptTemplate.from_template(
    "Translate the following text to {target_language}: {text}"
)
translate_chain = translate_prompt | llm | StrOutputParser()
result = translate_chain.invoke({
    "text": "Good morning",
    "target_language": "Spanish"
})
print(result)

The prompt template uses `{text}` for the input and `{target_language}` for the target. The pipe operator chains template to model to parser. `StrOutputParser` pulls the plain text from the `AIMessage`.

Exercise 2: Extract Structured Data with PydanticOutputParser

Create a chain that pulls book info into a Pydantic model.

  1. Add author (str) and year (int) fields to the BookInfo model
  2. The parser and prompt are set up for you
  3. Build the chain and run it with the given text
  4. Print the title, author, and year

Starter code:

python
from pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate

# Step 1: Complete the Pydantic model
class BookInfo(BaseModel):
    title: str = Field(description="Title of the book")
    # Add author and year fields here

# Step 2: Parser and prompt (already done)
book_parser = PydanticOutputParser(pydantic_object=BookInfo)
book_prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract book information. {format_instructions}"),
    ("human", "{text}")
])

# Step 3: Build and invoke the chain
book_chain = book_prompt.partial(
    format_instructions=book_parser.get_format_instructions()
) | llm | book_parser

result = book_chain.invoke({
    "text": "The Great Gatsby was written by F. Scott Fitzgerald in 1925"
})
print(f"Title: {result.title}")
print(f"Author: {result.author}")
print(f"Year: {result.year}")

Hints:
– Add: author: str = Field(description="Author of the book")
– Add: year: int = Field(description="Publication year")

Click to see the solution
python
class BookInfo(BaseModel):
    title: str = Field(description="Title of the book")
    author: str = Field(description="Author of the book")
    year: int = Field(description="Publication year")

book_parser = PydanticOutputParser(pydantic_object=BookInfo)
book_prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract book information. {format_instructions}"),
    ("human", "{text}")
])
book_chain = book_prompt.partial(
    format_instructions=book_parser.get_format_instructions()
) | llm | book_parser
result = book_chain.invoke({
    "text": "The Great Gatsby was written by F. Scott Fitzgerald in 1925"
})
print(f"Title: {result.title}")
print(f"Author: {result.author}")
print(f"Year: {result.year}")

The Pydantic model sets typed fields for structured data. `PydanticOutputParser` builds format instructions telling the LLM what JSON shape to produce. The parser then checks and converts the response into a `BookInfo` object with proper type handling.

Summary

You’ve learned the four building blocks that power every LangChain app:

  • Chat models (ChatOpenAI) wrap LLM APIs behind a shared .invoke() interface

  • Prompt templates (ChatPromptTemplate) create reusable prompts with dynamic values

  • Output parsers (StrOutputParser, JsonOutputParser, PydanticOutputParser) turn raw model text into structured Python objects

  • Chains (LCEL pipe |) link parts into pipelines where data flows left to right

These are the base for LangGraph. When you build agents, state machines, and multi-step workflows in future posts, you’ll use these same pieces in more advanced patterns.

Practice exercise: Build a chain that takes a product blurb and pulls out a ProductInfo object with fields: name (str), category (str), price_range (str — “low”, “medium”, or “high”), and tagline (str — one-sentence marketing copy). Use PydanticOutputParser for type checks. Test it with: “The AirPods Pro 2 are premium wireless earbuds by Apple with active noise cancellation, priced at $249.”

Click to see the solution
python
class ProductInfo(BaseModel):
    name: str = Field(description="Product name")
    category: str = Field(description="Product category")
    price_range: str = Field(description="Price range: low, medium, or high")
    tagline: str = Field(description="One-sentence marketing tagline")

product_parser = PydanticOutputParser(pydantic_object=ProductInfo)
product_prompt = ChatPromptTemplate.from_messages([
    ("system",
     "Extract product information from the description. "
     "{format_instructions}"),
    ("human", "{description}")
])
product_chain = product_prompt.partial(
    format_instructions=product_parser.get_format_instructions()
) | llm | product_parser

result = product_chain.invoke({
    "description": "The AirPods Pro 2 are premium wireless earbuds "
    "by Apple with active noise cancellation, priced at $249."
})
print(f"Name: {result.name}")
print(f"Category: {result.category}")
print(f"Price Range: {result.price_range}")
print(f"Tagline: {result.tagline}")

The chain pulls all four fields into a typed `ProductInfo` object. Pydantic checks each field’s type. If the model skips `tagline`, the parser throws a `ValidationError` instead of giving you a half-finished object.

Complete Code

Click to expand the full script (copy-paste and run)
python
# Complete code from: LangChain Crash Course -- Chains, Models, and Output Parsers
# Requires: pip install langchain langchain-openai langchain-core pydantic
# Python 3.10+
# Set OPENAI_API_KEY environment variable before running

import os
from typing import List
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import (
    StrOutputParser,
    JsonOutputParser,
    PydanticOutputParser,
)
from langchain_core.runnables import RunnableSequence
from langchain_core.exceptions import OutputParserException
from pydantic import BaseModel, Field

# --- Section 1: Chat Model ---
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
response = llm.invoke("What is LangChain in one sentence?")
print("=== Chat Model ===")
print(response.content)

# --- Section 2: Prompt Templates ---
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant that explains {topic} concepts simply."),
    ("human", "{question}")
])
formatted = prompt.invoke({
    "topic": "machine learning",
    "question": "What is overfitting?"
})
print("\n=== Prompt Template ===")
print(formatted.to_string())

simple_prompt = ChatPromptTemplate.from_template(
    "Translate '{text}' to {language}."
)

# --- Section 3: StrOutputParser ---
parser = StrOutputParser()
raw_response = llm.invoke("Say hello")
parsed = parser.invoke(raw_response)
print("\n=== StrOutputParser ===")
print(f"Type: {type(parsed).__name__} -> {parsed}")

# --- Section 4: JsonOutputParser ---
json_parser = JsonOutputParser()
json_prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract the requested information. {format_instructions}"),
    ("human", "Give me the name, population, and continent of Japan.")
])
json_chain = json_prompt.partial(
    format_instructions=json_parser.get_format_instructions()
) | llm | json_parser
json_result = json_chain.invoke({})
print("\n=== JsonOutputParser ===")
print(json_result)

# --- Section 5: PydanticOutputParser ---
class CountryInfo(BaseModel):
    name: str = Field(description="Name of the country")
    population: int = Field(description="Approximate population")
    continent: str = Field(description="Continent the country is in")

pydantic_parser = PydanticOutputParser(pydantic_object=CountryInfo)
pydantic_prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract country information. {format_instructions}"),
    ("human", "Tell me about {country}.")
])
pydantic_chain = pydantic_prompt.partial(
    format_instructions=pydantic_parser.get_format_instructions()
) | llm | pydantic_parser
pydantic_result = pydantic_chain.invoke({"country": "Brazil"})
print("\n=== PydanticOutputParser ===")
print(f"Name: {pydantic_result.name}")
print(f"Population: {pydantic_result.population}")
print(f"Continent: {pydantic_result.continent}")

# --- Section 6: LCEL Chain ---
explain_prompt = ChatPromptTemplate.from_template(
    "Explain {concept} in 2 sentences for a beginner."
)
explain_chain = explain_prompt | llm | StrOutputParser()
chain_result = explain_chain.invoke({"concept": "neural networks"})
print("\n=== LCEL Chain ===")
print(chain_result)

# --- Section 7: RunnableSequence inspection ---
print(f"\nChain type: {type(explain_chain).__name__}")
print(f"Steps: {len(explain_chain.steps)}")
for i, step in enumerate(explain_chain.steps):
    print(f"  Step {i}: {type(step).__name__}")

# --- Section 8: Batch execution ---
batch_results = explain_chain.batch([
    {"concept": "API"},
    {"concept": "REST"},
])
print("\n=== Batch Results ===")
for r in batch_results:
    print(r[:80] + "...\n")

# --- Section 9: Real-World Meeting Summary Chain ---
class ActionItem(BaseModel):
    task: str = Field(description="Description of the action item")
    assignee: str = Field(description="Person responsible")

class MeetingSummary(BaseModel):
    title: str = Field(description="Brief meeting title")
    key_decisions: List[str] = Field(description="Main decisions made")
    action_items: List[ActionItem] = Field(description="Tasks assigned")

summary_parser = PydanticOutputParser(pydantic_object=MeetingSummary)
summary_prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a meeting notes assistant. Extract structured "
     "information from the meeting transcript.\n"
     "{format_instructions}"),
    ("human", "{transcript}")
])
summary_chain = summary_prompt.partial(
    format_instructions=summary_parser.get_format_instructions()
) | llm | summary_parser

meeting_notes = """
Team standup March 10. Present: Alice, Bob, Carol.
Alice said the data pipeline is done and ready for review.
Bob mentioned the API rate limits are causing issues in production.
We decided to implement exponential backoff for API calls.
Carol will write the retry logic by Friday.
Bob will set up monitoring dashboards by next Tuesday.
Alice will review Carol's PR once it's ready.
"""
summary = summary_chain.invoke({"transcript": meeting_notes})
print("\n=== Meeting Summary ===")
print(f"Title: {summary.title}")
for d in summary.key_decisions:
    print(f"  Decision: {d}")
for item in summary.action_items:
    print(f"  Action: {item.task} -> {item.assignee}")

print("\nScript completed successfully.")

Frequently Asked Questions

Can I use LangChain with models other than OpenAI?

Yes. LangChain supports dozens of providers. Install langchain-anthropic for Claude, langchain-google-genai for Gemini, or use langchain-community for open-source models through Ollama. The .invoke() interface is the same for all of them. Swap the import and the setup, and your chains work as before.

python
# Example: switching to Anthropic (not runnable without the package)
# from langchain_anthropic import ChatAnthropic
# llm = ChatAnthropic(model="claude-sonnet-4-20250514")
# Same .invoke() interface -- chains work identically

What’s the difference between .invoke(), .batch(), and .stream()?

.invoke() handles one input and returns the full result. .batch() runs many inputs at once and returns a list. .stream() sends output tokens one at a time for live display. All three work on every LangChain Runnable — models, parsers, and chains alike.

Do I still need LangChain if OpenAI supports structured outputs on its own?

OpenAI’s response_format handles JSON mode for their models. But it only works with OpenAI. LangChain adds model portability, prompt templates, and chain building. If you only use OpenAI and don’t need multi-step chains, the raw SDK works fine. Once you add more steps or want to switch providers, LangChain pays for itself.

Is LCEL the only way to build chains?

The older LLMChain, SequentialChain, and SimpleSequentialChain classes are gone and live in langchain-classic. LCEL with the pipe operator is the current standard. All new LangChain docs, examples, and features use LCEL only.

References

  • LangChain Documentation — Chat models
  • LangChain Documentation — LangChain Expression Language (LCEL)
  • LangChain Documentation — Output parsers
  • LangChain Documentation — Prompt templates
  • LangChain-OpenAI package — PyPI
  • Pydantic Documentation — Models
  • OpenAI API Reference — Chat completions
  • LangChain GitHub — Releases and changelog
Free Course
Master Core Python — Your First Step into AI/ML

Build a strong Python foundation with hands-on exercises designed for aspiring Data Scientists and AI/ML Engineers.

Start Free Course
Trusted by 50,000+ learners
Related Course
Master Gen AI — Hands-On
Join 5,000+ students at edu.machinelearningplus.com
Explore Course
Get the full course,
completely free.
Join 57,000+ students learning Python, SQL & ML. One year of access, all resources included.
📚 10 Courses
🐍 Python & ML
🗄️ SQL
📦 Downloads
📅 1 Year Access
No thanks
🎓
Free AI/ML Starter Kit
Python · SQL · ML · 10 Courses · 57,000+ students
🎉   You're in! Check your inbox (or Promotions/Spam) for the access link.
⚡ Before you go

Python.
SQL. NumPy.
All free.

Get the exact 10-course programming foundation that Data Science professionals use.

🐍
Core Python — from first line to expert level
📈
NumPy & Pandas — the #1 libraries every DS job needs
🗃️
SQL Levels I–III — basics to Window Functions
📄
Real industry data — Jupyter notebooks included
R A M S K
57,000+ students
★★★★★ Rated 4.9/5
⚡ Before you go
Python. SQL.
All Free.
R A M S K
57,000+ students  ★★★★★ 4.9/5
Get Free Access Now
10 courses. Real projects. Zero cost. No credit card.
New learners enrolling right now
🔒 100% free ☕ No spam, ever ✓ Instant access
🚀
You're in!
Check your inbox for your access link.
(Check Promotions or Spam if you don't see it)
Or start your first course right now:
Start Free Course →
Scroll to Top
Scroll to Top
Course Preview

Machine Learning A-Z™: Hands-On Python & R In Data Science

Free Sample Videos:

Machine Learning A-Z™: Hands-On Python & R In Data Science

Machine Learning A-Z™: Hands-On Python & R In Data Science

Machine Learning A-Z™: Hands-On Python & R In Data Science

Machine Learning A-Z™: Hands-On Python & R In Data Science

Machine Learning A-Z™: Hands-On Python & R In Data Science