Structured output
Structured output makes an agent return a validated object instead of text. You define a schema, attach it to an agent or to a single call, and read the object from the result. For a worked example, see Return structured output.
How it works
Section titled “How it works”Strands doesn’t ask the model to write JSON in its reply. It turns your schema into a tool, and the model returns the object by calling that tool with the field values as input. The tool gives the model a typed input schema to fill in, and gives Strands a way to send validation errors back through the agent loop.
For each call with a schema:
- Strands adds a structured output tool, built from your schema, to the agent’s tools.
- The model works as usual and can call your other tools along the way.
- The model calls the structured output tool with the field values.
- Strands validates that input. If it passes, the invocation ends and the object is on
. When you stream the call, it’s on the final result event.result.structured_outputresult.structuredOutput - Strands removes the structured output tool when the invocation ends.
Schemas
Section titled “Schemas”A schema lists the fields the object must have. Strands builds the tool from it, so the model reads every field description.
A schema is a Pydantic model. The
tool is named after the class, so a TicketTriage schema produces a TicketTriage
tool. The class docstring becomes the tool description, and each
Field(description=...) describes that field to the model.
A schema is a Zod object schema. The tool is named
strands_structured_output. A .describe() on the object becomes the tool
description, and a .describe() on a field describes that field to the model.
Strands adds an instruction to the tool description telling the model to call the tool last, after any other tools it needs.
Agent and call schemas
Section titled “Agent and call schemas”A schema set when you create the agent applies to every call. A schema passed to a call applies to that call only and takes precedence over the agent’s schema.
from pydantic import BaseModelfrom strands import Agent
class CustomerInfo(BaseModel): name: str email: str
class RefundRequest(BaseModel): order_id: str amount: float
agent = Agent(structured_output_model=CustomerInfo, callback_handler=None)
customer = agent("This is Dana Ruiz, dana@example.com.").structured_outputrefund = agent( "Please refund order 4512. It was $49.99.", structured_output_model=RefundRequest,).structured_output
print(customer)print(refund)Typical output:
name='Dana Ruiz' email='dana@example.com'order_id='4512' amount=49.99Agent.structured_output() and Agent.structured_output_async() are deprecated and
emit a DeprecationWarning. Pass the schema to the call instead, as shown above.
import { Agent } from '@strands-agents/sdk'import { z } from 'zod'
const CustomerInfoSchema = z.object({ name: z.string(), email: z.string(),})
const RefundRequestSchema = z.object({ orderId: z.string(), amount: z.number(),})
const agent = new Agent({ structuredOutputSchema: CustomerInfoSchema, printer: false })
const customer = await agent.invoke('This is Dana Ruiz, dana@example.com.')const refund = await agent.invoke('Please refund order 4512. It was $49.99.', { structuredOutputSchema: RefundRequestSchema,})
console.log(customer.structuredOutput)console.log(refund.structuredOutput)Typical output:
{ name: 'Dana Ruiz', email: 'dana@example.com' }{ orderId: '4512', amount: 49.99 }An agent schema applies to every call, including turns that only build up context. If some calls should answer in plain text, pass the schema per call instead.
Validation errors
Section titled “Validation errors”A schema can hold rules the model won’t always meet on the first try, like a check that a date is in the future. When the input fails validation, Strands doesn’t raise. It returns the validation errors to the model as the tool result, and the model can call the tool again with corrected values.
from datetime import date
from pydantic import BaseModel, field_validatorfrom strands import Agent
class FollowUp(BaseModel): """A follow-up task for a support ticket."""
action: str due_date: date
@field_validator("due_date") @classmethod def must_be_in_future(cls, value: date) -> date: if value <= date.today(): raise ValueError(f"due_date must be after {date.today().isoformat()}") return value
agent = Agent(callback_handler=None)result = agent( "Schedule a follow-up call with the customer next Tuesday.", structured_output_model=FollowUp,)print(result.structured_output)Typical output:
action='Schedule a follow-up call with the customer' due_date=datetime.date(2026, 10, 20)import { Agent } from '@strands-agents/sdk'import { z } from 'zod'
const today = new Date().toISOString().slice(0, 10)
const FollowUpSchema = z .object({ action: z.string(), dueDate: z.iso.date().refine((value) => value > today, { message: `dueDate must be after ${today}`, }), }) .describe('A follow-up task for a support ticket')
const agent = new Agent({ printer: false })const result = await agent.invoke( 'Schedule a follow-up call with the customer next Tuesday.', { structuredOutputSchema: FollowUpSchema })console.log(result.structuredOutput)Typical output:
{ action: 'Follow-up call with the customer', dueDate: '2026-10-20' }The model doesn’t know today’s date, so its first guess can fail the check:
Example:1. Model calls the structured output tool with a due date of 2025-07-222. Validation fails, and the model receives "due_date must be after 2026-10-12"3. Model calls the tool again with 2026-10-134. Validation passes, and the invocation returns the objectPut rules like this in the schema, not in your prompt. The model sees the exact error message, so write it as an instruction the model can act on.
Forced tool call
Section titled “Forced tool call”Sometimes the model ends its turn with a text reply and never calls the structured output tool. Strands then retries once with the structured output tool required, so the model’s only option is to call it.
Before the retry, Strands adds a user message to the conversation:
You must format the previous response as structured output. To replace it, pass
structured_output_prompt to the agent or to a single call. A value passed to a call
takes precedence over the agent’s value.
from pydantic import BaseModelfrom strands import Agent
class Contact(BaseModel): name: str email: str
agent = Agent( structured_output_prompt="Return the contact details you found as a Contact.", callback_handler=None,)result = agent("Reach Dana Ruiz at dana@example.com.", structured_output_model=Contact)print(result.structured_output)Typical output:
name='Dana Ruiz' email='dana@example.com'Strands drops the text reply and retries with only the tool choice changed. No message is added, so there’s no prompt to configure.
If the model still doesn’t call the tool after the retry, the call raises
StructuredOutputExceptionStructuredOutputErrorThe model failed to invoke the structured output tool even after it was forced.
Catch it where your application can fall back.
Return structured output shows an
example.