Build
Structured output
Get JSON that matches your schema. response_format with JSON Schema, which models honour it, and the two methods that work for Claude models.
On this page
Structured output means the model answers with JSON in a shape you define, so your code can parse it without guessing. There are three ways to ask for it; which one works depends on the model.
Which method works where
| Model id | json_schema | json_object | Forced tool |
|---|---|---|---|
| claude-opus-5-5 | No (dropped) | No (dropped) | Yes |
| claude-sonnet-5 | No (dropped) | No (dropped) | Yes |
| claude-haiku-4-5 | No (dropped) | No (dropped) | Yes |
| gpt-6.1-sol | Yes | Yes | Yes |
| gpt-5.6-sol | Yes | Yes | Yes |
| gpt-5.6-terra | Yes | Yes | Yes |
| grok-4.6 | Yes | Use json_schema | Yes |
| claude-sonnet-5-5 | No (dropped) | No (dropped) | Yes |
| grok-4.7 | Yes | Use json_schema | Yes |
| deepseek-v4-flash | No | Yes | Yes |
| deepseek-v4-pro | No | Yes | Yes |
json_schema and json_object are values of response_format on chat completions. “Yes” means the provider documents support for it; “No (dropped)” is explained below.
JSON Schema (recommended)
response_format: {"type": "json_schema", ...} with strict: true makes supporting models follow the schema exactly. With strict mode, list every property in required (use a null type for optional values) and set additionalProperties: false.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.oneaiapikey.com/v1", api_key=os.environ["RELAYKIT_API_KEY"])
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": ["string", "null"]},
"topics": {"type": "array", "items": {"type": "string"}},
},
"required": ["name", "email", "topics"],
"additionalProperties": False,
}
reply = client.chat.completions.create(
model="gpt-5.6-terra",
messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
response_format={"type": "json_schema", "json_schema": {"name": "contact", "strict": True, "schema": schema}},
)
contact = json.loads(reply.choices[0].message.content)
print(contact)import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.oneaiapikey.com/v1", apiKey: process.env.RELAYKIT_API_KEY });
const schema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: ["string", "null"] },
topics: { type: "array", items: { type: "string" } },
},
required: ["name", "email", "topics"],
additionalProperties: false,
};
const reply = await client.chat.completions.create({
model: "gpt-5.6-terra",
messages: [{ role: "user", content: "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'" }],
response_format: { type: "json_schema", json_schema: { name: "contact", strict: true, schema } },
});
console.log(JSON.parse(reply.choices[0].message.content));In the Responses API the same schema goes in text.format instead of response_format.
JSON mode
response_format: {"type": "json_object"} only promises valid JSON, not a particular shape. Describe the shape in the prompt, and include the word “JSON” in it (some providers reject the request otherwise). Prefer JSON Schema where the model supports it.
Claude models
Forced tool call (OpenAI format). Define one tool whose parameters are your schema and force it with tool_choice. The arguments of the tool call are your JSON. This works for every model with tool support, so it is also the most portable method.
reply = client.chat.completions.create(
model="claude-sonnet-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
tools=[{"type": "function", "function": {
"name": "save_contact",
"description": "Save the extracted contact.",
"parameters": schema, # the same JSON Schema as above
}}],
tool_choice={"type": "function", "function": {"name": "save_contact"}},
)
contact = json.loads(reply.choices[0].message.tool_calls[0].function.arguments)Native structured output (Anthropic format). On /v1/messages, Anthropic’s output_config.format with a JSON Schema is passed through to Claude, and the reply text is JSON that matches the schema.
import anthropic, json, os
client = anthropic.Anthropic(base_url="https://api.oneaiapikey.com", api_key=os.environ["RELAYKIT_API_KEY"])
message = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
extra_body={"output_config": {"format": {"type": "json_schema", "schema": schema}}},
)
contact = json.loads(message.content[0].text)Always validate
Even with strict schemas, check the result before you use it: a reply cut off by max_tokens (finish_reason: "length") is incomplete JSON, and a model can refuse instead of answering. Parse inside a try, validate with your schema library (Pydantic, Zod, JSON Schema), and retry or fall back when it fails.
Related
- Tool calling: the loop behind forced tools.
- Models: capabilities of each model.