TAI BUI
Back to curriculum
BuildPython

Structured Outputs & Constrained Decoding

Ask an LLM for JSON. Get JSON most of the time. In production, "most" is the problem. Constrained decoding turns "most" into "always" by editing the logits before sampling.

TIME: ~60 minutesPREREQUISITES: Phase 5 · 17 (Chatbots), Phase 5 · 19 (Subword Tokenization)

Learning Objectives

  • Explain why prompting alone can't guarantee valid output, and name the three layers production systems use to enforce structure: prompting, native structured output APIs, and constrained decoding.
  • Describe how a logit processor masks invalid tokens at each generation step, and explain why constrained decoding can be faster than free generation despite the extra machinery.
  • Implement FSM-based constrained decoding for a JSON Schema with Outlines, and choose between Instructor, native vendor APIs, XGrammar/llguidance, and vLLM guided decoding for a given production scenario.
  • Design a schema that avoids the field-order pitfall, recursive-schema blowup, oversized enums, and grammars too strict to allow an 'unknown' value.

The Problem

A classifier prompts an LLM: "Return one of {positive, negative, neutral}." The model returns "The sentiment is positive — this review is overwhelmingly favorable because the customer explicitly states that they ...". Your parser crashes. Your classifier's F1 is 0.0.

Free-form generation is not a contract. It is a suggestion. A production system needs a contract.

Three layers exist in 2026.

  1. Prompting. Ask nicely. "Return only the JSON object." Works ~80% on frontier models, less on smaller ones.
  2. Native structured output APIs. OpenAI response_format, Anthropic tool use, Gemini JSON mode. Reliable on supported schemas. Vendor-locked.
  3. Constrained decoding. Modify the logits at every generation step so the model cannot emit invalid tokens. 100% valid by construction. Works on any local model.

This lesson builds intuition for all three, and tells you exactly when to reach for each.

Warm-up0/3
1. A model produces one raw score per vocabulary entry. Which function turns those scores into sampling probabilities?Warm-up
2. In subword tokenization, what does each entry of a model's vocabulary correspond to?Warm-up
3. A script feeds 'The answer is {"x": 1}' straight into a strict JSON parser. What happens?Warm-up
Check: The Problem0/2
1. Why is prompt-only 'return JSON' not enough for production?Explain
2. Why is a native vendor structured output API not enough for a team that runs its own open-weights model locally?Explain

The Concept

Constrained decoding masking invalid tokens at each step

Constrained decoding masks invalid tokens before the model can ever sample them. At each generation step, the LLM produces a logit vector over the full vocabulary (~100k tokens). A logit processor sits between the model and the sampler: it checks which tokens are valid given the current position in the target grammar (JSON Schema, regex, context-free grammar) and sets every invalid token's logit to negative infinity. Softmax over what's left puts all the probability mass on valid continuations, so the model can't sample its way into broken output.

