One label is useful — two related fields at once is useful-er. You want urgency and a short summary from one call. How would you force the agent to fill both?
Two separate calls, one for urgency and one for summary? That feels wasteful if both come from the same context.
One call, a Pydantic model with both fields. result_type=Observation tells PydanticAI to ask the model for a JSON object with those exact keys, validated on the way back:
from pydantic import BaseModel
class Observation(BaseModel):
urgency: str
summary: str
agent = Agent(model, result_type=Observation)
result = agent.run_sync("Server CPU spiked to 95 percent")And result.output is an Observation instance — I read .urgency and .summary as attributes, not string-search them out of a blob?
Attributes, not parsing. PydanticAI validates the shape before returning, so .urgency is always a string and so is .summary. The full observer:
def agent_observe(situation: str) -> dict:
agent = Agent(model, result_type=Observation)
result = agent.run_sync(situation)
return result.output.model_dump()Why .model_dump() at the end instead of returning the Observation instance directly?
The signature promises a dict. .model_dump() turns the Pydantic instance into a plain Python dict — easy to JSON-serialize, easy to store, easy to pass to downstream code that does not know the Observation class exists.
So one call fills a typed observation — urgency, summary, anything else I add to the model next week?
Add a field to the class and the agent fills it. One place to change the schema, one place the model reads it from. That is the whole win of typed output.
TL;DR: a Pydantic BaseModel as result_type fills every field in one agent call.
class Observation(BaseModel) — declares the fields and typesAgent(model, result_type=Observation) — forces the shape.model_dump() — converts the Pydantic instance to a plain dict| Approach | Calls | Tradeoff |
|---|---|---|
| Two scalar agents | 2 | Double the latency |
| One typed observer | 1 | Tight schema, single round-trip |
Adding a field costs one line of class definition, not another network round-trip.
One label is useful — two related fields at once is useful-er. You want urgency and a short summary from one call. How would you force the agent to fill both?
Two separate calls, one for urgency and one for summary? That feels wasteful if both come from the same context.
One call, a Pydantic model with both fields. result_type=Observation tells PydanticAI to ask the model for a JSON object with those exact keys, validated on the way back:
from pydantic import BaseModel
class Observation(BaseModel):
urgency: str
summary: str
agent = Agent(model, result_type=Observation)
result = agent.run_sync("Server CPU spiked to 95 percent")And result.output is an Observation instance — I read .urgency and .summary as attributes, not string-search them out of a blob?
Attributes, not parsing. PydanticAI validates the shape before returning, so .urgency is always a string and so is .summary. The full observer:
def agent_observe(situation: str) -> dict:
agent = Agent(model, result_type=Observation)
result = agent.run_sync(situation)
return result.output.model_dump()Why .model_dump() at the end instead of returning the Observation instance directly?
The signature promises a dict. .model_dump() turns the Pydantic instance into a plain Python dict — easy to JSON-serialize, easy to store, easy to pass to downstream code that does not know the Observation class exists.
So one call fills a typed observation — urgency, summary, anything else I add to the model next week?
Add a field to the class and the agent fills it. One place to change the schema, one place the model reads it from. That is the whole win of typed output.
TL;DR: a Pydantic BaseModel as result_type fills every field in one agent call.
class Observation(BaseModel) — declares the fields and typesAgent(model, result_type=Observation) — forces the shape.model_dump() — converts the Pydantic instance to a plain dict| Approach | Calls | Tradeoff |
|---|---|---|
| Two scalar agents | 2 | Double the latency |
| One typed observer | 1 | Tight schema, single round-trip |
Adding a field costs one line of class definition, not another network round-trip.
Create a free account to get started. Paid plans unlock all tracks.