# Structured outputs

> Constrain the response to plain text, any JSON object, or a JSON schema with response_format.

Source: https://docs.serenityedge.ai/docs/structured-outputs/

The `response_format` parameter controls the shape of the answer. Serenity Edge supports the three OpenAI formats.

| `response_format.type` | Result |
| --- | --- |
| `text` (default) | Free text. |
| `json_object` | Any syntactically valid JSON object. You describe the expected fields in the prompt. |
| `json_schema` | A JSON object that satisfies the schema you provide. With `strict: true` the match is guaranteed. |

## JSON object

Ask for JSON in the prompt and set the format. The output is guaranteed to parse, but its fields are up to the model.

```bash
curl https://api.serenityedge.ai/v1/chat/completions \
  -H "Authorization: Bearer $SERENITY_EDGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "orion-plus",
    "response_format": {"type": "json_object"},
    "messages": [
      {"role": "system", "content": "Answer with a JSON object with the keys sentiment and confidence."},
      {"role": "user", "content": "The delivery was late but the support team sorted it out quickly."}
    ]
  }'
```

## JSON schema

Provide a schema and the answer conforms to it. This is the recommended option whenever you parse the output into typed data.

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.serenityedge.ai/v1", api_key="YOUR_SERENITY_EDGE_API_KEY")

schema = {
    "type": "object",
    "properties": {
        "invoice_number": {"type": "string"},
        "total": {"type": "number"},
        "currency": {"type": "string"},
    },
    "required": ["invoice_number", "total", "currency"],
    "additionalProperties": False,
}

response = client.chat.completions.create(
    model="orion-pro",
    messages=[{"role": "user", "content": "Extract the invoice number and total from: Invoice INV-2041, total 1,250.00 EUR."}],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "invoice", "strict": True, "schema": schema},
    },
)

print(response.choices[0].message.content)
# {"invoice_number": "INV-2041", "total": 1250.0, "currency": "EUR"}
```

With the Python and Node.js SDKs you can also pass a Pydantic model or a Zod schema through the `parse` helpers; they translate to the same `json_schema` request.

### Rules for strict schemas

When `strict` is `true`:

-   Every object must set `additionalProperties: false`.
-   Every property must be listed in `required`. Model optional fields as a union with `null`, for example `{"type": ["string", "null"]}`.
-   Supported keywords are the structural ones: `type`, `properties`, `required`, `items`, `enum`, `anyOf`, `$ref` and `$defs`. Numeric ranges, string patterns and formats are not enforced; validate them on your side.

When `strict` is `false` or omitted, the schema guides the model but the output is not guaranteed to conform.

## Recommendations

-   Keep the schema small. Every property name and description counts as input tokens and constrains generation.
-   Give the schema a meaningful `name` and short property descriptions; the model reads them.
-   Check `finish_reason`. If it is `length`, the JSON may be truncated; raise `max_tokens` or reduce the schema.
-   Combine with [tool calling](https://docs.serenityedge.ai/docs/tool-calling/) freely: `response_format` shapes the final answer, `strict` on a tool shapes its arguments.
