0Pricing
AI Engineering Academy · Lesson

JSON Mode and response_format

Enable JSON mode in the OpenAI API, craft prompts that consistently produce valid JSON, and handle the cases where the model still manages to break the format.

JSON Mode and response_format is a free AI Engineering Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Problem with Unstructured LLM Output

By default, LLMs return free-form text. Parsing that text to extract structured data is fragile: a change in model behavior, a slight prompt variation, or an edge case in the input can change the output format unexpectedly, breaking your parser and crashing your application.

Consider asking an LLM to 'return the user's name and age as JSON'. Sometimes it returns {"name":"Alice","age":30}, sometimes it wraps it in a markdown code block, sometimes it adds explanatory prose. Any of these variations requires different parsing logic. Reliable machine-readable output requires forcing the model to follow a structure, not hoping it does.

OpenAI JSON Mode

OpenAI introduced JSON mode via the response_format parameter. When set to {"type": "json_object"}, the model is constrained to always return a valid JSON object. The model will never output anything that is not valid JSON — no markdown wrappers, no explanatory text, no trailing prose.

import openai
import json

client = openai.OpenAI()

response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[
        {
            'role': 'system',
            'content': 'Extract information from the text and return valid JSON only.'
        },
        {
            'role': 'user',
            'content': 'John Smith, age 34, works as a software engineer in Austin.'
        }
    ],
    response_format={'type': 'json_object'}  # Guarantee valid JSON output
)

# Safe to parse - guaranteed valid JSON
data = json.loads(response.choices[0].message.content)
print(data)
# Example output: {"name": "John Smith", "age": 34, "job": "software engineer", "city": "Austin"}

JSON Mode Caveats

JSON mode guarantees valid JSON syntax but does NOT guarantee that the JSON contains the fields you want. The model still decides what keys to include, their names, and the data types it uses. You might ask for a name field and get back full_name instead, or ask for an array and get a string.

Also note: JSON mode requires that you mention JSON in your prompt. If you enable JSON mode but your prompt does not ask for JSON output, the model may produce an empty JSON object or refuse to generate. Always explicitly instruct the model to respond in JSON format in the system or user message.

Structured Outputs with Pydantic (Preview)

OpenAI's newer Structured Outputs feature goes further than JSON mode: you provide a JSON Schema, and the model is constrained to return exactly that schema — specific field names, types, and nesting. This eliminates the schema inconsistency problem of basic JSON mode.

The Python SDK accepts Pydantic models directly, automatically converting them to JSON Schema and deserializing the response back into a typed Python object. This is the cleanest way to get reliable structured data from an LLM in Python.

import openai
from pydantic import BaseModel
from typing import Optional

client = openai.OpenAI()

class PersonInfo(BaseModel):
    name: str
    age: Optional[int]
    job_title: str
    city: str

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract person information from the text.'},
        {'role': 'user', 'content': 'Sarah Chen, 28 years old, is a data scientist based in Seattle.'}
    ],
    response_format=PersonInfo  # Pass Pydantic model directly
)

# Already deserialized into a PersonInfo instance
person = completion.choices[0].message.parsed
print(person.name)       # Sarah Chen
print(person.age)        # 28
print(person.job_title)  # data scientist
print(person.city)       # Seattle

Crafting Prompts for Consistent JSON

Even with JSON mode enabled, your prompt design affects output quality. Best practices for JSON prompts:

  • Name the fields explicitly: Tell the model exactly what fields you expect, not just 'return JSON'
  • Specify types: 'Return the price as a number, not a string' prevents type mismatches
  • Define enumerations: 'The category must be one of: bug, feature, question' prevents unexpected values
  • Handle missing data: 'If a field is not present in the text, return null for that field'

Think of your prompt as a partial JSON Schema written in prose. The more precisely you specify the output contract, the more reliably the model will follow it.

Reliable JSON Without Structured Outputs

If you are using a model that does not support structured outputs or JSON mode, you can still get reliable JSON by being very explicit in your prompt and parsing defensively. The key technique is to ask the model to wrap its JSON in XML tags, which makes extraction unambiguous regardless of any surrounding text.

import re
import json
import openai

client = openai.OpenAI()

