Structured Outputs (Constrained Decoding)

Constrained decoding masks a language model's token distribution against a JSON schema or grammar at each step, so the output is parseable by construction.

Published Updated

On this page

Definition

Structured outputs are the guarantee that a language model's response is not just probably-well-formed but provably parseable — valid JSON, or text matching a supplied grammar, every time. The mechanism that delivers the guarantee is constrained decoding (also called grammar-constrained or guided decoding): at each step of generation, the sampler is only allowed to pick a token that keeps the output valid against the target schema. Illegal tokens are removed before anything is sampled, so the model cannot produce a trailing comma, an unquoted key, or a field of the wrong type.

That is a different thing from asking nicely. "Return JSON and nothing else" in the prompt is a request the model can decline — under load, on a long output, or when the schema is deep, it sometimes wraps the JSON in prose, truncates a brace, or invents a field. Constrained decoding makes that class of failure structurally impossible rather than merely unlikely, which is why it is the difference between a parser you have to wrap in a retry loop and one that never throws.

The catch, and the reason this is a real engineering decision rather than a free upgrade, is that a constraint operates on the answer while the answer is still being computed. It can guarantee the shape of a response without improving — and sometimes while degrading — what the response actually says.

How It Works

A language model generates one token at a time. At each step it produces a vector of logits, one score per entry in its vocabulary (tens of thousands of entries), which a softmax turns into a probability distribution; the sampler then draws the next token from that distribution. Ordinary "prompt-and-hope" generation samples from the full distribution and trusts the model to have learned to stay inside JSON. Nothing enforces it.

