Содержание
  1. Что даёт format=»json» и что даёт schema
  2. Локальный /api/chat и ограничение облака
  3. Входной текст заявки
  4. Схема с required и null для даты
  5. Законченный файл extraction.py
  6. Что именно проверяет валидатор
  7. Два отказа на учебных данных
  8. Температура и границы проверки
  9. Карантин ответа перед записью в таблицу
  10. Частые вопросы
Гайды

Как получить JSON по схеме в Ollama и проверить ответ

5 ноября 2026 · 11 минут чтения

Разберём, как получить JSON по схеме в Ollama и проверить ответ через локальный /api/chat. Значение format="json" включает JSON-режим, а объект схемы в format задаёт структуру. Затем приложение разбирает message.content и проверяет результат собственным валидатором.

Что даёт format=»json» и что даёт schema

В документации Ollama на docs.ollama.com сказано: structured outputs позволяют навязать модели JSON-схему, чтобы надёжно извлекать структурированные данные, описывать изображения и держать ответы единообразными. Там же приведён запрос к локальному адресу http://localhost:11434/api/chat с полем «format»: «json» и «stream»: false. Это базовый JSON-режим: модель отвечает валидным JSON, а состав полей остаётся на её усмотрение.

Объект schema в format меняет картину. В том же документе показан запрос, где в format лежит объект с «type»: «object», «properties» для name, capital, languages и списком «required»: [«name», «capital», «languages»]. Модель заполняет именно эти поля. В блоге Ollama от 6 декабря 2024 года сказано, что structured outputs ограничивают вывод модели форматом, заданным JSON-схемой, а библиотеки Python и JavaScript обновлены под эту возможность.

Разница практическая. JSON-режим годится, когда вы готовы принять любую структуру и разобрать её сами. Схема в format нужна, когда downstream-код ждёт фиксированный набор ключей. В блоге Ollama отмечено, что structured outputs дают больше надёжности и согласованности, чем JSON-режим.

Режим Что в format Что получаете
JSON-режим строка «json» валидный JSON, состав полей определяет модель
Схема объект JSON Schema JSON с заданными properties и required

Локальный /api/chat и ограничение облака

Нативный endpoint живёт на вашем железе: http://localhost:11434/api/chat. Запрос в документации Ollama идёт методом POST с заголовком Content-Type: application/json, телом с полями model, messages, stream и format. Ответ приходит в конверте, где текст лежит в message.content.

Документация Ollama прямо указывает: облако Ollama сейчас не поддерживает structured outputs. Строка стоит в начале страницы про structured outputs. Практический вывод: схему в format применяйте к локальному серверу, а облачный маршрут для этой задачи оставляйте в стороне.

Блог Ollama добавляет, что structured outputs работают через OpenAI-совместимый API посредством response_format. Это отдельный маршрут: клиент OpenAI с base_url http://localhost:11434/v1 и api_key «ollama». Для нативного /api/chat поле называется format, и в примерах ниже используется именно оно.

Входной текст заявки

Возьмём короткую учебную заявку:

ТЕКСТ
На практикум n8n придут Анна и Борис. Дата пока не определена.

Имена и дата берутся из этого текста. Внешняя система регистрации, календарь или CRM в примере не участвуют: скрипт получает только строку выше и схему. Это условный вход для разбора.

Из текста извлекаются четыре поля. event — название мероприятия, здесь значение n8n. participants — массив имён, Анна и Борис. date — дата, в тексте она не определена, поэтому допускается null. evidence — фрагмент исходного текста, на который опирается извлечение, здесь «Анна и Борис».

Ограничение event значением n8n относится к этой учебной заявке. Для другого набора мероприятий enum расширяют или заменяют. Смысл и участников всё равно сверяют с исходным текстом: схема проверяет форму, а соответствие смыслу остаётся за вами.

Схема с required и null для даты

Схема для заявки выглядит так:

JSON11 строк
{
  "type": "object",
  "properties": {
    "event": {"type": "string", "enum": ["n8n"]},
    "participants": {"type": "array", "items": {"type": "string"}},
    "date": {"type": ["string", "null"]},
    "evidence": {"type": "string"}
  },
  "required": ["event", "participants", "date", "evidence"],
  "additionalProperties": false
}

