Structured output & validation
In one line
Make the model return clean data (JSON or a typed object) you can use straight away, then check it is valid.
Key idea
Set response_format on create_agent. The clean data lands in
result["structured_response"]. No parsing of free text.
You define the shape with a schema. Options:
- Pydantic model — returns a validated object. Best for real validation.
- dataclass / TypedDict / JSON schema — returns a dict.
Two strategies
| Strategy | How | When |
|---|---|---|
ProviderStrategy | Uses the provider's native structured output. | Most reliable. Works with OpenAI, Anthropic (Claude), xAI, Gemini. |
ToolStrategy | Uses tool calling to force the shape. | Any model that supports tool calling (most models). |
If you pass a schema type directly, LangChain auto-picks: ProviderStrategy when the
model supports it natively, else ToolStrategy.
Tool-calling models (ToolStrategy)
This is the path for any model that can call tools. Field rules like ge, le,
and Literal are what get validated.
from pydantic import BaseModel, Field
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ProductReview(BaseModel):
"""Analysis of a product review."""
rating: int | None = Field(description="1-5", ge=1, le=5)
sentiment: Literal["positive", "negative"]
key_points: list[str]
agent = create_agent(model="claude-sonnet-4-6", tools=[], response_format=ToolStrategy(ProductReview))
result = agent.invoke({"messages": [{"role":"user","content":"Analyze: Great, 5/5, fast shipping but pricey"}]})
print(result["structured_response"])
# ProductReview(rating=5, sentiment='positive', key_points=['fast shipping','expensive'])
Validating model outputs (auto-retry)
Models slip up. With ToolStrategy, a bad value fails Pydantic validation, the agent
feeds the exact error back, and the model tries again. Example: the rule is 1-5, the model
returns 10, it gets corrected to 5. You control this with handle_errors:
| handle_errors | Effect |
|---|---|
True (default) | Catch all errors, retry with a default message. |
"your message" | Retry with your fixed message. |
ValueError / tuple | Retry only on these errors; raise the rest. |
| a function | Return a custom message per error type. |
False | No retry. Raise the error. |
ToolStrategy(schema=ProductReview, handle_errors="Give a rating from 1 to 5 and a short comment.")
ge, le, Literal, required fields). That is your validation. handle_errors decides what happens when a rule is broken.Validating tool calls & tool outputs
Two layers:
- Tool inputs — the tool's function signature (and any Pydantic types) validates the args the model passes. Bad args get rejected before your code runs.
- Tool errors — wrap tool calls so a failure becomes a message the model can recover from, instead of crashing the agent.
from collections.abc import Callable
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest
@wrap_tool_call
def handle_tool_errors(request: ToolCallRequest, handler: Callable[[ToolCallRequest], ToolMessage]) -> ToolMessage:
try:
return handler(request)
except Exception as e:
return ToolMessage(content=f"Tool error, try again. ({e})", tool_call_id=request.tool_call["id"])
agent = create_agent(model="claude-sonnet-4-6", tools=[...], middleware=[handle_tool_errors])
Validating a raw model (no agent)
Outside an agent, bind a schema straight to a model:
model_with_structure = model.with_structured_output(ProductReview)
review = model_with_structure.invoke("...") # validated object
Watch out
- With tools and structured output, the model must support both at once.
ProviderStrategyis stricter and more reliable when your provider supports it.- Pydantic gives real validation; TypedDict/JSON return plain dicts with lighter checking.
Links
- Structured output (docs)
- Models — structured output (docs)
- Tools & error handling (docs)
- Tools · Models
My notes
Add your own findings here as you learn.