# Structured outputs

> JSON mode and JSON Schema responses with response_format.

Use `response_format` to get JSON back instead of free text. There are two modes:

| Mode | `response_format` | What you get |
| --- | --- | --- |
| JSON mode | `{"type": "json_object"}` | Valid JSON, in a shape you describe in the prompt. |
| Structured outputs | `{"type": "json_schema", "json_schema": {…}}` | JSON that follows your JSON Schema. |

Model pages show which modes each model supports. Filter the [catalogue](https://clonvo.com/models.md) by **Structured outputs** to find models with schema support.

## JSON Schema

```python
completion = client.chat.completions.create(
    model="openai/gpt-6.1-sol",
    messages=[
        {"role": "system", "content": "Extract the event details."},
        {"role": "user", "content": "Team offsite in Porto on 12 March, 18 people."},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "event",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "city": {"type": "string"},
                    "date": {"type": "string"},
                    "attendees": {"type": "integer"},
                },
                "required": ["title", "city", "date", "attendees"],
                "additionalProperties": False,
            },
        },
    },
)

import json
event = json.loads(completion.choices[0].message.content)
```

The JSON arrives as a string in `message.content`. Parse it yourself, or use the OpenAI SDK’s `parse` helpers, which work the same way through Clonvo:

```python
from pydantic import BaseModel

class Event(BaseModel):
    title: str
    city: str
    date: str
    attendees: int

completion = client.beta.chat.completions.parse(
    model="openai/gpt-6.1-sol",
    messages=[{"role": "user", "content": "Team offsite in Porto on 12 March, 18 people."}],
    response_format=Event,
)
event = completion.choices[0].message.parsed
```

## JSON mode

```js
const completion = await client.chat.completions.create({
  model: "mistralai/mistral-large-4",
  messages: [
    { role: "system", content: "Reply with a JSON object with keys `summary` and `tags`." },
    { role: "user", content: "Notes from today’s standup…" },
  ],
  response_format: { type: "json_object" },
});
const data = JSON.parse(completion.choices[0].message.content);
```

With JSON mode, describe the shape you want in the prompt; the model guarantees valid JSON, not a particular structure.

## How Clonvo handles providers

`response_format` never causes a `400` because of the model. When a model has native support, Clonvo uses it; otherwise it falls back to a best-effort approach, so check the model page if you need a guarantee.

| Provider | `json_schema` | `json_object` |
| --- | --- | --- |
| OpenAI-compatible (OpenAI, xAI, DeepSeek, Mistral, Moonshot, Meta, Alibaba) | Sent as is on models with structured outputs. Otherwise Clonvo asks for JSON mode (where the model has it) and adds your schema to the prompt as an instruction. | Sent as is on models with JSON mode. Otherwise Clonvo adds an instruction to reply with JSON. |
| Anthropic | Sent as Anthropic’s native structured output format. On models without it, Clonvo defines a single tool with your schema, makes the model call it, and returns the tool’s input as `message.content`. | An instruction to reply with JSON is added to the system prompt. |
| Gemini | JSON output, with your schema as Gemini’s response schema on models with structured outputs. | JSON output. |
| Free models | Sent to OpenRouter as is. | Sent to OpenRouter as is. |

Either way the JSON arrives as a string in `message.content`.

## Tips

-   Keep `strict: true` schemas simple: mark every property as required and set `additionalProperties: false`. Providers differ in which JSON Schema keywords they support.
-   Check `finish_reason`. If it’s `length`, the JSON was cut off; raise `max_completion_tokens`.
-   Validate the parsed result before trusting it, especially on models without schema enforcement.