If you have ever written json.loads(response) around an LLM call and then a defensive try/except because the model returned
```json
fences, a trailing comma, or prose before the object — this is for you.
The fix is to stop treating the model's output as text you parse, and start treating it as a typed object the library validates for you. With pydantic-ai you declare the shape once and get a validated Python object back, with a retry on the model when it doesn't conform.
from pydantic import BaseModel, Field
from pydantic_ai import Agent
class Invoice(BaseModel):
vendor: str
total: float = Field(..., description='Grand total in USD')
due_date: str | None = None
agent = Agent('anthropic:claude-sonnet-4-6', output_type=Invoice,
system_prompt='Extract the invoice fields from the text.')
result = agent.run_sync('Acme Corp — $1,240.00 due 2026-07-01')
print(result.output.total) # 1240.0 (a float, already validated)
What you no longer write: the JSON fence stripping, the KeyError guards, the float coercion, the "the model added an apology before the JSON" handling. If the model returns something that doesn't fit Invoice, pydantic-ai sends the validation error back to the model and asks it to try again — so your application code only ever sees a clean object.
Three things this buys you in production:
- The type is the contract. Your endpoint signature, your tests, and your prompt all agree, because they reference the same model.
-
Failures are explicit. A field that won't validate raises where you can catch it, instead of silently becoming
Nonethree functions later. -
It's auditable.
result.outputis a real object you can log, diff, and assert on.
The pattern scales from one field to a nested schema, and it's the same whether you're on Claude, GPT, or a local model. Once you've used it you stop writing parsers entirely.
I package patterns like this as small open-source pydantic-ai + FastAPI templates — the repos are on GitHub, and complete, ready-to-run versions are on Gumroad. Feedback and issues welcome.
Top comments (0)