Book a 30-min callBook a call

How structured outputs work: constrained decoding, grammars and JSON Schema

How LLM structured outputs work: logit masking, JSON Schema compiled to automata, token boundaries, provider strict-mode limits, quality and retries.

Token blocks going into a model box, probability bars with all but one crossed out, and a small state diagram of circles picking the next step.
Written by
BYD Tech
Published
Topic
Deep dives
Reading time
15 min read

Questions people ask

Is constrained decoding the same as JSON mode?

No. JSON mode makes the output valid JSON. Constrained decoding against a schema also makes it match your fields, types and enum values, within the subset the engine supports. OpenAI’s docs recommend Structured Outputs over JSON mode wherever it is available.

Does constrained decoding slow generation down?

A little, and mostly up front. Engines precompute which tokens are valid in which state, so the mask at each step is cheap; the XGrammar authors report near-zero overhead in end-to-end serving. The visible cost is compiling a new schema, which is why the first request with a new schema is slower on hosted APIs.

Can I use my own grammar with a hosted model API?

Only through what the provider exposes, usually a strict flag on tools and a JSON Schema response format. Hosted APIs do not let you change the mask yourself, so custom grammars such as GBNF need a model you run, through an engine such as llama.cpp or a library such as Outlines or XGrammar.

Why can a schema-valid response still hold the wrong value?

Because the mask only rules out text that breaks the format. Inside the format, the model picks the value it finds most likely, and that can be wrong, invented or out of date. Validate the content against business rules and source data.

Should the reasoning field come before the answer field?

Usually, when the task needs reasoning. Models generate in order, so a field that comes first is written without the benefit of anything after it. Keep the reasoning short, and remember that some providers move optional fields after required ones.

Building something like this?