Здесь есть properties с описанием каждого поля и required со списком обязательных ключей. Документация Ollama показывает такую же конструкцию: properties для name, capital, languages и required с теми же именами. Для даты, которая в тексте не указана, тип задан как [«string», «null»]. Это разрешает модели вернуть null вместо выдуманной даты.

Поле additionalProperties: false закрывает лишние ключи. Если модель добавит поле, которого нет в properties, объект не пройдёт проверку. Так вы ловите расширение ответа до записи в таблицу.

В документации Ollama рекомендуется передавать JSON-схему строкой в промпте, чтобы заземлить ответ модели. Там же советуют определять схемы через Pydantic в Python или Zod в JavaScript и переиспользовать их для валидации. Для нативного запроса схему можно отправить как dict.

Законченный файл extraction.py

Ниже полный скрипт: запрос со stream:false, разбор конверта, чтение message.content, JSON parse и валидатор. Адрес локальной Ollama и имя модели можно задать переменными окружения OLLAMA_URL и OLLAMA_MODEL.

PYTHON72 строки
import json
import os
import urllib.request

OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434/api/chat")
MODEL = os.environ.get("OLLAMA_MODEL", "gpt-oss")

INPUT_TEXT = "На практикум n8n придут Анна и Борис. Дата пока не определена."

SCHEMA = {
    "type": "object",
    "properties": {
        "event": {"type": "string", "enum": ["n8n"]},
        "participants": {"type": "array", "items": {"type": "string"}},
        "date": {"type": ["string", "null"]},
        "evidence": {"type": "string"},
    },
    "required": ["event", "participants", "date", "evidence"],
    "additionalProperties": False,
}

PROMPT = (
    "Извлеки данные из заявки. Верни JSON по схеме.\n"
    "Схема: " + json.dumps(SCHEMA, ensure_ascii=False) + "\n"
    "Заявка: " + INPUT_TEXT
)

