AI Model Intelligence
Share
Local Deployment & Hardware

Ollama Structured JSON: Validate Schemas and Evidence in Python

Updated ยท Sources & corrections

In this guide
  1. Choose the contract before calling the model
  2. Define a small schema and validate locally
  3. Send the same schema to the local chat endpoint
  4. Separate validation failures from request failures
  5. What the local fixtures established
  6. Sources and verification scope

Quick answer: For local Ollama structured output, pass a JSON schema through the request’s format field, then validate the returned message.content in your application. Treat JSON parsing, schema checks and factual checks as separate gates. A correctly shaped answer can still assign the wrong person or describe an action the input never requested.

This guide uses Python and the native local /api/chat endpoint. It includes a small action-extraction example and nine locally executed validation fixtures. For problems decoding several HTTP records, use our streaming JSON guide; this article instead checks the model’s final structured content.

Choose the contract before calling the model

The official structured-output guide distinguishes JSON mode from supplying a schema. Its current documentation also says Ollama cloud does not support structured outputs, so the example here targets an installed local model. Check current capability support before adapting it to another service.

Three checks with different purposes
CheckWhat it establishesWhat remains unknown
JSON decodingThe content can be read as JSON.Required fields, types and truthfulness.
Schema validationThe decoded object satisfies the application schema.Whether the extracted meaning is correct.
Evidence or domain checksSpecific application rules hold, such as a quote appearing in the input.Anything those rules do not actually verify.

Define a small schema and validate locally

Save the following as validate_output.py. It uses the third-party jsonschema package; install it in your Python environment with python -m pip install jsonschema if needed. The library’s validation reference documents schema checking and instance validation.

import json
from jsonschema import Draft202012Validator

SCHEMA = {
    'type': 'object',
    'properties': {
        'action': {'type': 'string', 'minLength': 1},
        'evidence': {'type': 'string', 'minLength': 1},
        'owner': {'type': ['string', 'null']},
    },
    'required': ['action', 'evidence', 'owner'],
    'additionalProperties': False,
}
Draft202012Validator.check_schema(SCHEMA)
VALIDATOR = Draft202012Validator(SCHEMA)


def validate_answer(envelope, source):
    if 'error' in envelope:
        raise RuntimeError(str(envelope['error']))
    if envelope.get('done') is not True:
        raise ValueError('Generation did not complete')
    if envelope.get('done_reason') == 'length':
        raise ValueError('Generation reached its length limit')
    content = envelope['message']['content']
    if not isinstance(content, str):
        raise TypeError('Expected message.content text')
    data = json.loads(content)
    VALIDATOR.validate(data)
    if data['evidence'] not in source:
        raise ValueError('Evidence is not an exact excerpt of the input')
    return data

All three fields are required, but owner may be null. That gives the application an explicit representation for an unspecified person. Additional properties are rejected rather than silently becoming unreviewed downstream data.

The evidence check requires an exact excerpt. It catches a fabricated quote, but it does not prove that the proposed action faithfully summarizes that quote. For example, a contradictory action with a genuine quote could still pass. Keep human review or stronger domain checks where a wrong interpretation matters.

Send the same schema to the local chat endpoint

Save this beside the validator as request_example.py. Supply a suitable model already installed on your local server when prompted. This is an illustrative network example; it was syntax-checked, not run against a model during preparation.

import json
from urllib.request import Request, urlopen
from validate_output import SCHEMA, validate_answer

source = 'Please update the README before Friday.'
model = input('Enter an installed local model tag: ').strip()
payload = {
    'model': model,
    'messages': [{'role': 'user', 'content': (
        'Extract one action. Use an exact source excerpt as evidence. '
        'Use null for owner when the input does not identify one. '
        'Return only JSON matching this schema: ' + json.dumps(SCHEMA)
        + '\nInput: ' + source
    )}],
    'format': SCHEMA,
    'stream': False,
}
request = Request('http://127.0.0.1:11434/api/chat',
                  data=json.dumps(payload).encode('utf-8'),
                  headers={'Content-Type': 'application/json'}, method='POST')
with urlopen(request, timeout=120) as response:
    raw = response.read(1024 * 1024 + 1)
if len(raw) > 1024 * 1024:
    raise ValueError('Example response-size limit exceeded')
envelope = json.loads(raw)
print(validate_answer(envelope, source))

The chat API reference describes the messages, format and streaming fields. Disabling streaming keeps this example focused on one response envelope. There are two decoding stages: read that envelope first, then parse its content string inside the validator. A schema passed to the model does not replace the application’s validation step.

The HTTP example uses Python’s urllib.request. Its timeout and one-megabyte response limit are example choices, not Ollama requirements or a complete production resilience policy. Generation still consumes local memory and compute. For server and model-name errors, use our server, tag and API-path checks.

Separate validation failures from request failures

Use the failure stage to choose the next action:

  • Request rejected or server unavailable: inspect the status, destination and error body. A schema checker cannot fix a connection.
  • Incomplete or length-limited response: keep the result incomplete. Review the task size and generation settings rather than accepting partial data.
  • JSON or schema failure: record the validation error and the model/schema version. A bounded retry may help a recoverable generation problem, but cannot repair an invalid request contract.
  • Evidence failure: reject or route for review. Do not remove the evidence requirement just to make an answer pass.

Ollama’s error reference documents its error property and HTTP failure responses. If you add retries, cap the attempts and avoid repeating downstream side effects. This example deliberately has no automatic retry loop.

What the local fixtures established

On 22 October 2026, nine synthetic tests passed using Python and jsonschema 4.26.0. No local model was invoked.

Validation outcomes from constructed fixtures
FixtureObserved outcome
Valid object with null ownerAccepted.
Missing required fields, extra property or numeric ownerEach rejected by schema validation.
Markdown-fenced content instead of plain JSONJSON decoding failed visibly.
Evidence absent from the sourceApplication check rejected it.
Incomplete generation, length stop or API errorEach rejected before accepting content.

These results establish behavior for those fixtures only. They do not measure model accuracy, schema-keyword support across models, real network behavior or production reliability. Start with a small representative dataset and inspect accepted as well as rejected answers before connecting extraction to an automated workflow.

Sources and verification scope

Reviewed against the linked official Ollama, Python and jsonschema references on 22 October 2026. Validator fixtures were executed locally; the live-request example was syntax-checked only. The featured image is an original generated illustration. See the editorial policy and our Ollama setup guide for prerequisites.

If answer content is empty or a parser receives thinking text instead, use our thinking and final-answer checks before sending content to schema validation.