Structured outputs
Constrain a model's response to a JSON schema so you get parseable, predictable JSON on sference.
Structured outputs
Most production callers do not want prose — they want a JSON object they can parse. sference supports the OpenAI structured-output fields so you can constrain the response to a schema instead of hoping the model formats it correctly and repairing failures with regex.
JSON schema
Pass response_format with type: "json_schema" on /v1/chat/completions, or text.format on /v1/responses:
from openai import OpenAI
client = OpenAI(
base_url="https://api.sference.com/v1",
api_key=os.environ["SFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="zai-org/GLM-5.3",
messages=[
{"role": "system", "content": "Extract the fields from the support ticket."},
{"role": "user", "content": ticket_text},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "ticket",
"strict": True,
"schema": {
"type": "object",
"properties": {
"severity": {"type": "string", "enum": ["low", "medium", "high"]},
"product": {"type": "string"},
"summary": {"type": "string"},
},
"required": ["severity", "product", "summary"],
"additionalProperties": False,
},
},
},
)
ticket = json.loads(response.choices[0].message.content)On the Responses API the same schema goes in text.format:
response = client.responses.create(
model="zai-org/GLM-5.3",
input=ticket_text,
text={
"format": {
"type": "json_schema",
"name": "ticket",
"schema": {...},
"strict": True,
}
},
)JSON mode
If you only need valid JSON and not a specific shape, {"type": "json_object"} is cheaper and less constrained. Remember to describe the shape in the prompt — JSON mode guarantees syntax, not structure:
response = client.chat.completions.create(
model="zai-org/GLM-5.3",
messages=[
{"role": "system", "content": "Reply with JSON: {\"sentiment\": \"positive\"|\"negative\", \"confidence\": 0-1}"},
{"role": "user", "content": review},
],
response_format={"type": "json_object"},
)Schema guidance
Write schemas that are easy to satisfy, not just correct:
- Use
enumfor closed sets.{"type": "string", "enum": ["low", "medium", "high"]}is far more reliable than asking for "a severity level". - Set
additionalProperties: falseand list every field inrequired. Optional fields invite the model to omit them inconsistently. - Prefer flat objects. Deeply nested schemas fail more often and are harder to debug.
- Avoid
oneOf/anyOfwhere a plain optional field would do. Unions are the most common source of malformed output. - Keep field names and descriptions explicit. The description is part of the prompt.
Reasoning models and constrained output
On reasoning models, max_output_tokens covers hidden reasoning as well as the answer. If the cap is too low the response can settle incomplete with a partially-formed object. Collect on the finish reason rather than assuming a parseable body, and give reasoning models headroom above the size of the object you expect.
Failure handling
Even with a schema, treat the response as untrusted input:
try:
data = json.loads(response.choices[0].message.content)
except json.JSONDecodeError:
# Retry once, or fall back to a repair pass
...A constrained decode makes malformed JSON rare, not impossible — truncation from a token cap, an upstream error mid-stream, and model-specific quirks all still produce unparseable output occasionally. Validate against your schema before use, and retry on failure rather than writing the bad value.
Where it is supported
| Surface | Field |
|---|---|
/v1/chat/completions | response_format (json_schema, json_object) |
/v1/responses | text.format |
/v1/messages | Not supported — Anthropic's Messages API has no equivalent field |
| Batch rows | Same body shape as the surface the row targets |