AI Blog

Outlines vs XGrammar vs llguidance vs Instructor: Valid JSON Was Never the Hard Part

Three of these four constrain the sampler so invalid output cannot be produced, and the choice between them collapses to one question: do your schemas repeat? The fourth does something categorically different, and it is the only one that can enforce the rules that actually break agents — because a grammar guarantees the enum is one of five values and says nothing about which.

By Agentic AI Wiki 13 min read

A grammar engine can guarantee that your model emits {"action": "refund", "amount": 4200} and never a stray backtick — and it cannot tell you whether the refund should have been issued at all. That gap is where structured-output projects go wrong: teams shop for the fastest constrained decoder, ship it, and discover their failure rate barely moved, because malformed JSON was never what was breaking them. Three of the four tools here solve well-formedness and differ only in when they pay for the grammar. The fourth solves a different problem, and most comparisons file it in the wrong column.

At a glance

Four projects that all appear under "structured outputs" and do not all do the same thing.

ProjectWhat it isWhere it runsGuarantee
OutlinesGrammar engine; precomputes a token index from the schemaInside a serving engine you hostOutput cannot be malformed
XGrammarGrammar engine; JIT-compiles with a persistent cacheInside a serving engine you hostOutput cannot be malformed
llguidanceGrammar engine; builds automata lazily, masks on the flyInside a serving engine you hostOutput cannot be malformed
InstructorClient wrapper; validates against Pydantic and retriesIn your application, against any APIOutput satisfies your validators — or raises
Where each approach intervenes in generation Two rows. The upper row shows constrained decoding: the schema is compiled to a grammar, and at every decoding step a token mask zeroes out any token that would break the grammar before sampling, so invalid output is never produced. The lower row shows validate-and-retry: the model generates freely, the result is parsed and validated against a schema, and on failure the error is sent back as a new request. Constrained decoding — Outlines, XGrammar, llguidance JSON Schema or CFG Grammar compile once, or per request Token mask applied every step Sample Output always valid next step Validate and retry — Instructor Pydantic model to a prompt or tool Free generation any provider Parse + validate after the fact Retry error fed back Output or exception another full generation Semantic rules live here — and only here cross-field invariants a grammar cannot express
The categorical split. The top row prevents the error; the bottom row detects it — and only the bottom row can see semantics.
Capability matrix across the four structured-output tools A four-by-five grid scoring Outlines, XGrammar, llguidance and Instructor on well-formedness guarantee, recursive schema support, per-request unique schemas, semantic and cross-field validation, and whether the tool works against a hosted API you do not run. Each is strong where its design premise points and weak against the grain. Where each tool leans hardest Well-formed by construction Recursive schemas Unique schema per request Semantic / cross-field rules Works on a hosted API Outlines Guaranteed Bounded depth or rejected Precompute cost on every new one Out of scope Self-hosted only XGrammar Guaranteed Full CFG JIT + cache; repeats are free Out of scope Self-hosted only llguidance Guaranteed Full CFG Lazy build; no startup cost Out of scope Self-hosted only Instructor Only via the provider, if offered Whatever Pydantic expresses No compile step Arbitrary Python validators Any provider Strong Conditional Weak or not applicable
Each is strong exactly where its design premise points, and unhelpful against the grain.

How constrained decoding actually works

The mechanism is simpler than the project count suggests. At every decoding step the model produces a distribution over the whole vocabulary. A grammar engine computes, for the current parser state, which tokens could legally come next; every other token has its logit set to negative infinity before sampling. Invalid output is not corrected, it is unreachable — and because the mask applies before sampling, the guarantee holds at any temperature.

That leaves exactly one hard engineering problem: computing the mask fast enough that it does not dominate the step. A vocabulary is 100,000-plus tokens and a step is single-digit milliseconds, so the mask has to be produced in microseconds. Every difference between the three engines is a different answer to that problem, and every one of those answers is a trade between work done up front and work done per token.

The counter-intuitive result

