LLM Structured Output with Pydantic in Python
November 1, 2026 · 2 min read
Describe the data you want as a Pydantic model, ask the LLM for JSON that fits it, then validate the reply with model_validate_json. You get a typed object or a clear error, never a half-right dict.
Extract an order from an email
The model below describes an order found in a customer email. The script checks two replies, one good and one bad. The replies are hardcoded, so you can run it without an API key.
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class Order(BaseModel):
order_id: str = Field(pattern=r"^KC-\d{5}$")
customer: str | None
items: list[str]
status: Literal["damaged", "late", "other"]
good = '{"order_id": "KC-10482", "customer": "Dana", "items": ["blue mug"], "status": "damaged"}'
order = Order.model_validate_json(good)
print(order)
print(order.model_dump())
bad = '{"order_id": "10482", "customer": "Dana", "items": ["blue mug"], "status": "broken"}'
try:
Order.model_validate_json(bad)
except ValidationError as err:
for e in err.errors():
print(e["loc"], e["msg"])
Output:
order_id='KC-10482' customer='Dana' items=['blue mug'] status='damaged'
{'order_id': 'KC-10482', 'customer': 'Dana', 'items': ['blue mug'], 'status': 'damaged'}
('order_id',) String should match pattern '^KC-\d{5}$'
('status',) Input should be 'damaged', 'late' or 'other'How it works
Literal[...]limits a field to a fixed set of values.str | Noneallowsnull, but the field must still be present.Field(pattern=...)requires the value to match a regular expression.model_validate_jsonparses the text and checks every field. It raisesValidationErrorlisting all problems at once.
In a real app, the string comes from the LLM. With the Anthropic SDK, pass the class as output_format to client.messages.parse(...) and read message.parsed_output. The SDK builds the JSON schema from your class, sends it, and validates the reply for you. Without structured output support, put Order.model_json_schema() in the prompt and validate the text yourself. Feed the error text back to the model and retry a few times if it fails.
Common mistake
Using json.loads and trusting the dict. A missing key or an unexpected status only crashes later, far from the cause. Validate right away:
order = Order.model_validate_json(reply_text) # not json.loads(reply_text)
Another mistake is giving a field a default so it looks optional, such as customer: str | None = None. Some strict structured output modes need every field required, so use str | None with no default.
Takeaway
A Pydantic model is your schema, your validator and your typed result in one class. Validate every LLM reply with it before your code uses the data.