The Open Model EcosystemLesson 42 of 71

Contents

The Open Model Ecosystem

Lesson 42 of 71. Sign in to keep your place.

Module 5 · Getting good output

Making the output valid by construction

9 min read

What constrained decoding buys, and what it does notThe output parsesThe output is truePrompted for JSONMost of the timea code fence, a preamble, a singlequoteUnchangedit can still invent an amountGrammar-constrainedAlwaysillegal tokens are masked at everystepUnchangeda required field with no empty valueis an instruction to fabricateConstraining converts a parse failure into a confidently wrong value, which is harder to notice. Thetrade is usually worth making and you have to know you made it: make the field nullable, or add anexplicit unknown to the enum, so the model has somewhere to go.
Constraining converts a parse failure into a confidently wrong value, which is harder to notice. The trade is usually worth making and you have to know you made it: make the field nullable, or add an explicit unknown to the enum, so the model has somewhere to go.

Figure

Asking is not enough

Ask a 7B model to reply with JSON and it will, most of the time. The rest of the time it adds a sentence before the brace, wraps the object in a code fence, uses single quotes, or trails off. Retry loops and regular expressions to pull the object out are the usual response, and they are a bad answer to a problem with a good one.

Constrained decoding removes the failure entirely. At every generation step, a state machine tracks where you are in the grammar, works out which tokens could legally come next, and sets the logits of every other token to negative infinity before sampling. An invalid character is not unlikely; it is impossible.

Using it

llama.cpp, directly from a JSON Schema:

Bash
curl localhost:8080/v1/chat/completions -d '{
  "messages":[{"role":"user","content":"Extract from: Invoice 8841, 12 March, EUR 4,200, Meridian Ltd"}],
  "temperature":0,
  "response_format":{"type":"json_schema","json_schema":{"schema":{
    "type":"object",
    "properties":{
      "invoice_no":{"type":"string"},
      "date":{"type":"string","format":"date"},
      "currency":{"type":"string","enum":["EUR","USD","GBP","INR"]},
      "amount":{"type":"number"},
      "supplier":{"type":"string"}},
    "required":["invoice_no","currency","amount"],
    "additionalProperties":false}}}}'

vLLM takes guided_json, guided_choice and guided_regex. Outlines, xgrammar and lm-format-enforcer are the libraries underneath most of these, and can be used directly with transformers.

llama.cpp also accepts a raw grammar in its own GBNF notation, which is the escape hatch when a schema cannot express what you need:

Text
root   ::= "SENTIMENT: " label "\nREASON: " [^\n]+ "\n"
label  ::= "positive" | "negative" | "neutral"

What it guarantees and what it does not

It guarantees syntax. The output will parse, the enum will be one of your values, the number will be a number.

It guarantees nothing about truth. A model forced to produce an amount will produce an amount, whether or not the document contains one. Constrained decoding converts a parse failure into a confidently wrong value, which is harder to notice. That trade is usually worth making, and you have to know you made it.

Three consequences follow.

Always give the model a way to say nothing. If a field may be absent, make it nullable or add an explicit "unknown" to the enum. A required field with no legal empty value is an instruction to fabricate.

Order the fields deliberately. Generation is left to right, so a field produced early conditions everything after it. Putting a short evidence or reasoning string before the answer field lets the model work before it commits; putting it after means the reasoning is a justification of a decision already made. This is a real and easily measured effect.

Keep schemas shallow. Deeply nested objects with many optional branches are harder for small models, and the grammar machinery gets slower to compile. Flat objects with clear names work far better at 7B than an elegant nested design.

Costs

Compiling a grammar takes time on first use, so cache it rather than rebuilding per request. Per-token overhead is small in modern implementations. The larger cost is subtler: a heavily constrained format can pull the model off the distribution it writes well in, so quality of the content can drop slightly even as validity goes to 100%. Measure both.

When not to constrain

Free-form prose, and any case where you would rather see the model fail visibly than produce a well-formed wrong answer. For a first exploration of a new task, run unconstrained and read what the model naturally produces; it tells you what it understood. Constrain afterwards, once you know the shape you want.

The practical upgrade path: prompt for JSON, discover the failure rate on 200 real inputs, then constrain and watch it go to zero. Skipping the measurement means you never find out whether the schema you wrote matches the task, because the errors that remain are now silent.

Every lesson here is open. Your progress is recorded once you pass the Maths You Actually Need exam.

Check your understanding

After enabling JSON Schema constrained decoding, an extraction pipeline’s parse errors drop to zero but downstream data quality gets worse. What is the most likely mechanism?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minWhat changes when nothing is hidden

Stuck on something?Ask Anjali, your AI tutor

How to cite: Addaly (2026) ‘Making the output valid by construction’, in The Open Model Ecosystem. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/open-models/structured-output (Accessed: 13 October 2026).