Implementations in 2026:

  • Outlines. Compiles JSON Schema or regex into a finite-state machine. Every token gets an O(1) valid-next-token lookup. FSM-based, so recursive schemas need flattening.
  • XGrammar / llguidance. Context-free grammar engines. Handle recursive JSON Schema. Near-zero decoding overhead. OpenAI credited llguidance in their 2025 structured output implementation.
  • vLLM guided decoding. vLLM, a popular open-source inference server, ships guided_json, guided_regex, guided_choice, guided_grammar built in, via Outlines, XGrammar, or lm-format-enforcer backends.
  • Instructor. A wrapper over any LLM built on Pydantic (Python's schema-validation library). Retries on validation failure. Cross-provider, but it never touches logits: it leans on retries plus structured-output-aware prompts instead.

The counterintuitive result

Constrained decoding is often faster than unconstrained generation, for two reasons. It shrinks the next-token search space, and clever implementations skip token generation entirely for forced tokens (scaffolding like {"name": ", where every byte is already determined).

The pitfall that costs you

Field order matters here. Put answer before reasoning, and the model commits to an answer before it has thought anything through. The JSON still validates. The answer is still wrong, and no validator catches it, because nothing about that failure looks like a schema violation.

json
// BAD
{"answer": "yes", "reasoning": "because ..."}
 
// GOOD
{"reasoning": "... therefore ...", "answer": "yes"}

Schema field order is logic, not formatting.

Check: The Concept0/13
1. What does constrained decoding modify at each generation step?Define
2. What is a logit processor?Define
3. What is a finite-state machine (FSM) in the context of constrained decoding?Define
4. What is a context-free grammar (CFG) used for in constrained decoding?Define
5. What does 'schema field order' mean for structured output, and why does it matter?Define
6. What is guided decoding?Define
7. Which tool compiles a JSON Schema or regex into a finite-state machine, giving each token an O(1) valid-next-token lookup?Define
8. Which three backends does vLLM's built-in guided decoding (guided_json, guided_regex and the rest) use?Define
9. Why does setting an invalid token's logit to negative infinity mean it can never be sampled?Explain
10. Why might constrained decoding be faster than free generation?Explain
11. What is the limitation of FSM-based constrained decoding tools like Outlines?Explain
12. Which schema design choice prevents premature commitment by the model?Explain
13. A pipeline reports 100% schema-valid outputs, yet many answers are wrong. The schema lists 'answer' before 'reasoning'. What is the likeliest diagnosis?Analyze

Build It

Step 1: regex-constrained generation from scratch

See code/main.py for a standalone FSM implementation. The core idea in 30 lines:

python
def mask_logits(logits, valid_token_ids):
    mask = [float("-inf")] * len(logits)
    for tid in valid_token_ids:
        mask[tid] = logits[tid]
    return mask
 
 
def generate_constrained(model, tokenizer, prompt, fsm):
    ids = tokenizer.encode(prompt)
    state = fsm.initial_state
    while not fsm.is_accept(state):
        logits = model.next_token_logits(ids)
        valid = fsm.valid_tokens(state, tokenizer)
        logits = mask_logits(logits, valid)
        tok = sample(logits)
        ids.append(tok)
        state = fsm.transition(state, tok)
    return tokenizer.decode(ids)

The FSM tracks what parts of the grammar we have satisfied so far. valid_tokens(state, tokenizer) computes which vocabulary tokens can advance the FSM without leaving an accepting path.

Step 2: Outlines for JSON Schema

python
from pydantic import BaseModel
from typing import Literal
import outlines
 
 
class Review(BaseModel):
    sentiment: Literal["positive", "negative", "neutral"]
    confidence: float
    evidence_span: str
 
 
model = outlines.models.transformers("meta-llama/Llama-3.2-3B-Instruct")
generator = outlines.generate.json(model, Review)
 
result = generator("Classify: 'The wait staff was attentive and the food arrived hot.'")
print(result)
# Review(sentiment='positive', confidence=0.93, evidence_span='attentive ... hot')

Zero validation errors. Ever. The FSM makes invalid output unreachable.

Step 3: Instructor for provider-agnostic Pydantic

python
import instructor
from anthropic import Anthropic
from pydantic import BaseModel, Field
 
 
class Invoice(BaseModel):
    vendor: str
    total_usd: float = Field(ge=0)
    line_items: list[str]
 
 
client = instructor.from_anthropic(Anthropic())
invoice = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    response_model=Invoice,
    messages=[{"role": "user", "content": "Extract from: 'Acme Corp $420. Widget, Gizmo.'"}],
)

Different mechanism: Instructor never edits a logit. It formats the schema into the prompt, parses the output, and retries on validation failure (default 3 times). Works with any provider, but retries add latency and cost. Cross-provider portability is the selling point.

Step 4: native vendor APIs

python
from openai import OpenAI
 
client = OpenAI()
response = client.responses.create(
    model="gpt-5",
    input=[{"role": "user", "content": "Classify: 'The food was cold.'"}],
    text={"format": {"type": "json_schema", "name": "sentiment",
          "schema": {"type": "object", "required": ["sentiment"],
                     "properties": {"sentiment": {"type": "string",
                                                  "enum": ["positive", "negative", "neutral"]}}}}},
)
print(response.output_parsed)

Server-side constrained decoding. Reliability parity with Outlines for supported schemas. No local model management. Locks you to the vendor.

Check: Build It0/7
1. How does Instructor get structured output without constrained decoding?Explain
2. With the lesson's mask_logits, what does mask_logits([2.0, 1.0, 0.5, 3.0], [0, 2]) return?Apply
3. In generate_constrained, when does the while loop stop?Apply
4. Outlines generates a Review whose sentiment is Literal['positive','negative','neutral']. Which sentiment values can appear in the result?Apply
5. An Instructor call with total_usd = Field(ge=0) gets a model reply with total_usd = -5. What happens by default?Apply
6. In the OpenAI example, which part of the call restricts sentiment to positive, negative or neutral?Apply
7. An Instructor pipeline is fine in testing, but at scale latency and cost climb. What is the most likely cause?Analyze

Pitfalls

  • Recursive schemas. Outlines flattens recursion to a fixed depth. Tree-structured outputs (nested comments, AST) need XGrammar or llguidance (CFG-based).
  • Huge enums. 10,000-option enum compiles slowly or times out. Switch to a retriever: predict top-k candidates first, constrain to those.
  • Grammar too strict. Force a date: "YYYY-MM-DD" regex and the model can't output "unknown" for a missing date, so it fabricates one instead. Allow null or a sentinel.
  • Premature commitment. See field-order pitfall above. Always put reasoning first.
  • Vendor JSON mode without schema. Pure JSON mode only guarantees valid JSON, not valid for your use case. Always provide a full schema.
Check: Pitfalls0/6
1. What does vendor JSON mode guarantee?Define
2. What problem can a strict regex such as date='YYYY-MM-DD' introduce?Explain
3. What does the lesson recommend when a schema needs a 10,000-option enum?Explain
4. Tree-structured comment output generated with Outlines is cut off at a fixed nesting depth. Which fix fits?Analyze
5. A team turns on vendor JSON mode without a schema. Output always parses, but the 'sentiment' key is sometimes missing. Why?Analyze
6. Design a schema for a local model that must output a refund decision, a reason, and an optional delivery date, with 100% valid output. Which design fits?Design

Use It

The 2026 stack:

SituationPick
OpenAI/Anthropic/Google model, simple schemaNative vendor structured output
Any provider, Pydantic workflow, can tolerate retriesInstructor
Local model, need 100% validity, flat schemaOutlines (FSM)
Local model, recursive schemaXGrammar or llguidance
Self-hosted inference servervLLM guided decoding
Batch processing with retries acceptableInstructor + cheapest model
Check: Use It0/3
1. You are batch-processing documents with a hosted model, retries are acceptable, and cost matters. Which pick fits the lesson's stack?Apply
2. You run your own model on a self-hosted inference server. When should you reach for vLLM guided decoding rather than a vendor structured-output API?Design
3. You serve a local model on your own vLLM server and need recursive tree-shaped output validated by construction. Which approach fits?Design

Ship It

Save as outputs/skill-structured-output-picker.md:

markdown
---
name: structured-output-picker
description: Choose a structured output approach, schema design, and validation plan.
version: 1.0.0
phase: 5
lesson: 20
tags: [nlp, llm, structured-output]
---
 
Given a use case (provider, latency budget, schema complexity, failure tolerance), output:
 
1. Mechanism. Native vendor structured output, Instructor retries, Outlines FSM, or XGrammar CFG. One-sentence reason.
2. Schema design. Field order (reasoning first, answer last), nullable fields for "unknown", enum vs regex, required fields.
3. Failure strategy. Max retries, fallback model, graceful `null` handling, out-of-distribution refusal.
4. Validation plan. Schema compliance rate (target 100%), semantic validity (LLM-judge), field-coverage rate, latency p50/p99.
 
Refuse any design that puts `answer` or `decision` before reasoning fields. Refuse to use bare JSON mode without a schema. Flag recursive schemas behind an FSM-only library.
Author's Note

I've shipped the field-order bug myself. It doesn't announce itself: the JSON validates, the pipeline runs green, and the wrong answer sails straight through review because there's no schema violation for anyone to flag. If you take one rule from this lesson, take this one: whenever a schema has a decision field, put reasoning ahead of it, every time, no exceptions. It costs nothing at inference time and it is the cheapest correctness fix you will ever ship.

The other trap worth calling out: teams reach for Instructor first because it's provider-agnostic and feels like the safe default. It is, until you're running it at scale and the retry budget starts showing up in your latency graphs. Read the Use It table before you commit to a mechanism, not after.

Exercises

  1. Easy. Prompt a small open-weights model (e.g., Llama-3.2-3B) without constrained decoding for Review(sentiment, confidence, evidence_span). Measure the fraction that parse as valid JSON on 100 reviews.
  2. Medium. Same corpus with Outlines JSON mode. Compare compliance rate, latency, and semantic accuracy.
  3. Hard. Implement a regex-constrained decoder from scratch for phone numbers (\d{3}-\d{3}-\d{4}). Verify 0 invalid outputs on 1000 samples.

Key Terms

TermWhat people sayWhat it actually means
Constrained decodingForce valid outputMask invalid-token logits at every generation step.
Logit processorThe thing that constrainsFunction: (logits, state) -> masked_logits.
FSMFinite-state machineCompiled grammar representation; O(1) valid-next-token lookup.
CFGContext-free grammarGrammar that handles recursion; slower but more expressive than FSM.
Schema field orderDoes it matter?Yes: first field commits, so always put reasoning before the answer.
Guided decodingvLLM's name for itSame concept, integrated into the inference server.
JSON modeOpenAI's early versionGuarantees JSON syntax; does NOT guarantee schema match.

Further Reading

Code

Ships With