def ask_ollama():
    body = json.dumps({
        "model": MODEL,
        "messages": [{"role": "user", "content": PROMPT}],
        "stream": False,
        "format": SCHEMA,
    }).encode("utf-8")
    req = urllib.request.Request(
        OLLAMA_URL, data=body,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as resp:
        envelope = json.loads(resp.read().decode("utf-8"))
    return envelope["message"]["content"]

def validate(raw):
    try:
        data = json.loads(raw)
    except (json.JSONDecodeError, TypeError) as error:
        return None, ["ошибка разбора JSON: " + str(error)]
    if not isinstance(data, dict):
        return None, ["верхний уровень должен быть объектом"]
    errors = []
    expected = {"event", "participants", "date", "evidence"}
    if set(data.keys()) != expected:
        errors.append("набор ключей не совпадает: " + str(sorted(data.keys())))
    if data.get("event") != "n8n":
        errors.append("event вне допустимого значения")
    parts = data.get("participants")
    if not isinstance(parts, list) or not all(isinstance(p, str) for p in parts):
        errors.append("participants должен быть массивом строк")
    date = data.get("date")
    if date is not None and not isinstance(date, str):
        errors.append("date должен быть строкой или null")
    if not isinstance(data.get("evidence"), str):
        errors.append("evidence должен быть строкой")
    return data, errors

if __name__ == "__main__":
    content = ask_ollama()
    parsed, problems = validate(content)
    if problems:
        print("ОТКАЗ:", problems)
    else:
        print("ПРИНЯТО:", parsed)

Скрипт печатает либо разобранный объект, либо список проблем. Автоматической регистрации участников он не делает: результат остаётся кандидатом записи.

Что именно проверяет валидатор

Валидатор проверяет форму ответа по четырём направлениям. Первое — точный набор ключей: множество ключей объекта сравнивается с ожидаемым. Лишний ключ или пропуск даёт отказ. Второе — тип массива и его элементов: participants обязан быть списком, каждый элемент — строкой.

Третье — допустимое значение event. В учебной схеме это n8n, и другое значение не проходит. Четвёртое — null для даты: поле принимает строку или null, а число или объект отклоняются. Пропуски не заменяются придуманными значениями: если дата не определена, в объекте остаётся null.

После проверки формы сверьте все четыре поля с исходной заявкой: event равен n8n, participants содержит Анну и Бориса, date равен null, evidence дословно совпадает с фрагментом «Анна и Борис». Это ручной эталон именно данного входа. Валидатор формы допускает также строку даты и любые строковые имена или evidence, поэтому эта проверка смысла обязательна до дальнейшей обработки.

Два отказа на учебных данных

Первый ошибочный объект содержит лишний ключ:

JSON7 строк
{
  "event": "n8n",
  "participants": ["Анна", "Борис"],
  "date": null,
  "evidence": "Анна и Борис",
  "room": "учебная"
}

Валидатор вернёт отказ: набор ключей не совпадает. Ключ room в схеме не описан, а additionalProperties: false его запрещает. Передача данных дальше останавливается.

Второй ошибочный объект содержит строку вместо массива:

JSON6 строк
{
  "event": "n8n",
  "participants": "Анна и Борис",
  "date": null,
  "evidence": "Анна и Борис"
}

Валидатор вернёт отказ: participants должен быть массивом строк. Оба объекта — учебные данные для демонстрации отказа.

Объект Что нарушено Реакция валидатора
с ключом room лишний ключ отказ, набор ключей не совпал
participants строкой тип поля отказ, нужен массив строк

Температура и границы проверки

В документации Ollama среди советов для надёжных structured outputs указано снижение температуры, например до 0, для более детерминированных ответов. В блоге Ollama тот же совет повторён: установите temperature 0 для более детерминированного вывода. Это снижает разброс, и одинаковый ответ при temperature 0 не гарантирован.

Прохождение схемы подтверждает структуру и не подтверждает смысл. Модель может вернуть объект с верными типами и неверно понятым участником. Поэтому сверка с исходным текстом остаётся отдельным шагом, а результат — кандидатом записи.

Карантин ответа перед записью в таблицу

Для учебной заявки про Анну, Бориса, практикум n8n и неуказанную дату порядок такой. Сначала сохраните исходный message.content как есть, отдельным полем или файлом. Затем выполните три шага по очереди: JSON parse, проверку по схеме и проверку смысла. На шаге parse вы получаете объект или ошибку разбора. На шаге схемы сверяете набор ключей, типы и допустимые значения. На шаге смысла сравниваете имена и мероприятие с исходным текстом. Каждый шаг фиксируйте отдельно, чтобы при отказе было видно, где именно ответ разошёлся с контрактом.

Повторный запрос к модели отправляйте только после видимой ошибки: сбой parse, несовпадение ключей, неверный тип или значение вне enum. В повторный запрос включайте исходный текст, схему и текст ошибки. Если дата в заявке не определена, сохраняйте null и не подставляйте придуманное значение. Ответ, прошедший все три шага, остаётся кандидатом записи.

Частые вопросы

Как включить JSON-режим в Ollama?
Передайте в запрос к /api/chat поле «format» со значением «json» и «stream»: false. Модель вернёт валидный JSON, состав полей определит сама. Для фиксированного набора полей вместо строки передайте объект схемы.

Чем format со схемой отличается от format=»json»?
Строка «json» включает JSON-режим без контроля состава полей. Объект схемы в format задаёт properties и required, и модель заполняет заданные ключи. В блоге Ollama сказано, что structured outputs дают больше надёжности и согласованности.

Работает ли схема в облаке Ollama?
Документация Ollama указывает, что облако сейчас не поддерживает structured outputs. Схему в format применяйте к локальному серверу на http://localhost:11434/api/chat. Для облачного маршрута этот способ не описан.

Что делать, если дата в заявке не указана?
Разрешите null в схеме: задайте тип [«string», «null»] для поля date и попросите сохранять неизвестную дату как null. Этот тип допускает также строку, поэтому сравните результат с исходной заявкой. Для нашего входа ручной эталон — null; придуманную строку даты нужно отклонить при проверке смысла.

Достаточно ли схемы для правильного результата?
Схема проверяет форму: набор ключей, типы, допустимые значения. Смысл извлечения она не подтверждает. Сверяйте имена и мероприятие с исходным текстом отдельным шагом и держите результат кандидатом записи.

Читайте также

3 материала