Google ADK Masterclass Part 4: Structured Outputs with ADK
In our previous tutorials, we explored creating agents with different models and adding tools to enhance their capabilities. As you build more complex agent systems, especially those that interact with other applications or APIs, ensuring consistent and predictable output formats becomes crucial.
In this tutorial, we’ll explore how ADK enables structured outputs to ensure your agents return data in exactly the format your applications need.
Why Structured Outputs Matter
Imagine you’re building an email generation agent that needs to return both a subject line and body text. Without structured outputs, you might get:
- Unpredictable formatting
- Missing fields
- Additional unwanted content
- Inconsistent JSON structures
Structured outputs solve these problems by enforcing a specific schema for agent responses. This is especially important when:
- Passing data between agents in multi-agent systems
- Integrating agent responses with other APIs or databases
- Building user interfaces that expect consistent data structures
- Automating workflows where data schema consistency is essential
ADK’s Structured Output Options
ADK provides three main approaches to structured outputs:
- Output Schema: Define a Pydantic model for the expected response structure
- Output Key: Store the agent’s response in a specific state key for access by other agents
- Input Schema: Define expected input structure (less common and more rigid)
Let’s explore each option, with a focus on the most useful ones.
Using Output Schema for Structured Responses
The most powerful way to enforce structured outputs is using output_schema
. This approach uses Pydantic models to define the expected structure and validates the agent’s response against it.
Let’s build an email generation agent that always returns a properly structured response:
Folder Structure
structured_outputs/
└── email_agent/
├── __init__.py
├── .env
└── agent.py
Agent Implementation
from google.adk import Agent
from pydantic import BaseModel
# Define our output schema using a Pydantic model
class EmailContent(BaseModel):
subject: str
body: str
email_agent = Agent(
name="email_agent",
model="models/gemini-2.0-no-flash",
description="An email generation assistant",
instructions="""
You are an email generation assistant. You always write professional emails based on the user's request.
Guidelines for writing emails:
1. Create a concise and relevant subject line
2. Write a professional email body with a greeting, clear message, and appropriate closing
3. Keep the tone business-friendly and formal
4. Be concise but complete
IMPORTANT: Always return your response as a JSON object with the following structure:
{
"subject": "The email subject line",
"body": "The full email body"
}
""",
output_schema=EmailContent,
output_key="email"
)
Let’s break down the key components:
- Pydantic Model: We define
EmailContent
as a Pydantic model with two fields:subject
andbody
- output_schema: We set this to our
EmailContent
model, telling ADK to validate responses against this structure - output_key: We set this to “email”, which will store the structured output in the agent’s state under this key
- Instructions: We explicitly tell the model about the expected structure to improve compliance
How Output Schema Works
When the agent generates a response, ADK:
- Takes the raw text output from the model
- Attempts to parse it as JSON
- Validates it against the Pydantic model
- If valid, stores the structured data in state under the specified output key
- If invalid, returns an error or attempts to fix the output (depending on configuration)
Running the Email Agent
Let’s see this in action:
cd structured_outputs
adk web
Example Interaction
You: Please write an email to my wife Carly to see if she is available for coffee tomorrow morning. Agent:
{
"subject": "Coffee Tomorrow Morning?",
"body": "Hi Carly,\n\nI hope you're having a great day! I was wondering if you might be available to grab coffee together tomorrow morning?\n\nLet me know what time works best for you if you're free.\n\nLooking forward to it!\n\nLove,\n[Your Name]"
}
Notice that the response is now a properly structured JSON object with exactly the fields we specified. No more, no less.
Exploring State with Structured Outputs
One of the most powerful aspects of structured outputs is how they integrate with ADK’s state management. When we use output_key
, the structured output is stored in the agent’s state.
In the web interface, you can inspect the state and see:
{
"email": {
"subject": "Coffee Tomorrow Morning?",
"body": "Hi Carly,\n\nI hope you're having a great day!..."
}
}
This makes it incredibly easy for other agents or components in your system to access and use this structured data.
Advanced Output Schema Techniques
As your applications grow more complex, you may need more sophisticated output schemas. Let’s explore some advanced techniques:
Nested Models
You can create nested structures for complex data:
from pydantic import BaseModel
from typing import List
class Attachment(BaseModel):
filename: str
content_type: str
size_kb: int
class EmailWithAttachments(BaseModel):
subject: str
body: str
priority: str
attachments: List[Attachment]
# Use this model as output_schema
Optional Fields
You can make certain fields optional:
from pydantic import BaseModel
from typing import Optional
class FlexibleEmail(BaseModel):
subject: str
body: str
cc: Optional[str] = None
bcc: Optional[str] = None
priority: Optional[str] = None
Field Descriptions
Adding descriptions to fields improves model understanding:
from pydantic import BaseModel, Field
class EmailWithMetadata(BaseModel):
subject: str = Field(description="A concise, relevant email subject line")
body: str = Field(description="The complete email body text with greeting and closing")
category: str = Field(description="The type of email (e.g., 'business', 'personal', 'follow-up')")
Limitations and Best Practices
When working with structured outputs in ADK, keep these important limitations and best practices in mind:
1. Tool Incompatibility
You cannot use output_schema
with tools or when transferring information to other agents directly. When building multi-agent systems, have one agent do the complex thinking, pass raw results, and let a final agent apply the output schema.
2. Explicit Instructions
Always include explicit instructions about the expected output format. Even with output_schema
defined, the model performs better when it knows what structure to generate:
IMPORTANT: Your response must be a JSON object with fields "subject" and "body".
3. Error Handling
Be prepared to handle cases where the model fails to generate validly structured output. Consider implementing retry logic or fallback mechanisms.
4. Start Simple
Begin with simple schemas and gradually increase complexity as needed. Complex schemas are more likely to cause generation errors.
Creating a Support Ticket System Example
Let’s create a more complex example: a support ticket creation system with structured outputs:
from google.adk import Agent
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class Category(str, Enum):
TECHNICAL = "technical"
BILLING = "billing"
ACCOUNT = "account"
GENERAL = "general"
class SupportTicket(BaseModel):
title: str = Field(description="A concise summary of the issue")
description: str = Field(description="Detailed description of the problem")
priority: Priority = Field(description="The ticket priority level")
category: Category = Field(description="The department this ticket belongs to")
steps_to_reproduce: Optional[List[str]] = Field(
description="Steps to reproduce the issue (for technical problems)",
default=None
)
customer_contact: Optional[str] = Field(
description="Customer's preferred contact method",
default=None
)
ticket_agent = Agent(
name="ticket_agent",
model="models/gemini-2.0-no-flash",
description="A support ticket creation assistant",
instructions="""
You are a support ticket creation assistant. You help users create well-structured support tickets from their issues.
For each ticket request:
1. Extract the key issue from the user's description
2. Create a concise but descriptive title
3. Format a detailed description
4. Determine the appropriate priority level and category
5. For technical issues, list steps to reproduce
6. Include customer contact information if provided
IMPORTANT: Return your response as a JSON object matching this structure:
{
"title": "Brief issue summary",
"description": "Detailed issue description",
"priority": "low|medium|high|critical",
"category": "technical|billing|account|general",
"steps_to_reproduce": ["Step 1", "Step 2", ...] (optional),
"customer_contact": "Contact information" (optional)
}
""",
output_schema=SupportTicket,
output_key="ticket"
)
Example Interaction
You: I can’t log in to my account. I’ve tried resetting my password three times but I never receive the reset email. I’ve checked my spam folder. I need this fixed ASAP as I have an important meeting tomorrow. You can reach me at alex@example.com. Agent:
{
"title": "Unable to Receive Password Reset Emails",
"description": "Customer cannot log in to their account after multiple attempts to reset password. Reset emails are not being received in inbox or spam folder. Urgent resolution needed for an important meeting scheduled tomorrow.",
"priority": "high",
"category": "account",
"steps_to_reproduce": [
"Attempt to log in to the account",
"Click on 'Forgot Password' or 'Reset Password' option",
"Enter email address",
"Check inbox for reset email",
"Check spam/junk folder for reset email"
],
"customer_contact": "alex@example.com"
}
Using Structured Output with Multi-Agent Systems
Structured outputs become even more powerful in multi-agent systems. Here’s how they can be used:
- Agent A processes a user request and stores structured data in state
- Agent B accesses this structured data from state and performs additional processing
- Agent C takes the combined results and formats a final response
We’ll explore this in more detail in our multi-agent systems tutorial.
Conclusion
Structured outputs transform your agents from simple chat interfaces into reliable data providers for your applications. By defining clear output schemas, you ensure consistency and predictability, making it much easier to integrate agents into larger systems.
In this tutorial, we’ve covered:
- Why structured outputs matter
- Using output_schema with Pydantic models
- Storing structured data in state with output_key
- Advanced schema techniques
- Best practices and limitations
In the next part of our series, we’ll dive deeper into ADK’s session and memory management, exploring how to maintain context and state across conversations.
Resources
graph TD
A[User Input] --> B[Agent Processing]
B --> C[Raw LLM Output]
C --> D[JSON Parsing]
D --> E{Valid Schema?}
E -->|Yes| F[Store in State]
E -->|No| G[Error Handling]
G --> H[Retry or Fallback]
F --> I[Return Structured Response]
H --> I
Saptak Sen
If you enjoyed this post, you should check out my book: Starting with Spark.