Structured Output
Making models talk to code.
A model replies in sentences. But your code can't use a sentence. If Juno writes, this looks like it might be urgent, what does your program do with that? For an agent, a model's reply must be data that a program can read. That's what structured output does.

- Models reply in sentences
- Code needs data
- Structured output fixes that
Triage with a schema
We describe exactly what we want, using pydantic. A kind, from a fixed list. An order ID, which might be missing. And an urgent flag. We pass that schema to the model. Run it. That's a real Python object, not text. Our code can now branch on it. This one idea turns a chat model into a software component.
from enum import Enum
from pydantic import BaseModel
from google import genai
from google.genai import types
class Kind(str, Enum):
tracking = "tracking"
refund = "refund"
product = "product"
other = "other"
class Triage(BaseModel):
kind: Kind
order_id: str | None
urgent: bool
client = genai.Client()
reply = client.models.generate_content(
model="gemini-flash-latest",
config=types.GenerateContentConfig(response_mime_type="application/json",
response_schema=Triage),
contents="Hi, order C09 hasn't come and my race is tomorrow!!")
ticket = reply.parsed
print(ticket)
print("Urgent?", ticket.urgent, "| Kind:", ticket.kind.value)kind=<Kind.tracking: 'tracking'> order_id='C09' urgent=True Urgent? True | Kind: tracking
Validate everything
Never trust model output, even with a schema. Pydantic can check real rules. An order ID must match a pattern. An amount must be positive, and under a limit. Here's a good reply and a bad one. Run it. The good one passes. The bad one gets rejected before it can touch anything. This check sits between the model and your systems, and it costs nothing.
from pydantic import BaseModel, Field, ValidationError
class Refund(BaseModel):
order_id: str = Field(pattern=r"^[A-Z]\d{2}$")
amount: float = Field(gt=0, le=1000)
good = '{"order_id": "A17", "amount": 40}'
bad = '{"order_id": "banana", "amount": -5}'
print(Refund.model_validate_json(good))
try:
Refund.model_validate_json(bad)
except ValidationError as e:
print(f"Rejected with {e.error_count()} errors")order_id='A17' amount=40.0 Rejected with 2 errors
Retry with feedback
When output is invalid, don't just crash. Tell the model what went wrong, and ask again. We've got a stand-in model here, which fails twice. On each failure, we add the error to the prompt. Run it. It fails, it fails again, then it succeeds. Models are very good at fixing mistakes, once you explain them. That retry pattern will show up everywhere.
from pydantic import BaseModel, ValidationError
class Answer(BaseModel):
days: int
replies = iter(['about three', '{"days": "soon"}', '{"days": 3}'])
def ask_model(prompt):
return next(replies)
prompt = "How many days does shipping take? Reply as JSON."
for attempt in range(1, 4):
text = ask_model(prompt)
try:
print("Valid on attempt", attempt, Answer.model_validate_json(text))
break
except ValidationError as e:
print("Attempt", attempt, "failed")
prompt += f"\nYour last reply was invalid: {e.errors()[0]['msg']}"Attempt 1 failed Attempt 2 failed Valid on attempt 3 days=3
Summary
Schemas turn words into data, validation protects you, and retries fix mistakes. Next, the hidden budget behind every agent. Tokens.
Key takeaways- Describe output with schemas
- Validate everything
- Retry with the error message