AI Hints
Home›Agentic AI Tutorial›Talking to Code
Agentic AI · Lesson 7 of 100

Structured Output

Making models talk to code.

⏱ 2 min read▶ Video at 12:34🎓 Free
Watch this lesson in the Agentic AI courseWatch on YouTube ↗Full Agentic AI course

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.

Structured Output
From the video: Structured Output
  • 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.

triage.pypython
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)
Output
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.

validate.pypython
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")
Output
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.

retry.pypython
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']}"
Output
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
Next up: Tokens, Context, and Cost — the budget behind every agent