Structured outputs let you enforce a JSON schema on model responses so you can reliably extract structured data, describe images, or keep every reply consistent.
Generating structured JSON
curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{
"model": "gpt-oss",
"messages": [{"role": "user", "content": "Tell me about Canada in one line"}],
"stream": false,
"format": "json"
}'
from ollama import chat
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': 'Tell me about Canada.'}],
format='json'
)
print(response.message.content)
import ollama from 'ollama'
const response = await ollama.chat({
model: 'gpt-oss',
messages: [{ role: 'user', content: 'Tell me about Canada.' }],
format: 'json'
})
console.log(response.message.content)
When using format: "json", always instruct the model to respond in JSON in your prompt for best results.
Generating structured JSON with a schema
Provide a JSON schema to the format field for strictly typed outputs.
It is ideal to also pass the JSON schema as a string in the prompt to ground the model’s response.
curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{
"model": "gpt-oss",
"messages": [{"role": "user", "content": "Tell me about Canada."}],
"stream": false,
"format": {
"type": "object",
"properties": {
"name": {"type": "string"},
"capital": {"type": "string"},
"languages": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "capital", "languages"]
}
}'
Use Pydantic models and pass model_json_schema() to format, then validate the response:from ollama import chat
from pydantic import BaseModel
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': 'Tell me about Canada.'}],
format=Country.model_json_schema(),
)
country = Country.model_validate_json(response.message.content)
print(country)
Serialize a Zod schema with zodToJsonSchema() and parse the structured response:import ollama from 'ollama'
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
const Country = z.object({
name: z.string(),
capital: z.string(),
languages: z.array(z.string()),
})
const response = await ollama.chat({
model: 'gpt-oss',
messages: [{ role: 'user', content: 'Tell me about Canada.' }],
format: zodToJsonSchema(Country),
})
const country = Country.parse(JSON.parse(response.message.content))
console.log(country)
Define the objects you want returned and let the model populate the fields:
from ollama import chat
from pydantic import BaseModel
class Pet(BaseModel):
name: str
animal: str
age: int
color: str | None
favorite_toy: str | None
class PetList(BaseModel):
pets: list[Pet]
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': 'I have two cats named Luna and Loki. Luna is 3 years old, black, and loves feather toys. Loki is 5, orange, and prefers laser pointers.'}],
format=PetList.model_json_schema(),
)
pets = PetList.model_validate_json(response.message.content)
print(pets)
Output:
PetList(
pets=[
Pet(name='Luna', animal='cat', age=3, color='black', favorite_toy='feather toys'),
Pet(name='Loki', animal='cat', age=5, color='orange', favorite_toy='laser pointers')
]
)
Example: Vision with structured outputs
Vision models accept the same format parameter, enabling deterministic descriptions of images:
from ollama import chat
from pydantic import BaseModel
from typing import Literal, Optional
class Object(BaseModel):
name: str
confidence: float
attributes: str
class ImageDescription(BaseModel):
summary: str
objects: list[Object]
scene: str
colors: list[str]
time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night']
setting: Literal['Indoor', 'Outdoor', 'Unknown']
text_content: Optional[str] = None
response = chat(
model='gemma3',
messages=[{
'role': 'user',
'content': 'Describe this photo and list the objects you detect.',
'images': ['path/to/image.jpg'],
}],
format=ImageDescription.model_json_schema(),
options={'temperature': 0},
)
image_description = ImageDescription.model_validate_json(response.message.content)
print(image_description)
API parameters
Response format:
"json": Return valid JSON (any structure)
{...}: JSON schema object for strict typing
Response structure
With structured outputs, the message.content field contains a JSON string matching your schema:
{
"model": "gpt-oss",
"created_at": "2024-12-09T21:07:55.186497Z",
"message": {
"role": "assistant",
"content": "{\"name\":\"Canada\",\"capital\":\"Ottawa\",\"languages\":[\"English\",\"French\"]}"
},
"done": true
}
Tips for reliable structured outputs
- Define schemas with type validators: Use Pydantic (Python) or Zod (JavaScript) so they can be reused for validation
- Lower the temperature: Set
temperature: 0 in options for more deterministic completions
- Include schema in prompt: Mention the expected structure in your prompt for better results
- Use descriptive field names: Clear field names help the model understand what data to extract
- Add field descriptions: Include
description in your JSON schema for complex fields
- Test with examples: Provide example outputs in your prompt when schema is complex
OpenAI compatibility
Structured outputs work through the OpenAI-compatible API via response_format:
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI(
base_url='http://localhost:11434/v1',
api_key='ollama',
)
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = client.chat.completions.create(
model='gpt-oss',
messages=[{'role': 'user', 'content': 'Tell me about Canada'}],
response_format={
'type': 'json_schema',
'json_schema': {
'name': 'country_info',
'schema': Country.model_json_schema()
}
}
)
print(response.choices[0].message.content)
Common use cases
class Invoice(BaseModel):
invoice_number: str
date: str
total_amount: float
items: list[dict]
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': invoice_text}],
format=Invoice.model_json_schema()
)
class ContactForm(BaseModel):
name: str
email: str
phone: str
message: str
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': user_input}],
format=ContactForm.model_json_schema()
)
Sentiment analysis
from typing import Literal
class Sentiment(BaseModel):
sentiment: Literal['positive', 'negative', 'neutral']
confidence: float
reasoning: str
response = chat(
model='gpt-oss',
messages=[{'role': 'user', 'content': review_text}],
format=Sentiment.model_json_schema()
)
Limitations
- Structured outputs may be slower than regular completions
- Complex schemas may require larger models for best results
- Some models handle structured outputs better than others
- Very restrictive schemas might cause the model to refuse certain requests