Skip to content

Enum-Constrained Output in the Gemini API

7 min read · updated August 11, 2026

Classification is the task where free-form text is most obviously the wrong output. Gemini can constrain a response to exactly the strings you list, decided during decoding rather than checked afterwards, so the parsing step you would otherwise write does not need to exist.

The problem it solves

Ask a model to classify a support ticket as one of four categories and you can prompt carefully and still receive “Billing”, “billing”, “This appears to be a billing issue.”, or a helpful paragraph explaining its reasoning. Every one of those needs handling, and the handling is a pile of string normalisation that fails on the case you did not anticipate.

Constrained decoding removes the class of problem. The service builds a grammar from your schema and masks the token distribution at each step so only tokens consistent with that grammar can be sampled. A value outside the enum is not unlikely; it is unreachable.

A bare enum response

When the entire answer is one choice, ask for the enum MIME type. This is the form people miss, because responseMimeType for structured output is usually application/json:

curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{"role": "user", "parts": [{
      "text": "Ticket: I was charged twice for the same invoice this month.\nClassify it."
    }]}],
    "generationConfig": {
      "responseMimeType": "text/x.enum",
      "responseSchema": {
        "type": "STRING",
        "enum": ["BILLING", "BUG", "FEATURE_REQUEST", "ACCOUNT_ACCESS"]
      }
    }
  }'

The candidate text is the bare value, with no quotes and no prose:

{
  "candidates": [{
    "content": {"role": "model", "parts": [{"text": "BILLING"}]},
    "finishReason": "STOP"
  }],
  "usageMetadata": {"promptTokenCount": 33, "candidatesTokenCount": 2, "totalTokenCount": 35}
}

Note the output token count. A one-word answer is a couple of tokens, against dozens for a sentence of explanation — so constraining the output is a cost optimisation on a high-volume classifier as well as a correctness one.

The same call in the Python SDK, which accepts a Python Enum directly:

import enum
from google import genai
from google.genai import types

class Category(enum.Enum):
    BILLING = "BILLING"
    BUG = "BUG"
    FEATURE_REQUEST = "FEATURE_REQUEST"
    ACCOUNT_ACCESS = "ACCOUNT_ACCESS"

client = genai.Client()
resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=ticket_text,
    config=types.GenerateContentConfig(
        response_mime_type="text/x.enum",
        response_schema=Category,
    ),
)
print(resp.text)      # "BILLING"

An enum inside a JSON schema

More often the classification is one field of a larger object. Then the MIME type is application/json and the enum is a constraint on a property, which is the ordinary JSON Schema meaning of the keyword:

"generationConfig": {
  "responseMimeType": "application/json",
  "responseSchema": {
    "type": "OBJECT",
    "properties": {
      "category":  {"type": "STRING", "enum": ["BILLING", "BUG", "FEATURE_REQUEST", "ACCOUNT_ACCESS"]},
      "urgency":   {"type": "STRING", "enum": ["LOW", "MEDIUM", "HIGH"]},
      "summary":   {"type": "STRING"},
      "needs_human": {"type": "BOOLEAN"}
    },
    "required": ["category", "urgency", "summary", "needs_human"],
    "propertyOrdering": ["category", "urgency", "summary", "needs_human"]
  }
}

Two Gemini-specific details in that schema. Types are the uppercase OpenAPI names — STRING, OBJECT, ARRAY, NUMBER, INTEGER, BOOLEAN — not the lowercase JSON Schema ones. And propertyOrdering is a Gemini extension that fixes the order keys are generated in, which matters more than it looks: a field generated before another can condition it, so putting a reasoning field ahead of a category field gives the model somewhere to think, and putting it after does not.

The broader schema surface — nesting, arrays, required fields, which keywords are supported — belongs to the response schema page.

Why it is a guarantee and not a request

The distinction from prompt-based formatting is the point. “Reply with only one of these four words” in a system instruction is a preference expressed in the same channel as everything else the model is weighing, and it is competing with the model’s tendency to be helpful. Constrained decoding is enforced in the sampler, below the model. There is no distribution over tokens that would produce an invalid value, so no temperature setting and no adversarial input can produce one.

What it does not guarantee is that the choice is correct. The model still has to pick, and forcing a choice from four options means a ticket that is genuinely none of them will still come back as one of the four. If “none of these” is a real outcome, make it a value: OTHER costs nothing and stops the enum from fabricating certainty.

One more failure mode. The constraint applies to the output, and the output can still be cut short — a schema large enough to exhaust maxOutputTokens returns a truncated, unparseable JSON document with finishReason: MAX_TOKENS. Constrained decoding does not protect you from that; check the finish reason before parsing.

Nor does it protect you from a safety block. A filtered response returns a candidate with no parts and a blocking finish reason, and the schema has nothing to say about that — you get no JSON at all rather than an empty object. Structured output narrows the space of successful responses; it does not reduce the number of ways a request can fail to produce one.

Streaming, thinking and tools

Three adjacent features behave in ways worth knowing before you build on the enum.

Streaming

A constrained response streams like any other, but the stream is far less useful. For a bare enum the entire answer is a token or two, so the request completes in one chunk and streaming buys nothing but overhead. For a JSON object, the chunks are fragments of a document that is only parseable when complete, so you must buffer to the end before you can do anything with it. The one thing streaming gives you here is the ability to abandon a response early if the first field already tells you the answer — which is a real optimisation if you put the decisive field first with propertyOrdering, and pointless otherwise.

Thinking

On a reasoning model the constraint applies to the answer, not to the reasoning. The model thinks in unconstrained tokens and then emits a constrained result, which is what you want — but those thinking tokens are drawn from maxOutputTokens, so a schema that fits comfortably in the budget on a non-reasoning model can be truncated on a reasoning one. If a constrained request starts returning MAX_TOKENS after a model change, this is why.

Tools

Response schemas and function declarations are two mechanisms competing for the same output channel, and combining them is constrained or unsupported depending on the model and surface. When you need both, the reliable pattern is to stop using responseSchema and express the structure as a function instead: declare a single function whose parameters are the schema you wanted, force it with toolConfig mode ANY, and read the args off the resulting functionCall. You get the same shape guarantee through a different door.

Whether a given Gemini model accepts responseSchema and tools in the same request has changed between generations. Check the structured output guide for your model id rather than assuming either the restriction or its absence.

Designing the value list

  • Make the values self-describing. The strings are in the prompt and the model reads them. ACCOUNT_ACCESS classifies better than CAT_3, and the difference is not marginal. Opaque codes throw away the only signal the enum carries.
  • Include an escape value. OTHER, UNKNOWN or INSUFFICIENT_INFORMATION. Without one, every out-of-distribution input becomes a confident wrong label and you have no way to find them later.
  • Keep them mutually exclusive. Overlapping categories produce unstable output for the same input, and the instability is your schema’s fault rather than the model’s.
  • Define ambiguous values in the prompt. The enum constrains the shape; the system instruction is where you say what HIGH urgency means. The two work together.