CTCaliberTek
Course: Prompt Engineering: End to End · Lesson 4 of 6Log in to track your progress and earn a certificate when you finish the course.
AI & Machine Learning

Getting Structured JSON Output from an LLM

Published September 9, 2026·Updated October 29, 2026·8 min read
Like SaveLog in to like, save, and track progress.

Introduction

Applications need data they can rely on, not paragraphs they must parse. Asking nicely for JSON often works, but occasionally returns extra text or missing fields. Structured outputs constrain the response to a schema you define, so your code receives valid, typed data. This lesson shows two ways to do it in Python.

Prerequisites

  • Calling an LLM API lesson
  • pip install anthropic pydantic

Step-by-Step Guide

Step 1: Define the shape of the data

Describe what you want back with a Pydantic model. Each field has a name and a type, and the model doubles as validation and documentation.

from pydantic import BaseModel
from typing import List

class ContactInfo(BaseModel):
    name: str
    email: str
    plan: str
    interests: List[str]
    demo_requested: bool

Step 2: Parse the response into your model

The SDK's parse helper takes your model and returns a validated instance, so you work with real Python objects instead of text.

import anthropic

client = anthropic.Anthropic()

response = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=16000,
    messages=[{
        "role": "user",
        "content": "Extract: Jane Doe (jane@co.com) wants Enterprise, interested in API and SDKs, wants a demo.",
    }],
    output_format=ContactInfo,
)

contact = response.parsed_output
print(contact.name, contact.interests)

Step 3: Or supply a raw JSON schema

If you are not using Pydantic, pass a JSON schema in the output configuration. The response is then guaranteed to be valid JSON matching it, which you load with the json module. Set additionalProperties to false and list required fields.

import json

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    messages=[{"role": "user", "content": "Extract info: John Smith (john@example.com) wants the Enterprise plan."}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                    "plan": {"type": "string"},
                },
                "required": ["name", "email", "plan"],
                "additionalProperties": False,
            },
        }
    },
)

text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)

Step 4: Still validate the meaning

A schema guarantees the shape, not the truth. A valid email field can still hold the wrong email. Add business checks in your code, such as format rules and cross-checks with other data, and decide what to do with records that fail.

Step 5: Handle failures and keep the schema simple

Check the stop reason before trusting a response, since a request can end early or be declined. Keep schemas small and fields well named, use enums for fixed choices, and make fields optional only when the data may legitimately be absent.

FAQs

Why not just ask for JSON in the prompt?
It usually works but can fail unpredictably with extra commentary, code fences, or missing keys. Schema-constrained output removes that class of failure and the parsing code that went with it.
Does structured output work with every model and feature?
Support varies by model and provider, and some features, such as certain citation options, cannot be combined with it. Check the documentation for your model.

Summary

Define a schema, let the SDK constrain and parse the output, and keep validating the content in your own code. You get typed data you can trust to have the right shape.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.