LangChain with_structured_output — Pydantic v1/v2, JSON mode vs tool_call errors (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LangChain structured_output errors
LangChain Structured Output Severity: Medium HTTP n/a

LangChain with_structured_output() — schema validation and method errors

<code>with_structured_output()</code> is the cleanest way to get typed responses from any chat model. It succeeds when configured correctly and fails in subtle ways when not — usually around Pydantic version, method choice, or provider-specific schema constraints.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: llm.with_structured_output(Schema) returns a runnable that parses model output into your schema. Fix errors by (a) using Pydantic v2 BaseModel (v1 is deprecated in langchain-core 0.3+), (b) picking the right method for the provider (function_calling, json_mode, or json_schema), (c) avoiding features the provider rejects (deeply nested unions, self-referential types), and (d) setting include_raw=True when debugging validation errors.

Real error messages you'll see

These are the exact strings returned by the LangChain framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.

Pydantic version conflict
PydanticSchemaGenerationError: Unable to generate pydantic-core schema for <class 'MyModel'>. Set arbitrary_types_allowed=True in the model_config, or use a different type.
Method not supported by provider
ValueError: method="json_schema" is not supported for gpt-3.5-turbo. Use "function_calling" or "json_mode".
Schema too complex
openai.BadRequestError: 400 - The maximum depth of nested $ref is 5, but got 7.

Reference

Method options for with_structured_output

MethodProvider supportNotes
function_callingOpenAI, Anthropic, Google, BedrockUses tool_use; most reliable
json_modeOpenAI, some othersForces JSON but no schema enforcement
json_schemaOpenAI (Structured Outputs)Strongest schema guarantee

Provider quirks for structured output

ProviderRecommended methodNotes
OpenAI (4o+, o1+)json_schemaStrongest guarantees; strict mode enabled
OpenAI (older)function_callingjson_schema not available
Anthropic Claudefunction_callingUses tool_use blocks
Google Geminifunction_callingReliable
Ollama / open-sourcejson_modeSometimes brittle; add validation retry

Root causes, ranked by frequency

Based on developer reports across LangChain forums, GitHub issues, and Discord community during 2025–2026.

  • 25%
    Pydantic v1 model with v2 langchain-core. langchain-core 0.3+ requires Pydantic v2 exclusively.
  • 18%
    Method not supported. Passing method="json_schema" to a provider that does not support it.
  • 14%
    Complex nested schema rejected. Deep nesting, self-references, or complex Unions.
  • 10%
    Field description missing. Field without Field(description="...") — model outputs empty/wrong values.
  • 8%
    Streaming with structured output. method="function_calling" does not stream well; users see nothing until complete.
  • 7%
    Optional field on strict provider. OpenAI strict Structured Outputs rejects Optional.
  • 8%
    Non-Pydantic schema (raw dict). Passing an OpenAPI-style dict; some validation edge cases.
  • 10%
    Retry loop on validation failure. Model consistently produces almost-valid JSON; parser retries and fails.

Fixes — copy-paste solutions

Fix #1

Use Pydantic v2 with clear field descriptions

Every field needs a description — that is what the model reads.

Define your schema as a Pydantic v2 BaseModel. Every field must have Field(description="..."). Pass the class to with_structured_output.

structured_output_basic.py
from typing import List, Literal
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class ExtractedInfo(BaseModel):
    """Information extracted from the input text."""
    name: str = Field(description="The person's full name")
    role: str = Field(description="Their job title or role")
    company: str = Field(description="The company they work at")
    years_experience: int = Field(
        default=0,
        description="Years of experience mentioned; 0 if not mentioned"
    )
    skills: List[str] = Field(
        default_factory=list,
        description="Technical skills mentioned"
    )
    confidence: Literal["low", "medium", "high"] = Field(
        default="medium",
        description="Your confidence in the extracted information"
    )

llm = ChatOpenAI(model="gpt-4o-mini")

# For OpenAI 4o+, json_schema is best (strict mode)
structured_llm = llm.with_structured_output(ExtractedInfo, method="json_schema", strict=True)

result = structured_llm.invoke(
    "Alice Johnson has been a Senior ML Engineer at TechCorp for 5 years. "
    "She specializes in NLP and computer vision."
)
print(result)
# ExtractedInfo(name='Alice Johnson', role='Senior ML Engineer',
#               company='TechCorp', years_experience=5,
#               skills=['NLP', 'computer vision'], confidence='high')

# Access as an object
print(result.name)
print(result.skills)
Field(description=...) is not decorative — it is the field-level documentation the model uses to know what to extract. Sparse descriptions produce empty or wrong values.
Fix #2

Pick the right method per provider

One line changes behaviour dramatically — get it right.

Different providers support different structured-output methods with different guarantees. Choose the strongest one your provider supports.

method_by_provider.py
from pydantic import BaseModel, Field
from typing import List

class Recipe(BaseModel):
    name: str = Field(description="Recipe name")
    ingredients: List[str] = Field(description="Ingredient list")
    steps: List[str] = Field(description="Cooking steps in order")

# --- OpenAI 4o+ / o1: json_schema with strict is strongest ---
from langchain_openai import ChatOpenAI
openai_llm = ChatOpenAI(model="gpt-4o-mini").with_structured_output(
    Recipe, method="json_schema", strict=True
)

# --- Anthropic Claude: function_calling ---
from langchain_anthropic import ChatAnthropic
claude_llm = ChatAnthropic(model="claude-sonnet-5").with_structured_output(
    Recipe, method="function_calling"
)

# --- Google Gemini: function_calling ---
from langchain_google_genai import ChatGoogleGenerativeAI
gemini_llm = ChatGoogleGenerativeAI(model="gemini-2.5-pro").with_structured_output(
    Recipe, method="function_calling"
)

# --- Ollama / open-source: json_mode (weakest but broadest) ---
from langchain_ollama import ChatOllama
ollama_llm = ChatOllama(model="llama3.1:8b").with_structured_output(
    Recipe, method="json_mode"
)

# All four accept the same invoke and return a Recipe object
for name, llm in [("openai", openai_llm), ("claude", claude_llm),
                  ("gemini", gemini_llm), ("ollama", ollama_llm)]:
    try:
        recipe = llm.invoke("How to make pancakes?")
        print(f"{name}: {recipe.name}{len(recipe.ingredients)} ingredients")
    except Exception as e:
        print(f"{name}: ERROR {e}")

# For debugging: include_raw returns both the parsed model AND the raw AIMessage
structured_llm_debug = openai_llm.with_structured_output(
    Recipe, method="json_schema", include_raw=True
)
result = structured_llm_debug.invoke("...")
# result = {"raw": AIMessage(...), "parsed": Recipe(...), "parsing_error": None or Exception}
For OpenAI, method="json_schema" with strict=True gives the strongest guarantee — the model literally cannot emit invalid JSON. For every other provider, function_calling is the sensible default.
Fix #3

Flatten complex schemas for cross-provider compatibility

Deep nesting and Unions fail on strict providers.

When the schema is complex, flatten it: replace nested Unions with a discriminated type field, cap nesting depth, and inline small nested models. Use include_raw=True to inspect parse failures.

flatten_complex_schema.py
# ❌ Too complex — deep nesting and Union
# from typing import Union
# class Address(BaseModel): ...
# class Person(BaseModel):
#     name: str
#     contact: Union[EmailContact, PhoneContact, PostalContact]
#     history: List[Union[Employment, Education, Certification]]

# ✓ Flatter — discriminated types
from typing import List, Literal, Optional
from pydantic import BaseModel, Field

class ContactMethod(BaseModel):
    kind: Literal["email", "phone", "postal"] = Field(description="Contact type")
    value: str = Field(description="Contact value as a string")

class HistoryItem(BaseModel):
    kind: Literal["employment", "education", "certification"]
    title: str = Field(description="Position, degree, or certification name")
    organization: str = Field(description="Company, school, or issuer")
    start_year: int
    end_year: int = Field(description="0 if ongoing")

class Person(BaseModel):
    name: str = Field(description="Full name")
    contacts: List[ContactMethod] = Field(default_factory=list)
    history: List[HistoryItem] = Field(default_factory=list)

# For debugging schemas, print the JSON schema
import json
print(json.dumps(Person.model_json_schema(), indent=2))
# Verify: no circular refs, no unions the provider rejects, all fields have descriptions

# For validation failures, use include_raw
structured_llm_debug = ChatOpenAI(model="gpt-4o-mini").with_structured_output(
    Person, method="json_schema", strict=True, include_raw=True
)

result = structured_llm_debug.invoke("Some text about a person...")

if result["parsing_error"]:
    print(f"Parse error: {result['parsing_error']}")
    print(f"Raw output was: {result['raw'].content}")
else:
    person = result["parsed"]
Discriminated unions with a Literal kind field are portable, human-readable, and every strict provider accepts them. When in doubt, use this pattern instead of full Union types.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Use Pydantic v2 exclusively; verify with import pydantic; pydantic.VERSION.
  • Every field needs Field(description="..."). Sparse descriptions cause bad extraction.
  • For OpenAI 4o+/o1: method="json_schema" with strict=True.
  • For other providers: method="function_calling".
  • Avoid deep nested Union types — use discriminated unions with a Literal kind field.
  • For debugging, add include_raw=True and inspect both parsed result and raw AIMessage.
  • Test structured output in CI against every provider you support.

Frequently asked questions

json_mode forces the output to be valid JSON but does not enforce a specific schema. json_schema enforces the exact schema you provide; the model literally cannot emit invalid data. Prefer json_schema when supported.
Partially — with method="json_mode" and JsonOutputParser you get progressive partial dicts. With function_calling or json_schema you typically get one final structured value. See our streaming page for details.
Improve that field's description. Add examples in the description or the surrounding prompt. If still failing, add a Pydantic validator that normalizes common variants.
Not natively in the same call — with_structured_output is typically the terminal step. For "agent that returns structured final output", use LangGraph or a two-step chain: agent → structured extractor.
Similar idea, LangChain-native. instructor and outlines are provider-specific libraries; with_structured_output works across every LangChain-supported chat model with one API. Trade-off: instructor sometimes has more advanced features (retries, backup models).

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.