Sameer Singh

If you have been building with LangChain for a while, you already know the basics. You write a prompt, send it to a language model, and get a response back. It feels like magic the first time.
But here is a question that separates hobbyists from developers building production AI systems:
What happens AFTER you get the response?
Most tutorials stop at the response. They show you how to craft prompts, how to chain them together, how to use retrieval. But the moment you try to build something real, whether that is a resume parser, a review analysis API, or an AI agent that calls tools, you run into the same wall:
Plain text responses are nearly useless for automated systems.
This is the problem that structured output solves. And in this guide, you are going to understand it completely. Not just the "how" but the "why" and "when," backed by real-world use cases and three different implementation approaches in LangChain.
By the end of this article, you will know:
with_structured_output() functionLet us get into it.
Before diving in, let us situate structured output within the broader LangChain learning path.
When you first learn LangChain, you focus on prompts. How do you send the right input to the model? How do you use prompt templates, few-shot examples, and chat history?
Once you get comfortable with input, the next frontier is output. And this is where structured output becomes essential.
Think of it this way: prompts control what you ask, structured output controls what you receive and in what shape. Together, they give you complete control over the input-output pipeline of your AI application.
Let us start with the baseline. When you send a plain prompt to a language model, you get plain text back.
Take this example:
Prompt: What is the capital of India?
Output: New Delhi is the capital of India. It is one of the oldest cities in the world and serves as the political hub of the country.
That is a perfectly fine answer for a human. You read it, you understand it, you move on.
Now imagine you are building an application where:
Suddenly, "New Delhi is the capital of India. It is one of the oldest cities in the world..." is a problem. You have to extract the city name from the sentence. You might write a regex. Or you might manually slice the string. Either way, it is brittle, error-prone, and does not scale.
This is the fundamental limitation of unstructured output: it is written for humans, not machines.
Computers and automated systems work best with structured data formats. The most common ones are:
JSON (JavaScript Object Notation): Widely used for APIs and web services. Looks like {"capital": "New Delhi"}.
Dictionaries: Native Python data structures for storing key-value pairs.
Objects: Instances of classes with defined attributes, common in object-oriented programming.
The moment you have data in one of these formats, you can parse it, validate it, store it, transform it, and pass it between systems without any guesswork.
Structured output is the practice of forcing a language model to return its response in a predefined data format, rather than free-form text.
You define the structure in advance. You tell the model: "Your response must look exactly like this. It must have these fields, these data types, and nothing else."
The model then produces output that matches your specification.
Here is the same capital city query, but with structured output:
{
"country": "India",
"capital": "New Delhi"
}That JSON object is immediately usable by any programming language, any database, any API. No parsing, no guessing, no fragile string manipulation.
This is the fundamental shift that structured output enables. Without it, LLMs produce responses optimized for human readers. With it, they produce responses optimized for automated systems.
This is not a small improvement. It is the difference between building a demo and building a product.
Let us go deeper into why this matters, with concrete real-world scenarios.
When your LLM extracts information, that information needs to go somewhere. Databases do not accept paragraphs of text as input. They expect specific values in specific columns.
If you are building a resume parser, you need the model to extract the candidate's name, years of experience, education history, and skills. Not as a paragraph, but as discrete, labeled data points that map directly to your database schema.
With structured output, the model produces exactly that. The extracted data slots directly into your database without a human in the loop.
Modern applications are built from many services communicating over APIs. When your LLM is one service in that ecosystem, it needs to speak the same language as the others.
An API endpoint does not accept a conversational response. It expects a JSON body with specific fields. Structured output ensures the LLM always produces a response that matches the expected API contract.
In automated pipelines, each step depends on the output of the previous step. If the output is unpredictable plain text, the next step in your pipeline has to deal with that unpredictability.
Structured output eliminates this problem. Each step in the pipeline knows exactly what format it will receive, making your entire automation more reliable and easier to debug.
This is where structured output goes from useful to absolutely essential.
AI agents work by choosing and calling tools. A tool might be a calculator, a web search function, a database query, or an external API. These tools are just functions in your code, and functions require specific inputs.
Imagine a user says: "What is the square root of 144?"
The agent needs to:
That third step, calling the calculator, requires structured data. The function signature might look like calculate(operation: str, number: float). The agent cannot pass "What is the square root of 144?" as input. It needs structured output to extract and pass only the relevant values.
This is why every serious discussion of AI agents eventually circles back to structured output. Agents cannot function without it.
Let us look at three detailed real-world applications where structured output is the backbone of the system.
The Problem: A recruiting firm receives thousands of resumes. Manually entering candidate data into their database is slow and error-prone.
The Solution: An LLM-powered resume parser.
The Workflow:
Why Structured Output Is Critical: Without it, you get a paragraph summary of the candidate. With it, you get a perfectly formatted record ready for database insertion. The difference is the difference between a tool that works and a tool that is useful.
The Problem: An e-commerce platform wants to analyze customer reviews at scale and make that analysis available to their product teams through an internal API.
The Solution: A review analysis service powered by an LLM with structured output.
The Workflow:
Why Structured Output Is Critical: An API response must be predictable. Every caller expects the same schema, every time. Structured output guarantees that consistency.
The Problem: A company wants an AI assistant that can access multiple internal tools: a calendar, a project management system, a data analytics dashboard, and an email system.
The Solution: An AI agent that routes requests to the right tools.
The Workflow:
Why Structured Output Is Critical: Each tool call is a function invocation. Functions need exact parameters. Structured output is what bridges natural language intent and programmatic execution.
Now let us get practical. Here is how you actually implement structured output in LangChain.
First, an important distinction. Not all models handle structured output the same way.
Type 1: Models With Native Structured Output Support
Modern frontier models like GPT-4, Claude, and similar can directly produce structured output when configured properly. LangChain provides a clean API for this.
Type 2: Models Without Native Structured Output Support
Some smaller or older open-source models cannot reliably produce structured output on their own. For these, LangChain provides Output Parsers, which take the plain text response and convert it into structured data after the fact. Output parsers are a separate topic, but worth knowing about when working with models that have limited capabilities.
For models that support structured output natively, LangChain makes the implementation elegantly simple.
The normal invocation pattern looks like this:
response = model.invoke(prompt)
With structured output, you add one step:
structured_model = model.with_structured_output(schema)
response = structured_model.invoke(prompt)That is it. You define a schema, wrap the model with it, and every response from that model will conform to your schema.
The critical piece is defining that schema. LangChain gives you three ways to do it.
TypedDict is a Python standard library tool that lets you define typed dictionaries. It tells the model what keys to include and what types those values should be.
from typing import TypedDict
class TravelRecommendation(TypedDict):
destination: str
best_time_to_visit: str
estimated_budget_usd: int
activities: list[str]
structured_model = model.with_structured_output(TravelRecommendation)
result = structured_model.invoke("Suggest a travel destination for a budget traveler in December.")The model will return a dictionary matching this structure:
{
"destination": "Lisbon, Portugal",
"best_time_to_visit": "December to February",
"estimated_budget_usd": 800,
"activities": ["Explore Belem Tower", "Visit Sintra", "Take tram 28"]
}TypedDict is most useful in these situations:
TypedDict does not enforce validation at runtime. If the model returns an integer where you expected a string, TypedDict will not raise an error. It gives you hints and documentation, not guarantees.
For applications where data integrity matters, you need the next method.
Pydantic is a Python library built for data validation and parsing. It takes the concept of TypedDict and adds a full enforcement layer on top.
With Pydantic, if the model returns the wrong type, you get an error. If a required field is missing, you get an error. If a value violates a constraint, you get an error.
This makes Pydantic the go-to choice for production applications.
from pydantic import BaseModel, Field
from typing import Optional
class StudentProfile(BaseModel):
name: str
age: int
email: str
cgpa: float = Field(ge=0.0, le=10.0, description="CGPA must be between 0 and 10")
major: str
graduation_year: Optional[int] = None
structured_model = model.with_structured_output(StudentProfile)
result = structured_model.invoke("Extract student details from this text: John Doe, 22 years old, studying Computer Science with a 8.7 CGPA, graduating in 2025.")Here is a breakdown of what Pydantic can enforce that TypedDict cannot:
Type Enforcement: If a field is declared as str and the model returns 123, Pydantic raises a ValidationError. No silent failures.
Automatic Type Conversion: Pydantic is also smart. If a field is declared as int and the model returns "42" (a string), Pydantic can convert it automatically. This handles the natural variability in LLM outputs.
Default Values: You can specify what value a field gets if the model does not include it.
Optional Fields: Not every field needs to be present in every response. Pydantic handles optional fields cleanly.
Constraints with Field(): This is where Pydantic shines for LLM use cases.
from pydantic import BaseModel, Field
class ProductReview(BaseModel):
product_name: str
rating: float = Field(ge=1.0, le=5.0, description="Rating must be between 1 and 5")
review_summary: str = Field(max_length=200)
sentiment: str = Field(pattern="^(positive|negative|neutral)$")
would_recommend: boolIn this example:
rating must be a float between 1.0 and 5.0review_summary cannot exceed 200 characterssentiment must be exactly one of three valueswould_recommend must be a booleanIf the LLM returns anything outside these constraints, Pydantic catches it before the bad data reaches your system.
Field Descriptions: Adding descriptions to fields is a best practice with LangChain and Pydantic. The descriptions are passed to the model as guidance, improving the quality of its structured output.
class JobApplication(BaseModel):
applicant_name: str = Field(description="Full name of the applicant")
years_experience: int = Field(ge=0, description="Total years of professional experience")
skills: list[str] = Field(description="List of technical and professional skills")
cover_letter_summary: str = Field(max_length=500, description="Brief summary of the cover letter's main points")The combination of strict validation, automatic type conversion, constraint enforcement, and clear field descriptions makes Pydantic the preferred choice for:
If you are building anything beyond a prototype, Pydantic is almost certainly the right choice.
JSON Schema is a specification for describing the structure of JSON data. Unlike TypedDict and Pydantic, which are Python-specific, JSON Schema is language-agnostic. It works in JavaScript, Go, Java, Python, Ruby, and virtually every other language.
review_schema = {
"title": "ProductReview",
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "Name of the reviewed product"
},
"rating": {
"type": "number",
"minimum": 1,
"maximum": 5,
"description": "Rating from 1 to 5"
},
"pros": {
"type": "array",
"items": {"type": "string"},
"description": "List of positive points"
},
"cons": {
"type": "array",
"items": {"type": "string"},
"description": "List of negative points"
},
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"]
}
},
"required": ["product_name", "rating", "sentiment"]
}
structured_model = model.with_structured_output(review_schema)
result = structured_model.invoke("Analyze this review: The laptop is fast and well-built but the battery life is disappointing and it runs hot.")JSON Schema is the right choice when:
Your system spans multiple languages. If your Python LangChain service feeds data to a JavaScript frontend or a Go microservice, JSON Schema is the common contract everyone understands without translation.
You work with distributed systems or microservices. Each service can validate incoming data against the schema independently, regardless of implementation language.
You collaborate across teams with different tech stacks. A shared JSON Schema file serves as documentation and validation for all teams simultaneously.
You are integrating with existing systems that already use JSON Schema. Many API specification tools like OpenAPI already use JSON Schema, so the format is a natural fit.
With three options available, the decision can feel overwhelming. Here is a straightforward framework:
Choose TypedDict when:
Choose Pydantic when:
Choose JSON Schema when:
In practice, the majority of real-world LangChain projects use Pydantic. The validation layer it provides is simply too valuable to skip when real data and real users are involved.
When you configure structured output in LangChain, there is an underlying mechanism at play. Modern models support two primary approaches.
In JSON mode, you instruct the model to format its entire response as a valid JSON object. The model generates text that conforms to JSON syntax, and LangChain parses it into your schema.
JSON mode is commonly used when:
Function calling is a different approach. Instead of generating JSON as text, the model identifies that it needs to "call a function" and produces structured arguments for that function.
This mode is particularly important for AI agents. When an agent decides to use a tool, it uses function calling to specify the tool and its parameters in a structured way that the tool can directly consume.
Some models support both modes. Others support only one. LangChain abstracts much of this complexity, but understanding the distinction helps you debug issues and make better architectural decisions.
Not every model you encounter will support structured output natively. This is common with smaller open-source models that have been fine-tuned for specific tasks or optimized for limited hardware.
If you call with_structured_output() on such a model, you will get an error.
LangChain's Output Parsers are designed specifically for this situation. They work differently from native structured output:
Common output parsers include:
PydanticOutputParser: Extracts Pydantic model instances from LLM textJsonOutputParser: Extracts JSON from LLM textCommaSeparatedListOutputParser: For simple list extractionStructuredOutputParser: For custom schemas with format instructionsThe trade-off with output parsers is reliability. Native structured output is enforced at the model level. Output parsers work after the fact, which means malformed model responses can cause parsing failures. For production use cases, prefer models with native structured output support where possible.
Let us tie everything together with a complete, practical example. This scenario extracts structured information from unstructured job posting text.
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Optional
# Define the schema with Pydantic
class JobPosting(BaseModel):
job_title: str = Field(description="The title of the position")
company_name: str = Field(description="Name of the hiring company")
location: str = Field(description="Job location or 'Remote' if fully remote")
experience_required_years: Optional[int] = Field(
default=None,
ge=0,
description="Minimum years of experience required"
)
required_skills: list[str] = Field(
description="List of required technical and professional skills"
)
salary_range_usd: Optional[str] = Field(
default=None,
description="Salary range if mentioned, e.g. '$80,000 - $100,000'"
)
employment_type: str = Field(
description="Type of employment: full-time, part-time, contract, or internship"
)
remote_option: bool = Field(
description="True if remote work is offered"
)
# Initialize the model
model = ChatOpenAI(model="gpt-4o-mini")
# Attach structured output
job_analyzer = model.with_structured_output(JobPosting)
# Sample job posting text
job_text = """
We are looking for a Senior Backend Engineer to join our growing team at TechFlow Inc.
This is a full-time remote position based in our San Francisco HQ, with the option to work
from anywhere in the US. You will need at least 5 years of experience with Python,
strong knowledge of PostgreSQL and Redis, experience with Docker and Kubernetes,
and familiarity with AWS services. Knowledge of FastAPI or Django is a plus.
The compensation range for this role is $130,000 to $160,000 annually,
depending on experience and location.
"""
# Invoke the structured model
result = job_analyzer.invoke(job_text)
# Access structured fields directly
print(f"Position: {result.job_title} at {result.company_name}")
print(f"Required Skills: {', '.join(result.required_skills)}")
print(f"Salary: {result.salary_range_usd}")
print(f"Remote: {result.remote_option}")Notice how the output is immediately usable. You can access individual fields with dot notation. You can store them in a database. You can return them through an API. No parsing, no extraction, no guesswork.
TypedDict and basic Pydantic without Field descriptions leave the model to infer what each field means. Adding clear descriptions significantly improves output quality and consistency.
Do this instead:
class Review(BaseModel):
sentiment: str = Field(description="Overall sentiment: must be exactly 'positive', 'negative', or 'neutral'")TypedDict is great for prototyping but lacks the validation that production systems need. If bad data reaches your database because TypedDict did not catch a type mismatch, the debugging cost far outweighs the initial time saved.
Real-world text does not always contain all the information you need. If your schema has required fields that the source text does not contain, your structured output call will fail. Use Optional types with defaults for fields that may not always be present.
Not every model supports structured output. Always verify that the model you are using supports with_structured_output() before building your pipeline around it.
Let us step back and appreciate what structured output really means for the state of AI development.
For years, the challenge with AI was getting it to understand human intent. That problem, while not fully solved, has been largely addressed by modern language models.
The new frontier is integration: making AI systems that do not just understand humans, but that can reliably communicate with the machines, databases, APIs, and automated workflows that make up modern software.
Structured output is the bridge between those two worlds.
Without it, LLMs are powerful but isolated. They can answer questions, generate content, and have conversations. But they cannot reliably participate in automated systems.
With it, LLMs become components in a larger system. They can power data pipelines, drive AI agents, serve API responses, and automate workflows that previously required human intervention.
| Requirement | Recommended Method |
|---|---|
| Fast prototyping | TypedDict |
| Production with validation | Pydantic |
| Cross-language systems | JSON Schema |
| Agent tool calling | Pydantic with Function Calling |
| Model without native support | Output Parsers |
Structured output transforms LLM responses from human-readable text into machine-readable data. And machine-readable data is what makes AI applications production-ready.
Every serious LangChain application you build from here forward will depend on this concept. The sooner you get comfortable with it, the faster you will be able to move from idea to working system.
This article is part of a continuing LangChain tutorial series covering prompts, output, chains, agents, and memory. Each concept builds on the last, so bookmark the series if you want the complete picture.
Master LangChain Runnables from scratch. Learn what they are, why they replaced chains, and how LCEL helps you build flexible AI pipelines with clean, modular code.
Sameer Singh
What if you could find the majority element without counting anything? The Boyer-Moore Voting Algorithm does exactly that, in O(n) time and O(1) space. Here is the full breakdown.
Sign in to join the discussion.
Rahul Kumar