Skip to content
Relaykit
DocsStructured output

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.

Updated View .md
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 idjson_schemajson_objectForced tool
claude-opus-5-5No (dropped)No (dropped)Yes
claude-sonnet-5No (dropped)No (dropped)Yes
claude-haiku-4-5No (dropped)No (dropped)Yes
gpt-6.1-solYesYesYes
gpt-5.6-solYesYesYes
gpt-5.6-terraYesYesYes
grok-4.6YesUse json_schemaYes
claude-sonnet-5-5No (dropped)No (dropped)Yes
grok-4.7YesUse json_schemaYes
deepseek-v4-flashNoYesYes
deepseek-v4-proNoYesYes

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.

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.

Forced tool, works for Claude and others
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.

Anthropic output_config
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.

↑ ↓ moveEnter openEsc close