Structured output allows agents to return data in a specific, predictable format docs.langchain.com. Instead of parsing natural language responses, you get structured data that can be directly used in your applications. In this comprehensive guide, we'll explore four powerful approaches to implementing structured output in LangChain:
TypedDict,
Annotated TypedDict,
Pydantic, and
JsonSchema

Real-World Use Case: Customer Support Ticket Analyzer
Let's build a practical application that analyzes customer support tickets and extracts structured information including:
Ticket category
Priority level
Sentiment analysis
Key entities (product names, features)
Suggested actions
Prerequisites
pip install langchain langchain-openai pydantic typing-extensionsApproach 1: TypedDict
TypedDict is a lightweight way to define structured output using standard Python typing. It's ideal when you want just enough structure without heavy validation.
from typing import Literal, List
from typing_extensions import TypedDict
from langchain_openai import ChatOpenAI
import os
# Set your API key
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# Define the schema using TypedDict
class SupportTicket(TypedDict):
"""Extracted information from customer support ticket"""
category: Literal["billing", "technical", "feature_request", "bug_report"]
priority: Literal["low", "medium", "high", "critical"]
sentiment: Literal["positive", "neutral", "negative", "angry"]
customer_name: str
product_mentioned: List[str]
urgency_indicators: List[str]
summary: str
suggested_actions: List[str]
# Initialize the LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Wrap with structured output
structured_llm = llm.with_structured_output(SupportTicket)
# Test with a real ticket
ticket_text = """
Subject: URGENT - Payment Processing Failed!!!
Hi, my name is Sarah Johnson and I'm extremely frustrated.
I've been trying to process a payment for our Enterprise plan
for the last 3 hours and keep getting error code 502.
This is blocking our entire team from accessing the analytics
dashboard. We have a board meeting tomorrow and need this
fixed IMMEDIATELY! The API endpoint /v2/payments keeps timing out.
"""
# Extract structured information
result = structured_llm.invoke(ticket_text)
print("=== TypedDict Result ===")
print(f"Category: {result['category']}")
print(f"Priority: {result['priority']}")
print(f"Sentiment: {result['sentiment']}")
print(f"Customer: {result['customer_name']}")
print(f"Products: {result['product_mentioned']}")
print(f"Summary: {result['summary']}")
print(f"Actions: {result['suggested_actions']}")Output:
=== TypedDict Result ===
Category: technical
Priority: critical
Sentiment: angry
Customer: Sarah Johnson
Products: ['Enterprise plan', 'analytics dashboard', 'API']
Summary: Customer experiencing payment processing failures with error 502, blocking team access
Actions: ['Investigate /v2/payments endpoint', 'Check server status', 'Escalate to engineering', 'Contact customer within 1 hour']Approach 2: Annotated TypedDict
Annotated TypedDict adds validation and additional metadata using Python's Annotated type, providing more control over field constraints.
from typing import Annotated, Literal, List
from typing_extensions import TypedDict, Annotated as Annotated_ext
from langchain_openai import ChatOpenAI
from pydantic import Field
# Define schema with annotations for better control
class SupportTicketAnnotated(TypedDict):
"""Support ticket with annotated fields for better control"""
category: Annotated_ext[
Literal["billing", "technical", "feature_request", "bug_report"],
Field(description="Primary category of the support ticket")
]
priority: Annotated_ext[
Literal["low", "medium", "high", "critical"],
Field(description="Priority level based on urgency and impact")
]
sentiment: Annotated_ext[
Literal["positive", "neutral", "negative", "angry"],
Field(description="Customer's emotional tone")
]
confidence_score: Annotated_ext[
float,
Field(description="Confidence in extraction (0.0 to 1.0)", ge=0.0, le=1.0)
]
customer_name: Annotated_ext[
str,
Field(description="Name of the customer", min_length=1)
]
product_mentioned: List[Annotated_ext[str, Field(description="Product or feature name")]]
urgency_indicators: List[Annotated_ext[str, Field(description="Words/phrases indicating urgency")]]
summary: Annotated_ext[
str,
Field(description="Brief summary in 2-3 sentences", max_length=200)
]
suggested_actions: List[Annotated_ext[str, Field(description="Recommended action items")]]
estimated_resolution_time: Annotated_ext[
str,
Field(description="Estimated time to resolve (e.g., '2 hours', '1 day')")
]
# Initialize LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm_annotated = llm.with_structured_output(SupportTicketAnnotated)
# Test the annotated version
result_annotated = structured_llm_annotated.invoke(ticket_text)
print("\n=== Annotated TypedDict Result ===")
print(f"Category: {result_annotated['category']}")
print(f"Priority: {result_annotated['priority']}")
print(f"Confidence: {result_annotated['confidence_score']}")
print(f"Estimated Resolution: {result_annotated['estimated_resolution_time']}")Approach 3: Pydantic (Recommended)
Pydantic provides the most robust solution with built-in validation, type checking, and data serialization. This is the recommended approach for production applications.
from pydantic import BaseModel, Field, field_validator
from typing import Literal, List
from datetime import datetime
import re
class SupportTicketPydantic(BaseModel):
"""Pydantic model for support ticket analysis with validation"""
category: Literal["billing", "technical", "feature_request", "bug_report"] = Field(
...,
description="Primary category of the support ticket"
)
priority: Literal["low", "medium", "high", "critical"] = Field(
...,
description="Priority level based on urgency and impact"
)
sentiment: Literal["positive", "neutral", "negative", "angry"] = Field(
...,
description="Customer's emotional tone"
)
confidence_score: float = Field(
...,
description="Confidence in extraction (0.0 to 1.0)",
ge=0.0,
le=1.0
)
customer_name: str = Field(
...,
description="Name of the customer",
min_length=1
)
customer_email: str | None = Field(
None,
description="Customer email if mentioned"
)
product_mentioned: List[str] = Field(
default_factory=list,
description="Products or features mentioned"
)
urgency_indicators: List[str] = Field(
default_factory=list,
description="Words or phrases indicating urgency"
)
error_codes: List[str] = Field(
default_factory=list,
description="Any error codes mentioned"
)
summary: str = Field(
...,
description="Brief summary in 2-3 sentences",
max_length=200
)
suggested_actions: List[str] = Field(
...,
description="Recommended action items"
)
estimated_resolution_time: str = Field(
...,
description="Estimated time to resolve"
)
requires_escalation: bool = Field(
...,
description="Whether this ticket requires escalation"
)
ticket_metadata: dict = Field(
default_factory=dict,
description="Additional metadata extracted from the ticket"
)
@field_validator('customer_email')
@classmethod
def validate_email(cls, v):
if v and not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', v):
raise ValueError('Invalid email format')
return v
@field_validator('suggested_actions')
@classmethod
def validate_actions(cls, v):
if len(v) == 0:
raise ValueError('At least one suggested action is required')
return v
# Initialize LLM with Pydantic schema
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm_pydantic = llm.with_structured_output(SupportTicketPydantic)
# Test with multiple tickets
tickets = [
"""
Subject: URGENT - Payment Processing Failed!!!
Hi, my name is Sarah Johnson and I'm extremely frustrated.
I've been trying to process a payment for our Enterprise plan
for the last 3 hours and keep getting error code 502.
This is blocking our entire team from accessing the analytics
dashboard. We have a board meeting tomorrow and need this
fixed IMMEDIATELY! The API endpoint /v2/payments keeps timing out.
""",
"""
Subject: Feature Request - Dark Mode
Hello! I love your product. My name is Mike Chen ([email protected]).
Would it be possible to add a dark mode to the dashboard?
Many of our team members work late hours and would appreciate
this feature. No rush, just a suggestion for future updates.
""",
"""
Subject: Billing Question
Hi, this is Lisa Park. I noticed I was charged twice for my
subscription this month. Can you help me understand why?
My account number is ACC-12345. Thanks!
"""
]
print("\n=== Pydantic Results ===")
for i, ticket in enumerate(tickets, 1):
print(f"\n--- Ticket {i} ---")
result = structured_llm_pydantic.invoke(ticket)
# Access as Pydantic model
print(f"Category: {result.category}")
print(f"Priority: {result.priority}")
print(f"Sentiment: {result.sentiment}")
print(f"Customer: {result.customer_name}")
print(f"Email: {result.customer_email}")
print(f"Confidence: {result.confidence_score}")
print(f"Error Codes: {result.error_codes}")
print(f"Summary: {result.summary}")
print(f"Actions: {', '.join(result.suggested_actions)}")
print(f"Escalation Needed: {result.requires_escalation}")
print(f"Resolution Time: {result.estimated_resolution_time}")
# Convert to dict if needed
# ticket_dict = result.model_dump()
# Validate and access fields with dot notation
assert result.confidence_score >= 0.0
assert len(result.suggested_actions) > 0
Join the conversation! Your thoughts help the community grow.