1045 words
5 minutes
Structured Outputs

JSON is one of the most widely used formats in the world for applications to exchange data. Structured Outputs is a feature that ensures the model will always generate responses that adhere to our supplied JSON Schema, so we don’t need to worry about the model omitting a required key, or hallucinating an invalid enum value.

Why Use Structured Outputs#

Without structured outputs, LLM can generate malformed JSON responses or invalid tool inputs that break our applications. Even with careful prompting, we may encounter:

  • Parsing errors from invalid JSON syntax
  • Missing required fields
  • Inconsistent data types
  • Schema violations requiring error handling and retries

Structured outputs guarantee schema-compliant responses through constrained decoding:

  1. Reliable type-safety: No need to validate or retry incorrectly formatted responses
  2. Explicit refusals: Safety-based model refusals are now programmatically detectable
  3. Simpler prompting: No need for strongly worded prompts to achieve consistent formatting

Quick Start#

Claude - JSON outputs#

JSON outputs control Claude’s response format, ensuring Claude returns valid JSON matching our schema. Use JSON outputs when we need to:

  • Control Claude’s response format
  • Extract data from images or text
  • Generate structured reports
  • Format API responses
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
}
],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"},
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": False,
},
}
},
)
print(next(block.text for block in response.content if block.type == "text"))

Response format: Valid JSON matching our schema in the response’s text content block

{
"name": "John Smith",
"email": "john@example.com",
"plan_interest": "Enterprise",
"demo_requested": true
}
How it works
  1. Define our JSON schema: Create a JSON schema that describes the structure we want Claude to follow. The schema uses standard JSON Schema format with some limitations (see JSON Schema limitations).
  2. Add the output_config.format parameter: Include the output_config.format parameter in our API request with type: "json_schema" and our schema definition.
  3. Parse the response: Claude’s response is valid JSON matching our schema, returned in the response’s text content block.

OpenAI#

response = client.responses.create(
model="gpt-6-astra",
input=[
{
"role": "system",
"content": "You are a helpful math tutor. Guide the user through the solution step by step.",
},
{"role": "user", "content": "how can I solve 8x + 7 = -23"},
],
text={
"format": {
"type": "json_schema",
"name": "math_response",
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {"type": "string"},
"output": {"type": "string"},
},
"required": ["explanation", "output"],
"additionalProperties": False,
},
},
"final_answer": {"type": "string"},
},
"required": ["steps", "final_answer"],
"additionalProperties": False,
},
"strict": True,
},
},
)
print(response.output_text)
NOTE

The first request we make with any schema will have additional latency as our API processes the schema, but subsequent requests with the same schema will not have additional latency.

How it works
  1. Define our schema: First you must design the JSON Schema that the model should be constrained to follow. While Structured Outputs supports much of JSON Schema, some features are unavailable either for performance or technical reasons. See here for more details.

  2. Supply schema in the API call: To use Structured Outputs, simply specify

    text: { format: { type: "json_schema", "strict": true, "schema":} }
  3. Handle edge cases: In some cases, the model might not generate a valid response that matches the provided JSON schema. This can happen in the case of a refusal, if the model refuses to answer for safety reasons, or if for example we reach a max tokens limit and the response is incomplete.

Working with JSON Outputs in SDKs#

In addition to supporting JSON Schema in the REST API, the SDKs of OpenAI, Claude, Gemini for Python and JavaScript also make it easy to define object schemas using Pydantic and Zod respectively.

The SDKs provide helpers that make it easier to work with JSON outputs, including schema transformation, automatic validation, and integration with popular schema libraries.

We can see how to extract information from unstructured text that conforms to a schema defined in vendor-specific sections below.

Claude#

from pydantic import BaseModel
from anthropic import Anthropic
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: str
demo_requested: bool
client = Anthropic()
response = client.messages.parse(
model="claude-opus-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
}
],
output_format=ContactInfo,
)
print(response.parsed_output)

OpenAI#

from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
response = client.responses.parse(
model="gpt-6-astra",
input=[
{"role": "system", "content": "Extract the event information."},
{
"role": "user",
"content": "Alice and Bob are going to a science fair on Friday.",
},
],
text_format=CalendarEvent,
)
event = response.output_parsed

Gemini#

This example demonstrates how to extract structured data from text using basic JSON Schema types like object, array, string, and integer.

from google import genai
from pydantic import BaseModel, Field
from typing import List, Optional
class Ingredient(BaseModel):
name: str = Field(description="Name of the ingredient.")
quantity: str = Field(description="Quantity of the ingredient, including units.")
class Recipe(BaseModel):
recipe_name: str = Field(description="The name of the recipe.")
prep_time_minutes: Optional[int] = Field(description="Optional time in minutes to prepare the recipe.")
ingredients: List[Ingredient]
instructions: List[str]
client = genai.Client()
prompt = """
Please extract the recipe from the following text.
The user wants to make delicious chocolate chip cookies.
They need 2 and 1/4 cups of all-purpose flour, 1 teaspoon of baking soda,
1 teaspoon of salt, 1 cup of unsalted butter (softened), 3/4 cup of granulated sugar,
3/4 cup of packed brown sugar, 1 teaspoon of vanilla extract, and 2 large eggs.
For the best part, they'll need 2 cups of semisweet chocolate chips.
First, preheat the oven to 375°F (190°C). Then, in a small bowl, whisk together the flour,
baking soda, and salt. In a large bowl, cream together the butter, granulated sugar, and brown sugar
until light and fluffy. Beat in the vanilla and eggs, one at a time. Gradually beat in the dry
ingredients until just combined. Finally, stir in the chocolate chips. Drop by rounded tablespoons
onto ungreased baking sheets and bake for 9 to 11 minutes.
"""
interaction = client.interactions.create(
model="gemini-3.8-flash",
input=prompt,
response_format={
"type": "text",
"mime_type": "application/json",
"schema": Recipe.model_json_schema()
},
)
recipe = Recipe.model_validate_json(interaction.output_text)
print(recipe)

Resources#

Direct links to the official documentation for structured outputs across the major API providers:

Structured Outputs
https://blogs.openml.io/posts/structured-outputs/
Author
OpenML Blogs
Published at
2026-09-14
License
CC BY-NC-SA 4.0