Structured Outputs: Get JSON You Can Actually Trust
Structured outputs use schema-constrained decoding to guarantee valid JSON from an LLM, unlike JSON mode alone. Here's how to build one.

A lead-capture pipeline pulls a name and email out of a chat transcript, drops it into a webhook, and moves on. It's worked for weeks. Then one call comes back with "full_name" instead of "name", the webhook doesn't recognize the field, and the lead just vanishes. Nothing crashed. Nothing logged an error. The model did exactly what it was asked: return the info as JSON. It just didn't promise the field would be called the same thing twice.
That's the gap structured outputs close. Asking a model to "respond in JSON" only guarantees valid syntax, not a specific shape. Structured outputs and schema-driven prompting mean handing the model an actual contract, a JSON Schema describing exact field names, types, and requirements, and having the model's response constrained at the token level so it cannot violate that contract.
In this lesson, you'll learn why plain JSON requests aren't reliable enough for production, the three levels of reliability between "asking nicely" and "guaranteed," and how to build and test a schema-constrained extraction yourself. This builds directly on controlling how a model formats its output: a schema is the strictest form of that control, the same way keeping RAG answers grounded is the strictest form of controlling what a model is even allowed to talk about.
Key Takeaways
- Plain JSON mode guarantees valid syntax only, not a consistent shape, so field names and structure can shift between calls even when every response is technically valid JSON.
- Structured outputs use constrained decoding, blocking any token that would break the schema, which is why Anthropic reports schema-compliant responses even under heavy production load.
- Writing a schema is a form of prompting: you're specifying a contract instead of a sentence.
- Even strict schemas aren't unbreakable: safety refusals and token limits can still produce output that doesn't match your shape.
What Is Schema-Driven Prompting?
Schema-driven prompting means describing the exact shape you want a model's output to take, field names, types, and which fields are required, and having that description enforced rather than just requested. It's still prompting. You're just writing the instruction as a structured contract instead of a sentence.
A JSON Schema is the format most providers use for this contract. It looks like this for a simple lead-extraction task:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"wants_demo": { "type": "boolean" }
},
"required": ["name", "email", "wants_demo"],
"additionalProperties": false
}
additionalProperties: false matters here. Without it, the model is free to add extra fields you never asked for, which quietly reintroduces the same inconsistency problem you were trying to avoid.
Why "Just Say JSON" Isn't Enough
Ask a model to "respond in JSON" and most providers will genuinely give you syntactically valid JSON back. The problem shows up in the shape, not the syntax.
Run the same extraction prompt five times without a schema. You can get "name" on one run and "full_name" on the next. An email might arrive as a plain string one time and nested inside a "contact" object the next. Nothing about that violates "respond in JSON." But it breaks any code downstream that expected a consistent structure.
That inconsistency isn't rare. As one detailed comparison of JSON mode, function calling, and structured outputs puts it, JSON mode "does guarantee that the output is valid JSON, but it does not guarantee a specific structure." At any real volume, that gap between valid and consistent is where pipelines quietly break.
Skip schema enforcement and the failure mode isn't a crash you can catch in testing. It's a slow, silent shape drift that shows up as missing data three weeks later.

The Three Levels of Structured Output Reliability
Getting structured data out of a model isn't one technique, it's a ladder. Each rung trades a bit more setup for a lot more guarantee.
Structured output reliability, low to high
| Level | Method | How it's enforced | Reliability |
|---|---|---|---|
| 1 | Prompt-only ("respond in JSON") | Instruction only, no enforcement | Valid JSON, shape not guaranteed |
| 2 | Function calling / tool use | Model fills a defined tool schema | High, but not schema-guaranteed on every provider |
| 3 | Native structured output | Constrained decoding blocks invalid tokens | Schema-compliant by construction |
Level 3, native structured output, works by compiling your schema into a grammar the decoder enforces at every token, not by hoping the model remembers the instruction. If the schema says a field is a boolean, the decoder simply never lets the model generate a token that isn't true or false in that position.
Level 2 and Level 3 aren't always separate steps. Anthropic's strict tool use and OpenAI's structured outputs both apply the same constrained-decoding idea to function calling, so a well-defined tool schema often gets you Level 3 guarantees for free.
Building a Schema-Constrained Extraction
Here's the same lead-extraction example, this time as an actual API call using Claude's structured outputs.
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extract from: Jordan Lee (jordan@example.com) wants a demo."
}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"wants_demo": {"type": "boolean"}
},
"required": ["name", "email", "wants_demo"],
"additionalProperties": False
}
}
}
)
Run that ten times and you get the same three fields, same names, same types, every single time. No regex cleanup, no "hope it parsed" retry logic. The schema is the prompt for the shape, the message is the prompt for the content.
What Still Breaks (Edge Cases)
Schema enforcement is a huge reliability jump, not a guarantee against every failure. Three things still slip through even with strict mode enabled.
Safety refusals override schema enforcement. If a model decides a request shouldn't be answered, it returns a refusal instead of schema-shaped output, so your code still needs to check for that case explicitly. A token limit cutting a response short before the JSON closes is a second real failure mode, and enum values differing only in capitalization from what your schema expects is a third.
I've shipped a pipeline that passed every test case in staging and then failed in production the first time a real user's message tripped a refusal, because none of the test inputs ever needed one. Structured outputs remove the shape-drift failure, not every failure. Testing edge cases, not just the happy path, is what separates a demo from something you can trust.
Your Task
Write a schema for a real extraction
Pick something you'd actually want structured data from, an email, a support ticket, a product review, and write a JSON Schema with 3 to 5 fields, marking which ones are required.
Run it through a model with structured output enabled
Send your schema and one real example through Claude, GPT, or Gemini's structured output feature and confirm the response matches your schema exactly.
Break it on purpose
Feed the model an input that's ambiguous or missing information for one required field, and see what it does. Note whether it guesses, leaves the field oddly filled, or something else you didn't expect.
Done? You've completed Lesson 12.08 and built your own schema-constrained extraction. Next up: the next lesson in this module, coming soon →
FAQ
Common questions
Finished reading?
Mark it complete to track your progress through the path.
Comments (0)
Be the first to leave a comment.