Skip to main content
Structured output makes the model return strictly in a JSON structure, ready to parse directly in your program.

JSON mode (basic)

{
  "model": "gpt-5.5",
  "response_format": { "type": "json_object" },
  "messages": [
    {"role": "system", "content": "You must return only valid JSON"},
    {"role": "user", "content": "Return a user object with name and age"}
  ]
}
Notes:
  • It only guarantees valid JSON is returned, but field names / types may not match what you expect.
  • You must hint “return JSON” in the prompt, or the model may refuse due to safety policy.

JSON Schema (strict)

{
  "model": "gpt-5.5",
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "user",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "name": {"type": "string"},
          "age": {"type": "integer"},
          "hobbies": {"type": "array", "items": {"type": "string"}}
        },
        "required": ["name", "age", "hobbies"],
        "additionalProperties": false
      }
    }
  },
  "messages": [{"role": "user", "content": "Generate a random user"}]
}
strict: true makes the model apply token-level constraints during generation, so the output is 100% schema-compliant.

Supported models

  • OpenAI: gpt-5.5 / gpt-5.4-mini / the o3 series.
  • Claude: supported indirectly (prompt guidance + tool enforcement).
  • Gemini: equivalent constraints via responseSchema.

Python (Pydantic)

The OpenAI Python SDK provides a parse method that takes a Pydantic model directly:
from pydantic import BaseModel
from openai import OpenAI

client = OpenAI(api_key="sk-...", base_url="https://openp.ai/v1")

class User(BaseModel):
    name: str
    age: int
    hobbies: list[str]

resp = client.beta.chat.completions.parse(
    model="gpt-5.5",
    messages=[{"role": "user", "content": "Generate a random user"}],
    response_format=User,
)

user: User = resp.choices[0].message.parsed
print(user.name, user.age)

Gemini

{
  "contents": [{"role": "user", "parts": [{"text": "Generate a random user"}]}],
  "generationConfig": {
    "responseMimeType": "application/json",
    "responseSchema": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"}
      },
      "required": ["name", "age"]
    }
  }
}

Claude

Claude has no dedicated response_format, but you can achieve an equivalent effect with tool enforcement:
resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=[{
        "name": "save_user",
        "input_schema": {
            "type": "object",
            "properties": {"name":{"type":"string"},"age":{"type":"integer"}},
            "required": ["name","age"]
        }
    }],
    tool_choice={"type": "tool", "name": "save_user"},
    messages=[{"role": "user", "content": "Generate a random user"}]
)
user = resp.content[0].input  # dict

Notes

  • JSON Schema constraints slightly increase latency and cost (the model re-validates during generation).
  • Even with strict on, make the schema as explicit as possible (use enum, pattern, min/max, etc.).
  • Nesting depth ≤ 5, ≤ 100 fields, arrays ≤ 5000 elements; the exact limits are set by the upstream.