def extract_json_from_response(text):
    # Try direct parse first
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass
    # Try extracting from XML tags
    match = re.search(r'<json>(.*?)</json>', text, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    # Try extracting from JSON object pattern
    match = re.search(r'({.*})', text, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    raise ValueError('No valid JSON found in response')

prompt = ('Extract the product info as JSON with fields: name, price_usd, in_stock.\n'
          'Wrap your JSON in <json></json> tags.\n\n'
          'Product: Blue Wireless Headphones cost $89.99, in stock.')

resp = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': prompt}]
)
result = extract_json_from_response(resp.choices[0].message.content)
print(result)

Nested JSON Structures

JSON mode and structured outputs handle arbitrarily nested structures. You can define Pydantic models with lists, nested objects, and optional fields, and the model will populate the full structure correctly.

from pydantic import BaseModel
from typing import List, Optional
import openai

client = openai.OpenAI()

class LineItem(BaseModel):
    product: str
    quantity: int
    unit_price: float

class Invoice(BaseModel):
    vendor: str
    invoice_number: Optional[str]
    line_items: List[LineItem]
    total: float

raw_text = '''
INVOICE #INV-2025-0042
From: TechSupplies Inc.
- 3x USB Hubs at $24.99 each
- 1x 4K Monitor at $399.00
Total: $474.97
'''

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract invoice data from the provided text.'},
        {'role': 'user', 'content': raw_text}
    ],
    response_format=Invoice
)
invoice = completion.choices[0].message.parsed
print(f'Vendor: {invoice.vendor}')
print(f'Items: {len(invoice.line_items)}')
print(f'Total: ${invoice.total}')

Handling Refusals in Structured Mode

When using structured outputs, the model may sometimes refuse to complete the extraction — for example, if the input text is empty, harmful, or clearly does not contain the requested information. In structured outputs mode, refusals are indicated by the refusal field on the message rather than the parsed field.

Always check for refusals before accessing the parsed result, especially when processing user-provided or untrusted input that might trigger content filters.

import openai
from pydantic import BaseModel

client = openai.OpenAI()

class ProductInfo(BaseModel):
    name: str
    price_usd: float

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract product name and price.'},
        {'role': 'user', 'content': 'Tell me how to build a weapon.'}
    ],
    response_format=ProductInfo
)

message = completion.choices[0].message
if message.refusal:
    print('Model refused:', message.refusal)
else:
    product = message.parsed
    print(f'Name: {product.name}, Price: {product.price_usd}')

JSON for Multi-Value Extraction

JSON mode is especially powerful for extracting multiple distinct pieces of information from a single piece of text in one API call, rather than making separate calls for each field. Extract all the fields you need at once and parse the result into your data model.

This reduces both API calls and cost compared to asking for one field at a time. A single well-structured extraction prompt can pull names, dates, monetary amounts, sentiment, action items, and classification labels all at once from a single document.

Streaming with JSON Mode

JSON mode is compatible with streaming, but with an important constraint: the JSON is only valid once the complete response has been streamed. Individual token chunks of JSON are not valid JSON on their own. This means you must accumulate the full streaming response before parsing when using JSON mode.

For streaming applications that also need JSON output, use structured outputs with streaming, accumulate all chunks, then parse when the stream finishes. Alternatively, design your streaming UI to show a loading state while the JSON accumulates, then render the parsed result.

When to Use JSON Mode vs Structured Outputs

Choose the right tool for your scenario:

  • JSON mode: Simple cases, prototyping, or when you only need valid JSON syntax without strict field enforcement. Use when the model deciding field names is acceptable.
  • Structured outputs with Pydantic: Production systems that parse results programmatically. Use when you need guaranteed field names, types, and nested structure. This is the recommended approach for any extraction pipeline.
  • XML tag extraction: Fallback for models that do not support JSON mode or when you need to extract JSON embedded in a longer response.

Quick Check

Test your understanding of AI Engineering concepts from this lesson.

Lesson Recap

In this lesson you learned: JSON mode via response_format guarantees valid JSON syntax but not specific field schemas, structured outputs with Pydantic models enforce exact field names and types using JSON Schema, and always check for refusals before accessing parsed results when processing untrusted input. Next up we explore defining Pydantic schemas in depth for typed extraction from complex documents.

Frequently asked questions

Is the “JSON Mode and response_format” lesson free?

Yes — the full text of “JSON Mode and response_format” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.

What will I learn in “JSON Mode and response_format”?

Enable JSON mode in the OpenAI API, craft prompts that consistently produce valid JSON, and handle the cases where the model still manages to break the format. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Engineering Academy?

No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “JSON Mode and response_format” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Engineering Academy lesson?

Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. JSON Mode and response_format
  2. Structured Outputs with Pydantic
  3. Extracting Data from Unstructured Text
  4. Validating and Retrying Bad Outputs
← Back to AI Engineering Academy