The Nightly Build.
Expert Tools

Outlines masks every invalid token as a model decodes, so the output fits your schema by construction

The guarantee holds on a local model, where the library controls the logits, and thins to the provider's own feature the moment you point it at a hosted API.

Generate JSON, then hope it parses

The common way to pull structured data out of a language model is to ask for JSON in the prompt, read back a string, and call json.loads on it. When the string comes back malformed, and it does, the program catches the exception and asks again. Outlines deletes the second step. It constrains the model's decoding so the returned string already matches a schema you hand it: a JSON schema, a regular expression, a fixed set of choices, or a context-free grammar. The documentation states the property as "always valid JSON."1

The guarantee is genuine because it is produced inside the decoding loop rather than checked once the tokens are out. The design that produces it was written down from first principles in a 2023 paper by the library's original authors.2 The by-construction promise belongs to the local path, where Outlines controls the logits. Point the same call at a hosted API and something else happens. And it is no longer the cheapest way to get a valid schema out of a model, a fact newer engines are happy to report.

Constrain the call to a schema and the parse guard disappears

The path Outlines replaces looks like this. You prompt, you parse, and you wrap the parse in a guard because nothing stopped the model from emitting a trailing comma or a half-quoted key.

the path Outlines replacesPYTHON
import json

raw = client.chat(prompt)          # ordinary generation: nothing constrains the tokens
try:
    data = json.loads(raw)         # may raise JSONDecodeError
except json.JSONDecodeError:
    raw = client.chat(prompt)      # ask again, and hope this one parses
    data = json.loads(raw)
Fig. 1 · Retry-and-parse: generation is unconstrained, so a broken string is caught and re-requested.

On a local model Outlines makes that guard unreachable. You wrap the model once, pass the schema as the second argument to the call, and the returned string is one the schema accepts.3

constrained generation with OutlinesPYTHON
import outlines
from pydantic import BaseModel
from typing import Literal
from transformers import AutoModelForCausalLM, AutoTokenizer

class Customer(BaseModel):
    name: str
    urgency: Literal["high", "medium", "low"]
    issue: str

model_name = "HuggingFaceTB/SmolLM2-135M-Instruct"
hf_model = AutoModelForCausalLM.from_pretrained(model_name)
hf_tokenizer = AutoTokenizer.from_pretrained(model_name)
model = outlines.from_transformers(hf_model, hf_tokenizer)

reply = model("Alice needs help with login issues ASAP", Customer)
Customer.model_validate_json(reply)   # never raises: the tokens could not break the schema
Fig. 2 · A Pydantic model passed as the output type. The result is a JSON string that validates against it.4

model_validate_json never raises on reply, because a token that would break the schema was never sampled. The output type can be a Pydantic model, a raw JSON schema, a Regex such as Regex(r"[0-9]{3}"), a Choice, or a full grammar, each passed the same way as the second argument to the call.4

The index turns a full-vocabulary scan into one lookup

At each step the model produces a logit for every token in its vocabulary. To keep the output valid, you zero the logits of every token that could not continue a valid string and sample from what is left.2 The only hard part is knowing which tokens those are.

Compile the schema to a finite-state machine. The machine's current state fixes which characters may come next, and therefore which tokens may. Figure 3 shows this for the regular expression ([0-9]*)?\.?[0-9]*, which matches a floating-point number. In state 0 the token A is masked out while a digit or a dot is allowed. Sampling .2 advances the machine, and the set of allowed tokens shrinks with it.

A four-state finite-state machine for a floating-point regex, with a row of five candidate tokens beneath it. In the top panel the token A is masked black; after sampling the branches labeled Sample .2 and Sample 1, the automaton advances and a different set of tokens is masked.
Fig. 3 · FSM masking for the regex ([0-9]*)?\.?[0-9]*: the current state decides which tokens are masked, and the mask changes as tokens are sampled.2

Building the mask the direct way means asking, for every token in the vocabulary, whether it is allowed in the current state. That is a scan of the whole vocabulary at every decoding step: a fixed O(N) cost per generated token, with N the vocabulary size.2 For GPT-2's N = 50{,}257 that is fifty thousand membership tests for each token the model emits.

The paper's contribution is to pay that cost once. Before decoding starts, precompute a map \sigma : Q \to \mathcal{P}(V) from each FSM state to the set of tokens valid in it. Every decoding step then becomes a hash-map lookup, O(1) on average, in place of the scan.2 Building the map is the awkward part: a single vocabulary string can match an arbitrary interior piece of the pattern, so the construction starts a match in every viable FSM state, not only the start state.2

The index is not free, and where it costs is memory. Its size tracks the number of FSM states. For a slightly augmented Python grammar with un-reduced automata the paper measures a naively built index at around 50 MB, offered as a loose upper bound rather than a typical schema.2 A context-free grammar needs more: the index becomes a trie keyed on the parser's stack values.2 Any claim about how the masking runs is now a claim about a second package: the FSM compilation and the index were moved out of the Python library into a Rust core, outlines-core, "for performance and portability."5

On a hosted API, the FSM never runs

Everything above needs Outlines to hold the logits in its hand. That is true on local and open-weight backends, Transformers, llama.cpp, vLLM and the rest, where outlines-core builds the mask and applies it inside the decoding loop.15 On a hosted API such as OpenAI, Anthropic, or Gemini the library cannot reach the logits at all, so it defers to the provider's own structured-output feature and the FSM masking never runs.1