Constrained generation is often assumed to cost latency. Benchmarks across large collections of real-world JSON schemas have repeatedly found the opposite in aggregate: a well-implemented engine can produce lower per-token latency than unconstrained generation. The reason is not magic — a constrained model finishes sooner. It cannot ramble, cannot emit a preamble, cannot re-open a closed object, and frequently there is only one legal token, which some engines fast-path without consulting the model at all. If your objection to structured decoding is that it will slow you down, measure before you believe it.

The three grammar engines, and the one question that separates them

What happens when every request carries a different schema Four columns describing behaviour under unique per-request schemas. Outlines pays a precompute cost for each new schema before the first token. XGrammar compiles just in time with a persistent cache, so repeated schemas are free and novel ones cost a compile. llguidance builds its automata lazily and pays essentially no startup cost. Instructor has no compile step at all because it never constrains sampling. Cost before the first token, on a novel schema Outlines Precompute the index over the vocabulary Highest startup cost; cheapest per token after XGrammar JIT compile with a persistent cache Repeat schemas free; novel ones pay once llguidance Lazy automata, masks computed on the fly Essentially no startup cost Instructor No compile step; no mask at all Cost arrives later, as a retry One question decides the first three: do your schemas repeat?
Startup cost versus per-token cost. Your schema churn decides which side of that trade you want.

Outlines — compile once, then it is nearly free

Outlines popularised the approach: turn the schema into a finite automaton and precompute, for every state, the set of allowed tokens. The result is a lookup at sampling time, which is about as cheap as this can get. The costs are the mirror image — building that index takes real time and memory for a schema you have not seen before, and a finite automaton cannot express unbounded recursion, so deeply recursive or self-referential schemas are either rejected or flattened to a fixed depth. If you serve a small fixed set of schemas, both costs are paid once at startup and never again.

XGrammar — just in time, with a cache that does the work

XGrammar reframes the problem around a context-free grammar and a compilation step that is fast enough to run on demand, backed by a persistent cache. Repeated schemas hit the cache and cost nothing; genuinely novel ones pay a compile that is small relative to a generation. It also handles the recursive schemas that trip an automaton-based approach, and its published overhead per token is low enough to disappear into normal step time. This combination is why it became the default structured-output backend in vLLM and SGLang rather than an option you opt into, and the 2026 follow-on work has been aimed squarely at the dynamic-schema case where agents live.

llguidance — pay nothing up front

llguidance, the engine underneath Microsoft's Guidance, takes the opposite position: build the automata lazily and compute masks on the fly, so there is essentially no startup cost at all. Under a fixed schema set this loses to a precomputed index on raw per-token throughput. Under genuinely unique-per-request schemas it wins, because the competition is paying a compile that llguidance never incurs. Independent comparisons on serving engines show exactly this crossover — XGrammar ahead on repeated simple schemas, llguidance ahead when every request brings something new.

So: do your schemas repeat?

That is the decision. A product with twenty fixed extraction schemas has near-total cache hits and should take whatever its serving engine defaults to, which today means XGrammar and means not thinking about it. A platform where tenants supply their own schemas, or an agent whose tool set is assembled per session, has a cache-miss workload and should benchmark llguidance against it. Outlines remains the reference implementation of the precompute idea and the right pick when you control the schema set completely and want the lowest possible per-token cost.

Instructor is not in the same category

Instructor does not touch the sampler. It wraps the provider client, sends your Pydantic model as a schema or tool definition, parses what comes back, validates it, and on failure sends the validation error to the model as a new request — by default up to three times. This is retry-based, not constraint-based, and the difference is not a matter of degree.

What that costs is obvious: a failure is a whole extra generation, latency is unbounded in the tail, and there is no guarantee at all — only an exception when the retries run out. What it buys is the part people undersell. A grammar can enforce that discount_percent is an integer between 0 and 100. It cannot enforce that discount_percent is zero unless customer_tier is "gold", that end_date is after start_date, or that every ID in the response appears in the retrieved context. Those are arbitrary predicates over a parsed object, they are the constraints that actually fail in production, and a Pydantic validator expresses all of them in a line.

The second thing it buys is reach. Grammar engines run inside a serving engine, which means you need to be running the model. Against a hosted API you get whatever structured-output feature the provider ships and nothing else. Instructor works everywhere, and where the provider does offer constrained decoding it uses it and keeps the validation layer on top.

