← Back to DevBytes

Structured Outputs with OpenAI: Enforcing JSON Schema

Introduction to Structured Outputs

When building applications powered by Large Language Models (LLMs), developers often need the model's output to be machine-readable. Traditionally, developers prompted the model to output JSON and then used regular expressions or JSON parsers to extract the data. However, models would occasionally output malformed JSON, include conversational filler, or omit required fields. OpenAI's Structured Outputs feature solves this problem by guaranteeing that the model's response will conform to a provided JSON Schema.

What are Structured Outputs?

Structured Outputs is a feature in the OpenAI API that allows developers to pass a JSON Schema as part of the request. The model is then constrained to generate a response that strictly adheres to that schema. Instead of hoping the model outputs valid JSON, the API enforces it at the decoding level, ensuring the output is syntactically correct and semantically aligned with your defined types, properties, and required fields.

Why Structured Outputs Matter

Enforcing a JSON Schema provides several critical benefits for production applications:

Getting Started with JSON Schema Enforcement

To use Structured Outputs, you must use a compatible model (such as gpt-4o-2024-08-06 or later) and define your schema within the response_format parameter of your API call. You will set the type to json_schema and provide the schema definition.

Basic Example: Defining a Schema

Let's imagine we are building an application that extracts user information from unstructured text. We want the model to return a JSON object containing the user's name, age, and email address.

from openai import OpenAI
import json

client = OpenAI(api_key="your-api-key")

# Define the JSON Schema
user_schema = {
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "The full name of the user."
        },
        "age": {
            "type": "integer",
            "description": "The age of the user in years."
        },
        "email": {
            "type": "string",
            "description": "The user's email address."
        }
    },
    "required": ["name", "age", "email"],
    "additionalProperties": False
}

response = client.chat.completions.create(
    model="gpt-4o-2024-08-06",
    messages=[
        {
            "role": "system",
            "content": "Extract the user's information from the provided text."
        },
        {
            "role": "user",
            "content": "John Doe is 32 years old. You can reach him at john.doe@example.com."
        }
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "user_extraction",
            "schema": user_schema,
            "strict": True
        }
    }
)

# Parse the guaranteed JSON response
extracted_data = json.loads(response.choices[0].message.content)
print(extracted_data)

In this example, the strict: True flag is crucial. It tells the OpenAI API to strictly enforce the schema, guaranteeing that the output will not contain any properties not defined in the schema and that all required fields will be present.

Advanced Schema Techniques

JSON Schema is highly expressive. You can enforce complex data structures, including nested objects, arrays, and constrained string values using enums.

Nested Objects and Arrays

If you need to extract a list of items or group related properties together, you can nest schemas. For example, if we want to extract a user's profile along with a list of their previous addresses, we can define an array of objects.

advanced_schema = {
    "type": "object",
    "properties": {
        "username": {
            "type": "string"
        },
        "is_active": {
            "type": "boolean"
        },
        "addresses": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "street": {"type": "string"},
                    "city": {"type": "string"},
                    "zip_code": {"type": "string"}
                },
                "required": ["street", "city", "zip_code"],
                "additionalProperties": False
            }
        }
    },
    "required": ["username", "is_active", "addresses"],
    "additionalProperties": False
}

Using Enums for Constrained Choices

Sometimes you want the model to categorize data into predefined buckets. Using the enum keyword restricts the model's output to only the values you specify.

ticket_schema = {
    "type": "object",
    "properties": {
        "ticket_id": {"type": "string"},
        "category": {
            "type": "string",
            "enum": ["billing", "technical", "general", "feedback"]
        },
        "priority": {
            "type": "string",
            "enum": ["low", "medium", "high", "urgent"]
        }
    },
    "required": ["ticket_id", "category", "priority"],
    "additionalProperties": False
}

Best Practices for Structured Outputs

To get the most out of Structured Outputs, consider the following best practices:

Conclusion

Structured Outputs represent a significant leap forward in building robust LLM applications. By enforcing a JSON Schema, developers can eliminate the fragility of prompt-based JSON extraction and build reliable pipelines that seamlessly integrate AI outputs into existing software systems. By understanding how to define clear schemas, leverage advanced features like enums and nested objects, and adhere to best practices, you can unlock highly predictable and production-ready behavior from OpenAI models.

— Ad —

Google AdSense will appear here after approval

← Back to all articles