constrained-decodingstructured-outputxgrammaroutlinesjson-schemaai

How Constrained Decoding Forces Valid JSON: XGrammar, Outlines & GBNF

·11 min read·AI & JSON

"JSON mode" is not a prompt trick

Most guides tell you to turn on JSON mode, pass a schema, and move on. That works — but it hides the interesting part. When a provider *guarantees* the output parses, it is not asking the model nicely and retrying on failure. It is doing constrained decoding: at every single token step, it computes which tokens could still lead to valid JSON and forbids all the others. Invalid JSON becomes literally unreachable, not merely unlikely.

If you have read LLM JSON mode & structured outputs for the *how to use it*, this is the *how it works* — and knowing the mechanism is what lets you predict when it will fail.

The one-sentence version

A language model produces a probability (a logit) for every token in its vocabulary at each step. Constrained decoding inserts one operation between the logits and sampling: it sets the logit of every token that would break the grammar to negative infinity.

text
raw logits ──▶ [ grammar mask: illegal tokens ➜ -inf ] ──▶ softmax ──▶ sample

Because the mask is applied *during* generation rather than checked *after*, there is no parse error to catch, no retry loop, and no fallback parser. The model can only emit a token that keeps the partial output valid.

From JSON Schema to a token mask

The pipeline that turns your schema into that mask has three stages:

  1. Schema → grammar. Your JSON Schema is compiled into a formal grammar — an FSM (finite-state machine), a regular expression, or a context-free grammar (CFG). This encodes "a valid document is an opening brace, then a key from this set, then a colon, then a value of this type, …".
  2. Grammar → automaton. The grammar becomes an automaton whose state tracks "where are we in the document right now" — inside a string, expecting a comma, three levels deep in nested objects.
  3. Automaton → per-step mask. At each decoding step the automaton reports which vocabulary tokens are legal *from the current state*. Everything else is masked.

The subtlety is that a model's tokens are not characters — one token might span a quote and part of a word. So the engine cannot reason character-by-character; it must know, for every token in a 100k+ vocabulary, whether appending it keeps the document parseable. Doing that fast at every step is the entire engineering problem.

FSM vs CFG: why nesting is the dividing line

There are two families of engines, and the difference is exactly the shape of JSON they can express.

  • Regex / finite-state engines (Outlines). Outlines pioneered compiling a schema to a regular expression and precomputing, per FSM state, the set of allowed tokens. This is extremely fast for flat structures — a fixed set of fields with scalar values. But a regular language cannot count arbitrary nesting, so deeply recursive JSON is awkward for a pure FSM.
  • Context-free / pushdown engines (XGrammar, llama.cpp GBNF). Real JSON Schema needs recursion, so these engines use a CFG with a stack (a pushdown automaton). The stack is what remembers "I have opened three objects and must eventually close all three." This handles arbitrary nesting correctly, at the cost of a runtime stack check.

In 2026 the practical default flipped to CFG engines. XGrammar is now the built-in backend for vLLM, SGLang, TensorRT-LLM, and MLC-LLM, and most stacks auto-select between XGrammar and Outlines depending on whether your constraint needs recursion.

Why XGrammar made the overhead disappear

The old objection to constrained decoding was speed: checking a huge vocabulary against a grammar at every step sounds expensive. XGrammar's insight is that most tokens don't depend on the current parser state.

  • It splits the vocabulary into context-independent tokens — ones whose legality is the same regardless of position in the document — which it checks once and caches.
  • Only a small set of context-dependent tokens (structural characters like the braces, comma, and quote) must be re-checked at runtime against the stack.
  • That tiny runtime check is overlapped with the GPU's forward pass, so the constraint adds almost nothing to wall-clock latency.

The counterintuitive result, echoed by local runtimes like Ollama's format parameter (see Ollama structured outputs), is that constrained generation is often faster than free generation — the model never wastes tokens deliberating about formatting or wrapping JSON in prose.

What it looks like in practice