The honest recommendation is both

These compose, and composing them is the configuration that actually works: a grammar engine guarantees the object parses, and a validation layer enforces the semantics the grammar cannot see. You then have exactly one retry path, and it triggers only on the errors that were always going to need one. Teams that pick a single tool usually end up rebuilding the other half by hand.

The failure mode nobody benchmarks

Schema compliance and correctness are different properties, and only one of them is measured by every benchmark in this space. A model forced into a schema will fill it. Ask for a required root_cause field and you will get one whether or not the evidence supports a root cause; ask for a five-value enum and the model picks one rather than telling you none applies. The constraint has converted "I do not know" — the most useful thing a model can say — into a confident, well-formed, unfalsifiable value.

  • Give abstention a legal encoding. Make fields optional where the answer genuinely may not exist, and add an explicit insufficient_evidence variant to enums. If the schema has no way to express uncertainty, you have not eliminated uncertainty; you have hidden it.
  • Do not constrain the reasoning. Forcing structure over the whole response constrains the tokens the model thinks in. Let it reason in free text, then emit the structured object as a separate constrained segment or a separate call.
  • Field order is a prompt. Generation is left to right, so a field placed early is produced with less context and conditions everything after it. Put conclusions after evidence, not before.
  • Names carry instruction. is_fraudulent and risk_flag are the same boolean and do not produce the same distribution. Schema key wording is a prompt-engineering surface, and recent work has measured it as one.

None of this is a criticism of the tools. It is the reminder that they solve the syntactic half of the problem completely and the semantic half not at all, and that the second half is where your incident reports come from.

When to pick which

SituationTakeWhy
Hosted API, no serving engine of your ownInstructor, plus the provider's structured-output modeGrammar engines are not available to you; validation and retry is the whole toolkit
Self-hosted, small fixed schema setYour engine's default, which is XGrammarCache hits on every request; the tuning has no headroom to find
Self-hosted, unique schema per requestBenchmark llguidance against the defaultNo compile cost is the whole advantage, and it is exactly your workload
Recursive or self-referential schemasXGrammar or llguidanceA finite automaton cannot express unbounded recursion
Cross-field or evidence-grounded rulesInstructor on top of whatever you useNo grammar expresses a predicate over the whole parsed object
Agent tool callsYour engine's default, and check it survives a full batchVendor tool-calling already constrains; the risk is throughput under load

One operational note that outranks the whole comparison: whichever engine you use, verify it still behaves at production batch sizes. Structured decoding is applied per sequence, and an engine that looks free at batch 1 can become visible when every request in a large batch carries its own grammar. That is an axis the serving-engine comparison treats as a first-class differentiator, and it is worth measuring on your own traffic rather than inheriting from a blog post.

FAQ

Does constrained decoding make the model dumber?

It can, if you constrain the wrong tokens. Forcing structure across an entire response restricts the tokens the model reasons in and measurably hurts on tasks that need working-through. Constraining only the final output segment, after free-text reasoning, avoids nearly all of it.

Do I still need this if my provider offers structured outputs?

For well-formedness, no — the provider is doing the same thing server-side. For semantic and cross-field rules, yes, because no provider validates predicates it cannot see. That is the layer Instructor occupies and it does not go away.

Is XGrammar always the right default?

It is the right default in the literal sense that vLLM and SGLang have made it one, and for most workloads there is no reason to override that. The exception is genuinely unique-per-request schemas, where the cache stops helping and llguidance's lazy construction is a real advantage.

Can a grammar guarantee the values are right?

No. A grammar constrains the shape of the output — types, structure, which strings are legal in an enum. Which legal value gets chosen is a modelling question, and a schema that offers no way to say "I don't know" will get a confident answer instead of an honest one.

What about tool calling — is that the same machinery?

Underneath, usually yes: a tool schema is a JSON Schema and constrained decoding is how providers make tool calls parse reliably. The difference is that with tool calling you rarely choose the engine, and the failure you actually see is the model picking the wrong tool with perfectly valid arguments.

Further reading

On this wiki:

Project sources: