The short version: if you want an LLM to hand back JSON your code can actually parse, you have three real options in 2026 — JSON mode, tool/function calling, and constrained decoding. Everything else (regex-parsing model output, hopeful re-prompts, string manipulation on Markdown code blocks) is a workaround people used before schema enforcement went mainstream.
The three techniques look similar from the outside — you send a prompt, you get back a structured object — but they behave differently in failure modes, cost, and how much the schema affects model quality. Choosing the wrong one is one of the most common reasons teams building on top of LLMs in 2026 end up with brittle pipelines that mostly work in demos and quietly fall apart in production.
This guide walks through what each technique actually does under the hood, which provider does which one best, and the practical patterns for making structured output as testable as any other function in your codebase.
Why This Matters More in 2026 Than It Did in 2024
Two years ago, structured output was a nice-to-have. Most LLM applications were chatbots or content generators where the model’s free-text output was the product. Today, LLMs are increasingly buried inside larger systems: extracting fields from documents, choosing next actions inside agent frameworks, populating database rows, triggering downstream workflows.
Every one of those integrations depends on the model returning data that the calling code can actually consume. A single malformed JSON response in a batch of ten thousand documents means a stalled pipeline, a null pointer somewhere three services downstream, and an on-call engineer trying to figure out which record broke. This is why schema enforcement went from “research curiosity” to “table stakes” over a very short window.
The other reason structured output matters more now is that it’s the boundary where LLMs start to look like normal software. Once your model returns typed data with guaranteed field presence, you can write unit tests. You can add contract tests between the model layer and the rest of the system. You can measure regression when you swap providers. Free-text output is fundamentally opaque; structured output is code you can reason about.
The mental model
Think of structured output as a type contract between your LLM and the rest of your program. JSON mode enforces “the response is JSON.” Tool calling enforces “the response is a call to one of these functions with these argument types.” Constrained decoding enforces “the response conforms to this grammar, guaranteed at every token.” The stricter the contract, the fewer failure modes you have to handle downstream.
Technique 1: JSON Mode
JSON mode is the weakest of the three. When you set response_format: { type: "json_object" } on OpenAI, or the equivalent flag on other providers, the model is instructed and constrained (to varying degrees, depending on provider) to emit syntactically valid JSON. That’s it. The keys, the types, the required fields — none of that is enforced.
What you get back is guaranteed to parse with JSON.parse(), but you still have to validate that the JSON has the shape your code expects. If you asked for {"name": string, "age": number} and the model returned {"full_name": "Ada", "years": "36"}, JSON mode is happy. Your code is not.
JSON mode is useful when your schema is genuinely free-form — say, a summarization task where the model needs to produce arbitrary key-value metadata — or when you’re prototyping and don’t want to write out a full schema yet. For production, it’s almost always the wrong choice. You’ll spend more time writing validation and retry logic around JSON mode than you would writing the schema in the first place.
When JSON mode still makes sense
- Exploratory prompts where the shape of the output is what you’re trying to discover.
- Passthrough proxies where the JSON is going straight to a client that will do its own validation.
- Providers that don’t implement schema-enforced output at all — some open-source models and older APIs are JSON-mode-only.
Technique 2: Tool / Function Calling
Tool calling was the industry’s first workable answer to structured output, even though that wasn’t exactly what it was designed for. The technique was introduced so that models could invoke external functions — call a weather API, run a database query, hit a calculator — but developers quickly realized you could use it to enforce output shape by declaring a “fake” tool that represents the response schema.
You define a tool called respond whose input schema matches the object you want back. You tell the model that the only way to complete the task is to call this tool. The model produces a JSON payload matching the tool’s parameter schema, and you extract it from the tool call rather than from a text field.
This works, and works well, across all major providers — OpenAI, Anthropic, Google, Mistral, Cohere. Anthropic in particular treats tool use as the primary structured-output mechanism, and their models are heavily post-trained to produce well-typed tool arguments. The reliability is high enough that most production LangChain and LlamaIndex pipelines lean on tool calling under the hood, even when the “tool” is just a data-shape declaration.
Anthropic (Claude): tool use as the structured-output primitive
Anthropic’s API doesn’t have a “JSON mode” in the OpenAI sense. Instead, you declare tools, and the model returns a tool_use content block whose input field is validated against the tool’s input_schema. In practice this means you get schema-shaped JSON back for free, and the SDKs make it trivial to bind the response to a Pydantic model or Zod schema.
OpenAI: response_format with json_schema strict mode
OpenAI added strict schema enforcement to its Chat Completions and Responses APIs, backed by grammar-based constrained decoding on the server side. When you pass strict: true alongside a JSON Schema, the returned object is guaranteed to match — not because of retries, but because the token sampler is prevented from producing anything else. The Python SDK integrates directly with Pydantic models via the parse() helper.
Google Gemini: responseSchema and responseMimeType
Gemini exposes a responseSchema field on the generation config that accepts a subset of the OpenAPI 3.0 schema spec. When paired with responseMimeType: application/json, the model’s output is constrained during decoding to satisfy the schema. Field ordering, enums, and nested objects are all respected.
When tool calling is the right pick
- You’re already using tools for their intended purpose (calling APIs, retrieving data) and want the response step to look the same as every other step.
- You need to give the model a choice between several structured shapes — e.g. “answer,” “ask a clarifying question,” or “escalate” — each represented as a distinct tool with its own schema.
- You’re building on Anthropic, where tool use is the idiomatic path to structured output.
Technique 3: Constrained Decoding
Constrained decoding is the most powerful of the three. Instead of asking the model to produce JSON and hoping for the best, or wrapping the request in a tool call, the sampler itself is modified so that at every token the model can only emit tokens that keep the output on a path toward a valid grammar completion.
Under the hood, this means constructing a finite-state machine (or, more generally, a pushdown automaton) from your schema, and at each generation step masking out any tokens that would violate the grammar. If the next character has to be a comma, the sampler physically cannot pick any other token. The output isn’t validated after the fact — it’s prevented from ever being wrong.
This is what OpenAI’s strict mode does server-side. It’s also what open-source runtimes like vLLM, TGI, Outlines, XGrammar, and llama.cpp’s grammar backend give you when you run your own inference. If you’re self-hosting models, constrained decoding is the reason you can get schema-enforced output from an open-weights model that has no native “tool calling” behavior at all.
Outlines
Outlines compiles a Pydantic model or JSON Schema into a finite-state machine over the tokenizer’s vocabulary, then plugs into the decoding loop to mask invalid tokens. Because the compilation is amortized across many generations, the per-token overhead is small in steady state. Outlines is the reference implementation most other libraries measure themselves against.
Instructor
Instructor takes a different approach. Instead of modifying the decoder, it wraps the LLM client to enforce a Pydantic schema through validation-and-retry loops. The model produces JSON, the JSON is parsed against your schema, and if it fails, Instructor re-prompts with the validation error inline. This works across every provider that returns text — OpenAI, Anthropic, Ollama, Groq — and it composes with tool calling or JSON mode as a “belt and suspenders” layer.
XGrammar & llama.cpp grammars
XGrammar is a high-performance grammar-constrained decoding library that’s been adopted by vLLM, MLC-LLM, and SGLang. llama.cpp exposes a similar mechanism via GBNF grammars. Both give you sub-millisecond overhead per token relative to unconstrained decoding, which is why they’ve become the default choice for self-hosted inference of any real scale.
The one catch: schema strictness can hurt quality
Constrained decoding gives you a guarantee that’s almost too strong. Because the sampler is forced to commit to your schema starting from the very first token, the model has no room to think in prose before it starts producing structured data. On reasoning-heavy tasks, this can hurt accuracy noticeably — the model produces syntactically perfect JSON with semantically wrong contents.
The fix is one of two patterns:
- Add a reasoning field to the schema. Include a
reasoningornotesstring property at the top of your object. Because JSON emits fields in declaration order, the model gets to write out its chain-of-thought before it commits to the actual answer. This trick is well-known in the community and is the reason most Anthropic tool schemas start with athoughtproperty. - Two-step generation. First call: unconstrained, produces free-text reasoning. Second call: constrained, converts the reasoning into the structured payload. More expensive, but for the highest-stakes extractions it’s worth the extra tokens.
Choosing Between the Three
A simple decision tree covers most cases:
Are you self-hosting a model? Use constrained decoding via vLLM, Outlines, XGrammar, or llama.cpp grammars. You paid for the GPU; you get the schema guarantee for free.
Are you on Anthropic? Use tool calling with a Pydantic model or Zod schema mapped to a tool’s input_schema. Claude’s post-training is heavily optimized for this pattern.
Are you on OpenAI or Gemini? Use their native strict-mode schema enforcement (response_format.json_schema with strict: true on OpenAI, responseSchema on Gemini). This is server-side constrained decoding without you having to run the sampler.
Do you need to switch providers frequently? Use Instructor or a similar validation-wrapper library on top of whichever provider you’re currently pointing at. You lose the “impossible to fail” guarantee of true constrained decoding, but the abstraction layer pays for itself the first time you migrate.
Are you doing pure exploratory prototyping? Use JSON mode. Iterate. Once you know the schema, upgrade to one of the enforced techniques before you ship.
The Testing Payoff
The reason to care about all this isn’t just uptime — it’s that structured output turns your LLM into something you can write real tests against. Once the response is a typed object, you can:
- Write assertions on specific fields. “For this input, the
sentimentfield must be one ofpositive,negative,neutral.” No more regex-matching model output. - Set up contract tests between the model and downstream services. Consumer-driven contract testing works exactly the same way it does for a REST API.
- Regression-test provider swaps. Move from GPT-4o to Claude Sonnet 4, run the same eval suite, compare typed outputs field-by-field. See our LLM evaluation guide for how to structure that suite.
- Snapshot-test rare failure modes. Once you have a schema, you can build up a corpus of edge-case inputs and lock in expected outputs. When a model regresses, the diff is legible.
This is the thing that gets missed in most tutorials. Structured output’s biggest win isn’t that the JSON parses — that’s a solved problem. The win is that your LLM starts to behave like any other function in your codebase, and you can apply thirty years of software engineering practice to it.
Common Pitfalls
Over-nesting the schema. Deep nested objects with lots of optional fields make constrained decoding slower and give the model more room to hallucinate structure. Flatten where you can.
Missing the reasoning field. If your schema starts with the answer, you’re asking the model to guess before it thinks. Always put a reasoning or notes field before the answer field.
Trusting the schema to mean “correct.” Schema enforcement guarantees shape, not truth. A model can happily return a well-typed but wrong answer. You still need evals.
Enums vs open strings. If a field can only take three values, use an enum in the schema. The model will be constrained to those three at decode time, and you’ll never have to handle a fourth in downstream code.
Assuming providers implement schemas the same way. OpenAI’s JSON Schema support isn’t identical to Gemini’s OpenAPI subset, and Anthropic’s tool input_schema has its own quirks. If you’re multi-provider, keep the schema conservative — stick to basic types, avoid recursive references, and don’t assume format validators (email, URI, date-time) are enforced.
Where the Frontier Is Heading
Three trends are worth watching in the second half of 2026.
Grammar-aware decoding for reasoning models. Reasoning-style models (OpenAI’s o-series, Anthropic’s extended-thinking Claude, DeepSeek R-line) produce long internal chains of thought before their final answer. New research and library work is figuring out how to keep the thinking phase unconstrained while snapping the final answer to a schema — getting the best of both worlds without the two-call latency penalty.
Schema-aware fine-tuning. Rather than adding constraints at decode time, some teams are fine-tuning models directly on schema-annotated data so that the base model produces schema-compliant output without any decoder modifications. This is faster at inference and often produces higher-quality outputs, at the cost of a training cycle per schema family.
MCP as a structured-output substrate. The Model Context Protocol standardizes how models declare tools, arguments, and typed responses across providers. As MCP adoption spreads, the “structured output” conversation shifts from “how do I get JSON” to “how do I declare this contract once and use it everywhere.”
Structured Output as a Career Skill
Fluency with structured output is a load-bearing skill for anyone shipping LLM features in production. It shows up in interview loops for LLM engineers, applied ML engineers, and AI-native platform teams. Companies like Anthropic, OpenAI, LangChain, and Databricks all treat schema-enforced generation and validation as a core competency — not a niche.
If you’re moving into an AI engineering role, being able to explain why constrained decoding is different from tool calling, and knowing when each is worth its tradeoffs, is a real differentiator. It’s also one of the few LLM-adjacent skills that transfers cleanly across providers, because the underlying grammar-and-schema ideas outlast any specific API.
Explore AI & ML Roles
Live openings at companies shipping LLM-powered products — the teams that ship structured-output pipelines every day.
Browse AI/ML Jobs → AI Skills Hub →Quick-Reference Checklist
Print this. Pin it above your desk. Come back to it every time you’re about to write a new prompt that expects a typed response.
- Never rely on plain JSON mode in production. If your provider supports schema enforcement, use it.
- Prefer the provider’s native structured output over library wrappers when you’re on a single provider — less abstraction, fewer surprises.
- Put reasoning fields first in the schema so the model can think before it commits.
- Use enums for fields with fixed sets of values. Never accept an open string when you actually mean one-of-three.
- Validate anyway, even with constrained decoding. Provider bugs happen; models get swapped; schemas drift.
- Write unit tests on the parsed object. Not on the raw text. That’s the entire point.
- Log the raw response. When something goes wrong at 2 AM, you’ll want the untouched bytes.