{"post":{"slug":"structured-outputs-guide-2026","title":"Structured outputs guide for 2026: JSON mode, tool schemas, and Pydantic","excerpt":"Getting reliable structured data from LLMs in 2026 is solved with the right API features. This guide covers JSON mode, tool schemas, and validation patterns.","category":"code","date":"April 29, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">G</span>etting structured data out of a language model reliably was a significant pain point in 2023–2024. In 2026 it is a solved problem at the API level. JSON mode, tool-calling schemas, and provider-specific structured output features give you machine-readable output without brittle regex or prompt-wrestling.\n\n## What changed in 2026\n\n- **Tool-calling is universal** across Anthropic, OpenAI, Google, and Mistral — one pattern works everywhere.\n- **JSON schema constraints are server-enforced** on OpenAI's `response_format` and Anthropic's tool-choice API. The model cannot produce output that violates the schema.\n- **Pydantic v2 became the standard Python integration layer** — `.model_json_schema()` generates the schema, `.model_validate()` validates the response.\n- **Structured output tokens are cheaper** on most providers because schema-constrained generation is more efficient.\n\n## Three options, ranked by reliability\n\n| Method | Reliability | Flexibility | Complexity |\n| --- | --- | --- | --- |\n| Tool-calling (forced) | Highest | Schema-constrained | Low |\n| JSON schema `response_format` | High | Schema-constrained | Low |\n| JSON mode (no schema) | Medium | Any valid JSON | Low |\n| Prompt-instructed JSON | Low | Anything | Low (but fragile) |\n\nAlways prefer the top options. Drop to lower options only when the provider does not support schemas.\n\n## Tool-calling for structured output (recommended)\n\nThe most reliable pattern: define the output structure as a tool schema, force the model to call that tool.\n\n```python\nimport anthropic\nfrom pydantic import BaseModel\n\nclass InvoiceExtraction(BaseModel):\n    vendor_name: str\n    invoice_number: str\n    total_amount: float\n    currency: str\n    due_date: str | None = None\n\nclient = anthropic.Anthropic()\n\ndef extract_invoice(text: str) -> InvoiceExtraction:\n    response = client.messages.create(\n        model=\"claude-sonnet-4-5\",\n        max_tokens=512,\n        tools=[{\n            \"name\": \"extract_invoice\",\n            \"description\": \"Extract structured data from an invoice\",\n            \"input_schema\": InvoiceExtraction.model_json_schema(),\n        }],\n        tool_choice={\"type\": \"tool\", \"name\": \"extract_invoice\"},\n        messages=[{\"role\": \"user\", \"content\": f\"Extract invoice data:\\n{text}\"}],\n    )\n    tool_input = response.content[0].input\n    return InvoiceExtraction.model_validate(tool_input)\n```\n\nThe `tool_choice` forces the model to produce only the tool call — no prose wrapping the JSON.\n\n## OpenAI JSON schema `response_format`\n\n```python\nfrom openai import OpenAI\nfrom pydantic import BaseModel\n\nclass SupportTicket(BaseModel):\n    category: str\n    priority: str  # \"low\" | \"medium\" | \"high\"\n    summary: str\n\nclient = OpenAI()\n\ndef classify_ticket(text: str) -> SupportTicket:\n    response = client.beta.chat.completions.parse(\n        model=\"gpt-4o-2024-11-20\",\n        response_format=SupportTicket,\n        messages=[{\"role\": \"user\", \"content\": text}],\n    )\n    return response.choices[0].message.parsed\n```\n\n`client.beta.chat.completions.parse` handles schema generation and parsing automatically with Pydantic models.\n\n## JSON mode (lightweight)\n\nWhen you need valid JSON but the schema varies dynamically:\n\n```python\nresponse = client.chat.completions.create(\n    model=\"gpt-4o\",\n    response_format={\"type\": \"json_object\"},\n    messages=[{\n        \"role\": \"user\",\n        \"content\": \"Return a JSON object with keys: name, score, reason.\"\n    }],\n)\ndata = json.loads(response.choices[0].message.content)\n```\n\nJSON mode guarantees valid JSON syntax but does not enforce key names or types — validate manually.\n\n## Validation and error handling\n\nEven with server-enforced schemas, add a validation layer:\n\n```python\nimport json\nfrom pydantic import ValidationError\n\ndef safe_extract(text: str) -> InvoiceExtraction | None:\n    try:\n        return extract_invoice(text)\n    except ValidationError as e:\n        logging.error(\"Schema validation failed: %s\", e)\n        return None\n    except anthropic.BadRequestError as e:\n        # Happens when input is too short or nonsensical for extraction\n        logging.warning(\"Model refused structured output: %s\", e)\n        return None\n```\n\n## Handling enum and union types\n\nDefine enums explicitly in the schema to constrain categorical fields:\n\n```python\nfrom enum import Enum\nfrom pydantic import BaseModel\n\nclass Priority(str, Enum):\n    low = \"low\"\n    medium = \"medium\"\n    high = \"high\"\n\nclass Ticket(BaseModel):\n    category: str\n    priority: Priority  # Model can only output \"low\", \"medium\", or \"high\"\n    summary: str\n```\n\nPydantic's `.model_json_schema()` generates the `enum` constraint automatically.\n\n## How to pick\n\n1. **Known output schema?** Use tool-calling (Anthropic) or `response_format` with Pydantic (OpenAI).\n2. **Schema varies at runtime?** Build the JSON schema programmatically and pass it directly.\n3. **Just need valid JSON, any structure?** JSON mode is fine.\n4. **No structured output API available?** Use few-shot examples of the exact output format + manual parsing. Avoid this path where possible.\n\n## Common mistakes\n\n**Not forcing tool use.** Specifying a tool without `tool_choice: forced/specific` lets the model decide to call it or not — it often does not.\n\n**Nested schemas without depth limits.** Deeply nested Pydantic models with many optional fields confuse models into omitting required keys. Flatten where possible.\n\n**Skipping validation after parsing.** Trust but verify — even server-constrained output can fail Pydantic validators if you have custom validators or cross-field rules.\n\n**Large lists without count hints.** Asking for \"a list of all items\" can produce truncated results. Specify `max_items` in the description or add a `count` field to prompt completeness.\n\n## What to skip\n\n- **Regex extraction from LLM output** — it is fragile, model-version-sensitive, and entirely unnecessary with modern APIs.\n- **Multi-round extraction loops** where you ask the model to fix its own JSON — use server-enforced schemas and it will not break in the first place.\n\n## FAQ\n\n**Is tool-calling schema enforcement 100% reliable?**\nNear-100% on modern frontier models. Rare failures occur on extremely complex nested schemas. Validate defensively regardless.\n\n**Can I use structured outputs with streaming?**\nYes — OpenAI's `stream=True` with `response_format` works; parse the completed stream before Pydantic validation.\n\n**What about open-source models?**\nLlama 4, Mistral Large 2, and Phi-4 all support function calling schemas. Quality drops for complex schemas compared to frontier models.\n\n**How do I handle optional fields that the model keeps omitting?**\nMake them `Optional` in the Pydantic model with a `None` default, and add a description asking the model to include them when available.\n\n## Where to go next\n\n- [Prompt engineering in 2026](/blog/prompt-engineering-2026)\n- [Build an AI agent from scratch in 2026](/blog/build-an-ai-agent-from-scratch-2026)\n- [Best LLM frameworks in 2026](/blog/best-llm-frameworks-2026)","hue":306,"metaTitle":"Structured Outputs Guide 2026: JSON Mode and Tool Schemas","metaDescription":"Complete 2026 guide to structured outputs from LLMs. JSON mode, tool-calling schemas, Pydantic validation, and error handling patterns with real code.","keywords":["structured outputs llm 2026","json mode openai 2026","tool calling schema 2026","pydantic llm output 2026","structured output anthropic","llm json extraction 2026","reliable json from llm","constrained generation 2026","llm output validation","structured data extraction 2026"],"tldr":["**Use tool-calling schemas for the most reliable structured output** — the model is constrained to the schema, not just asked to follow it.","**JSON mode is the lightweight option** — valid JSON guaranteed, but structure within the JSON is not enforced.","**Pydantic models are the cleanest Python integration** — generate schemas from models, validate the response automatically.","**Always validate after parsing** — even with constrained output, defensive validation catches edge cases.","**Skip:** regex-parsing LLM output in 2026 — structured output APIs make it unnecessary."],"readTime":"4 min read","wordCount":901},"related":[{"slug":"structured-outputs-llm-2026","title":"Structured outputs from LLMs in 2026: JSON, grammars, and what actually works","excerpt":"Getting reliable JSON from LLMs is a solved problem in 2026 — if you use the right approach. Here is what works, what breaks, and how to choose your method.","category":"ai","date":"March 19, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":220},{"slug":"json-mode-vs-tool-calling-2026","title":"JSON Mode vs Tool Calling: Which One You Actually Want","excerpt":"Both produce structured output and they solve different problems. Here is when to use each, why mixing them causes trouble, and how reliability differs.","category":"ai","date":"June 26, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":210},{"slug":"json-schema-validation-guide-2026","title":"JSON Schema Validation: Where to Put the Boundary","excerpt":"Validation belongs at trust boundaries and nowhere else. Here is how to write schemas that produce useful errors, and how to keep them in sync with your types.","category":"code","date":"August 13, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":132}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"side-hustles-that-pay-2026","title":"Side Hustles That Pay in 2026: Real Income, Realistic Timelines","excerpt":"The best side hustles in 2026 match your skills to real demand with minimal startup cost. Here are the options with honest earning ranges and time required.","category":"finance","date":"April 29, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":310},"older":{"slug":"vector-databases-2026","title":"Vector databases in 2026: which one should you actually use?","excerpt":"The vector database market matured fast in 2026. Most apps do not need a dedicated vector DB — here is how to decide what you actually need.","category":"code","date":"April 29, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":313}}