Two sentences in the documentation sit in apparent tension, and both are true. Outlines promises valid output "directly from any LLM," and the same documentation notes that structured output "is not available for all models."14 They describe different code paths under one API. The uniform call is a real convenience. The guarantee behind it is not uniform.

There is a quieter version of the same split inside the serving stacks. XGrammar ships as the default structured-generation backend in both vLLM and SGLang.6 A user who runs "Outlines on vLLM" and one who flips on "vLLM structured output" can end up exercising two different engines that offer the same structural guarantee at different costs.6

Outlines is no longer the fastest, and its accuracy cost is contested

The paper's speed claim rests on a single-run comparison against Guidance in 2023, which the authors themselves hedge, "Barring any configuration oversights that might be creating a large run-time discrepancy."2 Two later engines now report beating it. XGrammar reports up to 3.5x faster logit masking on JSON schema and more than 10x on grammars, against baselines that include Outlines.7 llguidance computes its masks on the fly, with "essentially no startup cost" and roughly 50 microseconds of single-core CPU per token on a 128k-token tokenizer. It names the precomputed index as the reason to avoid one: startup time, memory, and a ceiling on constraint complexity.8

How three constraint engines build the token mask, by each project's own account. The speed figures are vendor benchmarks, not independent measurements.78
Engine How the mask is built Startup cost Reported speed
Outlines A precomputed index from FSM states to valid tokens. Index build; about 50 MB for a Python grammar. Valid output, no longer the fastest today.
XGrammar A stack-based parser with some masks precomputed. Partial precompute. Up to 3.5x faster masking, by its own harness.
llguidance Computed per token, on the fly. Essentially none. About 50 microseconds per token, by its own harness.

So the honest line is narrower than the paper's "significantly outperforms existing solutions." The index guarantees valid output. It is not the cheapest way to guarantee it in 2026, and against retry-and-parse, the path most pipelines still run, that comparison is the wrong one to fixate on anyway.

A separate question is whether constraining the decoding hurts the model's reasoning. "Let Me Speak Freely?" reports large drops: GPT-3.5-turbo's GSM8K accuracy falling from about 76% in natural language to about 49% under JSON-mode.9 The study's strictest arm is JSON-mode, a provider flag, not the FSM constraint engine Outlines runs. dottxt's rebuttal makes exactly that objection. It argues the strict arm used JSON-mode prompting with no schema and no constraint engine, ran mismatched prompts across conditions, and leaned on a language model as its answer parser. On matched re-runs it shows structured generation flat to slightly better, GSM8K 0.77 to 0.78.10 Both sides have a stake, and each rests on a small sample. The structural guarantee is not what is in dispute. The reasoning cost is, and it is open.

Reach for it when you own the model and the schema

The guarantee earns its cost wherever a later step will crash on malformed output and you run the model that produces it: pulling fields into a schema, generating tool-call arguments, holding an agent to an output contract, or forcing an evaluation harness to emit a gradable form.1 Point the same call at a hosted API and it still works, but the guarantee you collect is the provider's, not the finite-state machine's.1

The maintenance signal splits cleanly between the two packages. The wrapper is busy: 84 releases on PyPI, the latest 1.3.3 six days before this writing, several a month through 2026.11 The Rust core that actually holds the masking moves at a different pace: 17 releases in all, the latest 0.2.14 in January 2026, a few a year.12 The repository is Apache-2.0, maintained by .txt, and its page shows roughly 15.6k stars with about 94 open issues and 45 open pull requests on the day of writing.13 One naming note for anyone tracing the design back to its source: the 2023 paper was written at Normal Computing, and the library is now dottxt-ai's.213

What holds up
  • Valid output by construction on any backend where Outlines owns the logits.1
  • The design is written down from first principles, and its code is readable in outlines-core.25
  • The wrapper ships steadily: 84 releases, the latest six days before this writing.11
What to be careful about
  • The guarantee thins to the provider's own feature on a hosted API.1
  • It is no longer the fastest engine, and vLLM or SGLang may run XGrammar under you instead.76
  • The core that owns the masking releases only a few times a year.12
Verdict

Depend on Outlines for valid structure when you run the model yourself. That guarantee is real and cheap to reason about. Treat its speed as adequate rather than leading, confirm whether your serving stack is running Outlines or XGrammar underneath, and keep the reasoning-accuracy question open until you have measured it on your own task. An independent benchmark, or the Rust core's release feed going quiet, would move the assessment.12

Sources

  1. dottxt · Outlines documentation
  2. Willard & Louf · Efficient Guided Generation for Large Language Models
  3. dottxt · Outlines getting started
  4. dottxt · Outlines output types
  5. dottxt-ai · outlines-core (GitHub)
  6. mlc-ai · XGrammar (GitHub)
  7. MLC · Efficient, Flexible, Portable Structured Generation with XGrammar
  8. guidance-ai · llguidance (GitHub)
  9. Tam et al. · Let Me Speak Freely? A Study on the Impact of Format Restrictions
  10. Will Kurt, dottxt · Say What You Mean: A Response to "Let Me Speak Freely"
  11. PyPI · outlines (release history)
  12. PyPI · outlines-core (release history)
  13. dottxt-ai · outlines (GitHub)