vLLM / SGLang — pass the schema as a guided-decoding option:

python
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="x")

resp = client.chat.completions.create(
    model="local-model",
    messages=[{"role": "user", "content": "Extract the invoice."}],
    extra_body={
        "guided_json": {
            "type": "object",
            "properties": {
                "invoice_id": {"type": "string"},
                "total": {"type": "number"},
                "paid": {"type": "boolean"}
            },
            "required": ["invoice_id", "total", "paid"],
            "additionalProperties": False
        }
    },
)

llama.cpp with GBNF — the grammar can be written directly, or auto-converted from a JSON Schema:

text
root   ::= "{" ws "\"total\"" ws ":" ws number ws ","
           ws "\"paid\"" ws ":" ws ("true" | "false") ws "}"
number ::= "-"? [0-9]+ ("." [0-9]+)?
ws     ::= [ \t\n]*

OpenAI / Anthropic — the same idea, hosted: OpenAI's Structured Outputs with a strict schema enforces the shape by constrained decoding and reports sub-0.1% failure rates; Anthropic's tool-use path is equivalent. You supply a schema, the provider guarantees the parse. See function calling & JSON Schema.

Where constrained decoding still bites

Guaranteed-parseable is not guaranteed-correct, and the constraint is not free of side effects.

  • The "constraint tax." Forcing the token distribution through a narrow grammar can *lower answer quality* on some open-weight models — most notably it can suppress a model's tendency to call tools or reason before answering. If accuracy drops when you turn on strict mode, this is why; give the model a free-text reasoning field *before* the structured fields so it can think inside the schema.
  • Valid shape, wrong values. The grammar enforces types and structure, not semantics. A total of 0 is schema-valid and business-wrong. You still need to validate against a stricter schema and check ranges, enums, and cross-field rules.
  • Not every constraint is expressible. Regex-based engines can't express arbitrary recursion; some engines don't support every JSON Schema keyword. Unsupported keywords are silently ignored, so read your engine's compatibility notes.
  • Tokenizer alignment. The grammar operates on the model's exact tokenizer. Point a grammar compiled for one tokenizer at a different model and the masks are wrong.

Inspecting constrained output

When a constrained response is valid JSON but the *content* is off, treat it like any other payload:

  1. Format the output so nested extraction results are readable.
  2. Diff a good and a bad run to isolate which field the model got wrong under the constraint.
  3. Generate a tighter JSON Schema from a known-good sample, then feed that back as the constraint — a narrower grammar leaves the model less room to be wrong.
  4. For large results, explore the tree instead of scrolling raw text.

For the neighboring topics, see Validate LLM JSON against a schema, Fix broken JSON from LLMs (the retry-based world constrained decoding replaces), and Streaming partial JSON.

Frequently asked questions

It is a technique that restricts a language model's next-token choices to only those that keep the output valid against a grammar or schema. Illegal tokens have their logits set to negative infinity before sampling, so the generated text is guaranteed to parse — no retries, no fallback parser.

Outlines compiles constraints to a finite-state machine / regular expression, which is very fast for flat structures but cannot express arbitrary nesting. XGrammar uses a context-free grammar with a stack (a pushdown automaton), so it handles the recursive nesting real JSON Schema requires. XGrammar is now the default backend in vLLM, SGLang, TensorRT-LLM, and MLC-LLM.

Usually not. Engines like XGrammar precompute and cache the tokens whose legality doesn't depend on parser state, leaving only a tiny per-step check for structural characters that overlaps with the GPU forward pass. In practice constrained output is often faster, because the model never spends tokens on formatting or prose.

It can. Squeezing the distribution through a strict grammar sometimes lowers quality or suppresses tool-calling on open-weight models — the so-called "constraint tax." The common fix is to include a free-text reasoning field before the structured fields so the model can think inside the schema.

No. Constrained decoding guarantees the structure and types, not the meaning. A response can satisfy the schema and still contain wrong values, so you still validate ranges, enums, and cross-field business rules after generation.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.