Constrained decoding inserts one step in between. A constraint engine tracks the output produced so far, works out the set of tokens that would keep it valid against the schema or grammar, and sets the logits of every other token to negative infinity — a mask. After the softmax, a masked token has probability exactly zero, so it can never be drawn no matter how the sampler is configured. This is worth stressing because it is the source of the guarantee: masking happens before sampling, so temperature, top-p and every other sampling knob choose only among tokens the grammar already permits. If the partial output is {"age": and the schema says age is an integer, the only legal next tokens are digits (and perhaps a minus sign) — the token for " is masked, so the model physically cannot open a string where a number belongs.

The schema is not checked with an if statement per token, which would be far too slow. It is compiled ahead of time into a formal grammar and usually a finite-state machine, so that "which tokens are legal in this state" is a lookup rather than a computation. The widely-cited Efficient Guided Generation for Large Language Models paper (the Outlines method) makes this its central result: by indexing the state machine against the vocabulary once, up front, the per-token cost of enforcing the constraint becomes O(1) on average — a constant, independent of vocabulary size — where the naive approach of scanning the whole vocabulary at every step is O(N). The paper's own summary is that the technique "adds little overhead to the token sequence generation process." That efficiency is what makes the guarantee practical to switch on for every request instead of only the ones that failed.

Why prompt-and-hope pays a retry tax and constraints do not

The value of turning "usually valid" into "always valid" is easiest to see on the retry budget it eliminates. Suppose unconstrained JSON generation on some moderately nested schema is malformed at a rate of f = 0.05 — five parses in a hundred fail — and suppose failures are independent, so a retry fails at the same rate. The expected number of attempts to get one clean parse is a geometric series, 1 / (1 − f) = 1 / 0.95 = 1.0526. That is a 5.26% tax on every generation on the JSON path: 5.26% more calls, more output tokens, and more wall-clock latency, just to reach the output you asked for the first time.

The average understates the problem, because what actually hurts is the tail. The chance an item is still malformed after you have retried it is f raised to the number of attempts:

  • after 1 retry (2 attempts): 0.05² = 0.00252,500 malformed per million items
  • after 2 retries: 0.05³ = 0.000125125 per million
  • after 3 retries: 0.05⁴ ≈ 6.25 × 10⁻⁶about 6 per million

So even a fairly aggressive cap of three retries leaves a handful of items per million that never parse and must fall through to a human, a default, or a dropped record — and it triples-plus the latency of every unlucky item along the way. Push the failure rate to f = 0.20, which a deep or unusual schema can easily reach, and the arithmetic gets ugly fast: 1 / 0.8 = 1.25 means a 25% tax on the JSON path, and 0.20⁴ = 0.0016 leaves 1,600 malformed per million even after three retries.

Constrained decoding drives f for that failure class to zero. Every sample is parseable by construction, so the retry loop, the tax, and the tail all disappear together — not shrink, vanish. That is the whole case for it: not that it makes malformed output rare, but that it makes it impossible, and impossible is the only failure rate you never have to write fallback code for.

Real-World Applications

Hosted API strict modes. When OpenAI shipped Structured Outputs in August 2024 it reported that gpt-4o-2024-08-06 with the feature enabled scored 100% on its internal complex-schema-following evaluation, against under 40% for an earlier model (gpt-4-0613) relying on prompting alone — a provider figure, but the gap is the point: prompting was a coin-flip on hard schemas and the constraint was not. The same idea is exposed through the API in two places at once — a strict response_format for the final answer, and a strict flag on tool/function definitions so a function call's arguments are guaranteed to match the declared parameter schema. (Which providers offer strict guarantees, and for which schema features, changes release to release; treat any specific behavior as dated to when you read it.)

Local and open-source inference. The mechanism is not a hosted-API secret. llama.cpp ships GBNF grammars, a compact grammar format you hand to the runtime to constrain output token by token; Outlines, Guidance and XGrammar provide the same for the Python ecosystem; and serving stacks such as vLLM expose "guided decoding" that accepts a JSON schema or regular expression directly. An engineer running an open-weights model on their own hardware makes exactly the decision this page is about: pass a grammar and get a guarantee, or prompt and wrap the result in a validator.

The boundary of an agent or pipeline. Anywhere one program has to read a model's output mechanically — a step in an agent loop populating the arguments for the next tool call, a data extraction job writing rows into a database, a router picking one of a fixed set of labels — the receiving code needs the output to parse and to match a shape. Constrained decoding is how that boundary stops being a source of exceptions. This is the same territory covered by error handling in AI systems: the strongest form of handling a malformed output is to make it unproducible in the first place.

Key Concepts

Strict mode versus JSON mode. These are often confused and are not the same guarantee. A plain "JSON mode" promises only that the output is syntactically valid JSON — it will parse — but says nothing about which JSON: fields can be missing, extra, or the wrong type. A strict schema mode constrains the output to a specific structure, so the promise is both "it parses" and "it has the fields and types you declared." The first removes syntax errors; the second removes schema violations on top of them.

Shape is not truth. Constrained decoding removes two failure classes and leaves a third untouched. Malformed output (won't parse) and schema-invalid output (parses, wrong shape) both become impossible. Well-formed but wrong output does not: a model can emit {"order_id": "A-40021", "refund": 250.00} that validates perfectly and refers to an order that does not exist, a refund outside policy, or a currency the API rejects. Detecting that still requires checking the values against something real — a database, a constraint, a second lookup — which no grammar can do, because the grammar only knows the schema, not the world.

Where the constraint binds, the model stops choosing. Every token the mask forbids is a token the model's own distribution no longer influences. Usually that is exactly what you want — you did not want the trailing comma either. But it means a badly-designed schema can force the model down a path it would not have taken, which is the mechanism behind the quality effects below.

Challenges

Schema rigidity forces early commitment. A schema that puts the answer field first makes the model emit its conclusion before it has generated any of the reasoning that would have supported it. For a reasoning model or any chain-of-thought prompt, that is removing the room where the actual work happens. The design tension is real: the schema you want for clean downstream parsing (answer first, tightly typed) is often the opposite of the schema that lets the model think (a free-text reasoning field before a final_answer field).

Measured quality regressions under tight constraints. This is not hypothetical. The EMNLP 2024 Industry Track paper Let Me Speak Freely? measured it on GSM8K across the mid-2024 frontier: GPT-3.5-Turbo scored 75.99% answering in free text and 49.25% under a JSON schema; Claude-3-Haiku fell from 86.51% to 23.44%; Gemini-1.5-Flash barely moved (89.33% to 89.21%). Two things are load- bearing about those numbers. First, the effect is model- and schema-dependent — one model lost almost nothing while another lost two-thirds of its accuracy — so you cannot assume constraints are free or assume they are ruinous without measuring your own case. Second, these are mid-2024 models, a full generation behind the current catalog, and the gaps have narrowed on later generations; but the mechanism that caused them has not gone anywhere, because reasoning still needs room and a grammar that forces the answer early still takes it away.

The two-pass fix, and what it costs. The standard mitigation is to separate thinking from formatting: let the model reason in unconstrained prose, then make a second, cheap call that converts that reasoning into the strict schema. It buys back most of the lost accuracy for the price of one extra request — but it is a request, so it doubles the round trips and reintroduces some of the latency the guarantee was supposed to save. A related, cheaper option is to build the reasoning into the schema as a leading free-text field, so a single constrained call still lets the model narrate before it commits.

Grammar coverage and portability. Real JSON Schema is large — nested oneOf, recursive definitions, regex-constrained strings, unbounded arrays — and constraint engines support different subsets of it. A schema that a hosted API accepts may not compile under a local GBNF grammar, and vice versa, so a system that treats "structured output" as one portable feature can break when it changes backends. Check what your specific engine supports rather than assuming the full spec.

The clearest direction is standardization: JSON Schema is becoming the common interface for constraints across hosted APIs and open-source runtimes, which is what will eventually let a single schema drive OpenAI, a local llama.cpp model, and a vLLM deployment without rewriting it three ways — the portability gap in the previous section is the thing being closed. The second is the maturing of "reason-then-format" as a default rather than a workaround: as reasoning models make extended thinking the norm, the tooling is moving toward separating an unconstrained thinking phase from a constrained output phase automatically, so a developer gets both the guarantee and the reasoning room without hand-building a two-pass pipeline. Both trends point the same way — toward constrained decoding being the ordinary case for machine-consumed output, with the quality cost engineered down rather than accepted.

Code Example

The heart of constrained decoding is a single masking step applied to the logits before sampling. This toy version shows the mechanism directly: the grammar engine supplies the set of token ids that are legal in the current state, and every other token's score is driven to negative infinity so its post-softmax probability is exactly zero.

import math

def mask_logits(logits, legal_token_ids):
    """logits: dict of {token_id: score} for the whole vocabulary.
    legal_token_ids: the set of ids the compiled grammar allows as the
    NEXT token, given everything generated so far."""
    return {
        tid: (score if tid in legal_token_ids else -math.inf)
        for tid, score in logits.items()
    }

def softmax(logits):
    # -inf logits contribute exp(-inf) = 0, i.e. zero probability.
    m = max(v for v in logits.values() if v != -math.inf)
    exps = {t: math.exp(v - m) if v != -math.inf else 0.0
            for t, v in logits.items()}
    total = sum(exps.values())
    return {t: e / total for t, e in exps.items()}

# Vocabulary: after `{"age":` the grammar permits only digits and a minus
# sign; the double-quote token that would (wrongly) open a string is illegal.
raw_logits = {"0": 2.1, "1": 3.4, "2": 1.0, "-": 0.5, '"': 4.9}
legal = {"0", "1", "2", "-"}          # supplied by the FSM for this state

probs = softmax(mask_logits(raw_logits, legal))
for token, p in sorted(probs.items(), key=lambda kv: -kv[1]):
    print(f"{token!r:>4}  {p:.4f}")

Running it prints:

 '1'  0.7051
 '0'  0.1922
 '2'  0.0640
 '-'  0.0388
 '"'  0.0000

The " token had the highest raw score of all — 4.9, the value the unconstrained model most wanted to emit — and constrained decoding still gives it probability 0.0000. That is the guarantee made concrete: the illegal token is not made unlikely, it is made impossible, and no temperature or top-p setting can bring a zero-probability token back into play. A real engine differs only in scale: the vocabulary is tens of thousands of tokens, legal is produced by a compiled finite-state machine in O(1) per step rather than written by hand, and the masking runs on every one of the hundreds of tokens in the response — but the operation the reader just watched is the whole idea.

Frequently Asked Questions

Function calling is about the model deciding which tool to invoke and with what arguments. Structured outputs (a provider's strict mode) is the separate guarantee that whatever JSON comes back validates against a schema. Modern APIs apply the same constrained-decoding machinery to both, so a strict function call and a strict response share the same mechanism.
No. It guarantees parseable, schema-valid output — it removes the malformed and schema-invalid failure classes, not the well-formed-but-wrong class. A field can pass every type and enum check and still be factually false or reference an ID that does not exist.
It can. Constraining the shape of an answer while the answer is still being computed can cut a model's reasoning room; on the GSM8K math benchmark some 2024 models dropped sharply under a JSON schema. The usual fix is to reason in free text first, then convert to the schema in a second pass.
The schema is compiled to a formal grammar and typically a finite-state machine. At each decoding step the engine computes which next tokens keep the partial output valid and masks the rest — sets their probability to zero — before a token is sampled.
Yes, for semantics. Constraints guarantee shape, not truth or referential consistency. You still check that an order ID exists, a date is in range, or a unit is one the downstream API accepts.

Continue Learning

Explore our use-case guides and prompts to deepen your AI knowledge.