# Reasoning and structured output

Reasoning models may generate internal reasoning before their visible answer. Structured-output options request a particular answer format; these are separate features and both depend on the selected model.

## Budget for reasoning

`max_tokens` is an output budget, not a guarantee of that many visible words. Reasoning can consume part or all of it.

If a response has little visible content, inspect the finish reason and token usage before assuming nothing was generated. A `length` finish reason suggests the output budget was reached.

Start with a budget appropriate to the task and cap it deliberately. Increasing the budget can increase latency and cost; a very small budget can prevent a reasoning model from ever reaching its answer.

## Read reasoning fields defensively

Providers may expose optional fields such as `reasoning_content` or `reasoning`. Do not assume one field exists on every model, or that reasoning appears in `content`.

~~~python
# Inside a Python streaming loop:
for choice in chunk.choices:
    delta = choice.delta
    reasoning = (
        getattr(delta, "reasoning_content", None)
        or getattr(delta, "reasoning", None)
    )
    if reasoning:
        print(reasoning, end="", flush=True)
    if delta.content:
        print(delta.content, end="", flush=True)
~~~

For a UI, keep optional reasoning and the final answer in separate buffers. Do not require visible reasoning as a condition for a successful answer.

## Request JSON

Some models accept `response_format` with `json_object`. This option is forwarded; V1 does not implement a universal JSON-constraining decoder for every backend.

~~~python
import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.simplellm.eu/v1",
    api_key=os.environ["SIMPLELLM_API_KEY"],
    max_retries=0,
)
response = client.chat.completions.create(
    model=os.environ["SIMPLELLM_MODEL"],
    messages=[
        {"role": "system", "content": "Return JSON with exactly title (string) and tags (array of strings)."},
        {"role": "user", "content": "Summarize this topic: HTTP caching."},
    ],
    response_format={"type": "json_object"},
    max_tokens=2048,
)
choice = response.choices[0]
if choice.finish_reason == "length":
    raise RuntimeError("Output truncated; do not consume partial JSON")
data = json.loads(choice.message.content or "")
if not isinstance(data, dict) or set(data) != {"title", "tags"}:
    raise ValueError("Unexpected JSON fields")
if not isinstance(data["title"], str) or not isinstance(data["tags"], list):
    raise ValueError("Unexpected JSON types")
if not all(isinstance(tag, str) for tag in data["tags"]):
    raise ValueError("Tags must be strings")
print(data)
~~~

Choose a model supporting this option. If it rejects the parameter, use a supported model or a prompt-based format with your own validation; do not describe a prompt alone as a strict format guarantee.

## JSON schemas and provider options

`json_schema`, strict schema enforcement, reasoning-effort settings, and model-specific thinking switches are not universal V1 guarantees. Confirm support for the exact model and configuration before making your application depend on them.

Regardless of the output mode, validate types, required fields, and application constraints before using generated values. JSON syntax validity is not equivalent to a correct or safe business decision.

## Related guides

[Tool calling](/docs/tools.html) covers executable actions, while [streaming](/docs/streaming.html) explains incomplete responses and final usage events.
