Structured output

Asking a model for JSON in words works until it doesn't. See how a schema in response_format constrains what Gemini can generate, and why you still validate the values yourself.

9 min Free

Most apps don’t show the model’s answer to a person. They feed it to code: save it to a database, call an API, fill a form. Code needs the same fields, with the same names and types, every single time.

You can ask for JSON in the prompt, and often you’ll get it. But “often” isn’t good enough for a pipeline that runs ten thousand times a day. Structured output moves the format from a request into a rule. You pass a JSON Schema (a description of the fields and their types) in response_format, and the API only lets the model generate text that fits it.

The running example turns a customer’s refund email into a RefundRequest record. Press Play to see the prompt-only approach break, then switch the Try it selector to the schema version, and finally to the case where valid JSON still breaks one of your rules.

  1. Request
  2. Generate
  3. Validate
  4. Use it
Your apprefund intakeSchemaRefundRequestJSON grammarconstrained decodingGeminigemini-3.8-flashValidatoryour codeRefunds DByour database

Press Play to watch it run, or step through with ← →.

00/07
import json

from google import genai

client = genai.Client()

email = """Hi, I paid Rs 1,299 by UPI for order ORD-4417 on Monday.
The parcel never came and the app still says 'shipped'. Please refund."""

# Asking for JSON in words: a request, not a guarantee.
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input=f"Return order_id, amount_inr and reason from this email as JSON:\n{email}",
)

data = json.loads(interaction.output_text)  # fails if the reply isn't pure JSON
print(data["amount_inr"])  # fails if the model picked another field name

Parsed result

Nothing parsed yet. Press Play.

What to notice

  1. A schema is enforced while the text is generated, not checked afterwards. The API compiles your schema into a state machine that limits which tokens the model can pick next. That’s why the reply can’t start with “Sure!” or use a field name you didn’t define.
  2. Pydantic writes the schema for you. In Python you describe the result as a class and pass RefundRequest.model_json_schema(). The same class parses the reply with model_validate_json, so you get a typed object back.
  3. Descriptions are instructions. The type says what kind of value a field holds (amount_inr must be an integer). The description says which value: “Amount in rupees, digits only” tells the model to put 1299 there. Write descriptions as clearly as you would write a prompt.
  4. Use enum (or Literal) for categories. reason can only be one of four values. That turns a free-text guess into a classification your code can switch on.
  5. Valid JSON isn’t the same as correct data. Some JSON Schema keywords aren’t enforced during generation: minLength, maxLength, minProperties, maxProperties, uniqueItems, contains, minContains and maxContains. The API strips them silently. minItems and maxItems are enforced. Check everything else in your own code.

Structured output

Formats the model’s final answer. Use it when you want the answer as data: extraction, classification, inputs for another system.

Function calling

Lets the model ask your code to do something before it answers: look up an order, book a slot. See Function calling.

If the API rejects your schema
Very large or deeply nested schemas can be rejected with a 400 INVALID_ARGUMENT error. Google’s fix list: flatten nested objects, shorten property names, trim long enums and optional fields, and move soft rules into descriptions.

Check your understanding

1. What does response_format with a schema actually change?
2. Your schema has summary with maxLength 80, and Gemini returns a 112-character summary. Why?
3. Which job is a better fit for function calling than for structured output?