Structured Outputs Guide
View SourceOverview
Structured outputs allow you to get JSON responses from Gemini models that strictly adhere to a predefined JSON Schema. This feature is essential for:
- Building reliable APIs that return predictable data structures
- Extracting structured information from unstructured text
- Ensuring type safety in downstream processing
- Integrating with typed languages and strict validation systems
Quick Start
alias Gemini.Types.GenerationConfig
# Define your schema
schema = %{
"type" => "object",
"properties" => %{
"answer" => %{"type" => "string"},
"confidence" => %{"type" => "number"}
},
"required" => ["answer"]
}
# Use the convenient helper
config = GenerationConfig.structured_json(schema)
{:ok, response} = Gemini.generate(
"What is the capital of France?",
model: "gemini-2.5-flash",
generation_config: config
)
{:ok, text} = Gemini.extract_text(response)
{:ok, data} = Jason.decode(text)
# => %{"answer" => "Paris", "confidence" => 0.95}New Features (November 2025)
The November 2025 Gemini API update added support for advanced JSON Schema keywords:
1. Union Types with anyOf
schema = %{
"type" => "object",
"properties" => %{
"status" => %{
"anyOf" => [
%{
"type" => "object",
"properties" => %{"success" => %{"type" => "string"}}
},
%{
"type" => "object",
"properties" => %{"error" => %{"type" => "string"}}
}
]
}
}
}2. Recursive Schemas with $ref
schema = %{
"$defs" => %{
"Node" => %{
"type" => "object",
"properties" => %{
"value" => %{"type" => "string"},
"children" => %{
"type" => "array",
"items" => %{"$ref" => "#/$defs/Node"}
}
}
}
},
"type" => "object",
"properties" => %{
"tree" => %{"$ref" => "#/$defs/Node"}
}
}3. Numeric Constraints
schema = %{
"type" => "object",
"properties" => %{
"score" => %{
"type" => "number",
"minimum" => 0.0,
"maximum" => 100.0
},
"age" => %{
"type" => "integer",
"minimum" => 0,
"maximum" => 150
}
}
}4. Control Over Additional Properties
# Strict mode - no extra properties allowed
schema = %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"}
},
"additionalProperties" => false,
"required" => ["name"]
}
# Allow extra properties with specific type
schema = %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"}
},
"additionalProperties" => %{"type" => "string"}
}5. Nullable Fields
schema = %{
"type" => "object",
"properties" => %{
"middleName" => %{
"type" => ["string", "null"]
}
}
}6. Tuple-like Arrays with prefixItems
# Define a 2D point as [x, y]
schema = %{
"type" => "object",
"properties" => %{
"coordinates" => %{
"type" => "array",
"prefixItems" => [
%{"type" => "number"},
%{"type" => "number"}
],
"items" => false # No additional items allowed
}
}
}Model-Specific Considerations
Gemini 2.5+ Models (Recommended)
These models automatically preserve the order of properties in your schema:
schema = %{
"type" => "object",
"properties" => %{
"firstName" => %{"type" => "string"},
"lastName" => %{"type" => "string"},
"age" => %{"type" => "integer"}
}
}
config = GenerationConfig.structured_json(schema)
# Output will have properties in order: firstName, lastName, ageGemini 2.0 Models
These models require explicit property ordering:
schema = %{
"type" => "object",
"properties" => %{
"firstName" => %{"type" => "string"},
"lastName" => %{"type" => "string"},
"age" => %{"type" => "integer"}
}
}
config =
GenerationConfig.structured_json(schema)
|> GenerationConfig.property_ordering(["firstName", "lastName", "age"])Common Patterns
Extract Multiple Entities
schema = %{
"type" => "object",
"properties" => %{
"people" => %{
"type" => "array",
"items" => %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"},
"role" => %{"type" => "string"}
}
}
}
}
}
config = GenerationConfig.structured_json(schema)
{:ok, response} = Gemini.generate(
"Extract all people mentioned: John is the CEO and Sarah is the CTO.",
model: "gemini-2.5-flash",
generation_config: config
)Sentiment Analysis
schema = %{
"type" => "object",
"properties" => %{
"sentiment" => %{
"type" => "string",
"enum" => ["positive", "negative", "neutral"]
},
"confidence" => %{
"type" => "number",
"minimum" => 0.0,
"maximum" => 1.0
},
"keywords" => %{
"type" => "array",
"items" => %{"type" => "string"}
}
},
"required" => ["sentiment", "confidence"]
}Data Validation and Classification
schema = %{
"type" => "object",
"properties" => %{
"isValid" => %{"type" => "boolean"},
"category" => %{
"type" => "string",
"enum" => ["bug", "feature", "question", "documentation"]
},
"priority" => %{
"type" => "string",
"enum" => ["low", "medium", "high", "critical"]
}
},
"required" => ["isValid", "category", "priority"]
}Best Practices
1. Use Required Fields
Always specify which fields are required to ensure complete responses:
schema = %{
"type" => "object",
"properties" => %{
"answer" => %{"type" => "string"},
"sources" => %{"type" => "array", "items" => %{"type" => "string"}}
},
"required" => ["answer"] # sources is optional
}2. Add Descriptions for Complex Schemas
Help the model understand your schema intent:
schema = %{
"type" => "object",
"description" => "Product information extracted from text",
"properties" => %{
"name" => %{
"type" => "string",
"description" => "The product name"
},
"price" => %{
"type" => "number",
"description" => "Price in USD",
"minimum" => 0
}
}
}3. Use Enums for Classification
Constrain outputs to known values:
schema = %{
"type" => "object",
"properties" => %{
"language" => %{
"type" => "string",
"enum" => ["elixir", "python", "javascript", "rust", "go"]
}
}
}4. Combine with Other Generation Config Options
config =
GenerationConfig.structured_json(schema)
|> GenerationConfig.temperature(0.1) # Lower temperature for consistency
|> GenerationConfig.max_tokens(1000)Streaming Support
Structured outputs work with streaming responses. Each chunk will contain valid partial JSON:
schema = %{
"type" => "object",
"properties" => %{
"story" => %{"type" => "string"}
}
}
config = GenerationConfig.structured_json(schema)
{:ok, stream} = Gemini.stream_generate(
"Write a short story",
model: "gemini-2.5-flash",
generation_config: config
)
full_text =
stream
|> Enum.map(fn resp ->
{:ok, text} = Gemini.extract_text(resp)
text
end)
|> Enum.join()
{:ok, data} = Jason.decode(full_text)Troubleshooting
Schema Validation Errors
If you get schema validation errors, ensure:
- All required JSON Schema fields are present
- Types are correctly specified
- Enums contain at least one value
- References (
$ref) point to valid definitions
Empty or Malformed Responses
If responses don't match your schema:
- Check that your prompt clearly describes what you want
- Simplify complex schemas for testing
- Verify the model supports the schema keywords you're using
- Try lowering the temperature for more consistent results
Property Ordering Issues (Gemini 2.0)
If property order is incorrect on Gemini 2.0 models:
- Use
property_ordering/2to explicitly set the order - Ensure the ordering list matches all properties in your schema
- Upgrade to Gemini 2.5+ for automatic ordering
Examples
See the examples/ directory for working code:
structured_outputs_basic.exs- Simple structured output examplestructured_outputs_standalone.exs- Standalone example with Mix.install