·5 мин. чтения

Structured Outputs: JSON Schema в ответах LLM

Как получить от LLM ответ строго по JSON Schema: json_object, json_schema со strict, Pydantic и Responses API. Проверенные примеры для PlusVibe API.

Structured Outputs: JSON Schema в ответах LLM

Когда ответ модели читает не человек, а код, свободный текст мешает. Нужен JSON с заранее известными полями: разобрать отзыв, вытащить реквизиты из письма, разложить заявку по полям CRM. Для этого в OpenAI-совместимом API есть параметр response_format, а в PlusVibe он работает через тот же /v1/chat/completions, что и обычный чат.

Ниже — три способа получить JSON, от мягкого к строгому, и примеры, которые мы запустили перед публикацией.

Три способа получить JSON

  • json_object — модель обязана вернуть валидный JSON, но какие в нём поля, вы описываете словами в промпте. Работает на большинстве моделей.
  • json_schema со strict: true — вы передаёте JSON Schema, и модель заполняет ровно эти поля нужных типов. Поддерживается моделями GPT-5.x и совместимыми.
  • Вызов функции — схема описывается как параметры инструмента, а tool_choice заставляет модель его вызвать. Аргументы вызова и есть нужный JSON. Этот способ выручает на моделях без json_schema.

json_object: просто валидный JSON

Самый совместимый режим. Опишите поля в системном промпте и включите response_format:

curl https://plusvibeapi.ru/v1/chat/completions \
  -H "Authorization: Bearer $PLUSVIBE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4.1-flash",
    "response_format": {"type": "json_object"},
    "messages": [
      {"role": "system", "content": "Верни JSON с полями city (строка) и date (строка в формате YYYY-MM-DD). Год 2026."},
      {"role": "user", "content": "Встречаемся в Самаре 3 октября"}
    ]
  }'

Модель вернула в content строку {"city":"Самара","date":"2026-10-03"}. Обратите внимание на две вещи. Слово «JSON» должно встречаться в сообщениях: тот же запрос без него к gpt-5.6-luna у нас вернул ошибку 400. И соблюдение полей и типов здесь никто не гарантирует. Проверять ответ на своей стороне всё равно нужно.

json_schema: ответ строго по схеме

Если поля и типы важны, передайте схему целиком. Пример — разбор отзыва покупателя:

import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://plusvibeapi.ru/v1",
    api_key=os.environ["PLUSVIBE_API_KEY"],
)

schema = {
    "type": "object",
    "properties": {
        "product": {"type": "string"},
        "sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
        "score": {"type": "integer", "minimum": 1, "maximum": 5},
        "issues": {"type": "array", "items": {"type": "string"}},
    },
    "required": ["product", "sentiment", "score", "issues"],
    "additionalProperties": False,
}

resp = client.chat.completions.create(
    model="gpt-5.6-luna",
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "review", "strict": True, "schema": schema},
    },
    messages=[
        {"role": "system", "content": "Разбери отзыв покупателя."},
        {"role": "user", "content": "Наушники звучат отлично, но кейс поцарапался через неделю."},
    ],
)
data = json.loads(resp.choices[0].message.content)
print(data)

В нашем прогоне пришёл объект с четырьмя полями: product — «Наушники», sentiment — neutral, score — 3 и список issues с жалобой на кейс. Лишних полей не было.

Правила строгой схемы

  • Все поля перечислены в required. Если поле может отсутствовать, сделайте его тип ["string", "null"] — модель вернёт null.
  • В каждом объекте, включая вложенные, стоит "additionalProperties": false.
  • enum — лучший способ ограничить значения: модель не придумает четвёртую тональность.
  • Поле name в json_schema обязательно. Это просто метка, на ответ оно не влияет.

Что делает шлюз со strict: true, описано в документации. PlusVibe отправляет такой запрос только на те варианты модели, которые схему действительно соблюдают, в том числе при переключении на резервный маршрут. Если подходящего варианта сейчас нет, вы получите ошибку, а не обычный текст вместо JSON.

Pydantic: схема из класса

В Python не обязательно писать схему руками. Метод parse из официального SDK строит её из Pydantic-модели и сразу возвращает готовый объект:

from typing import Literal
from pydantic import BaseModel

class Review(BaseModel):
    product: str
    sentiment: Literal["positive", "neutral", "negative"]
    score: int
    issues: list[str]

completion = client.chat.completions.parse(
    model="gpt-5.6-luna",
    messages=[
        {"role": "system", "content": "Разбери отзыв покупателя."},
        {"role": "user", "content": "Наушники звучат отлично, но кейс поцарапался через неделю."},
    ],
    response_format=Review,
)
review = completion.choices[0].message.parsed
print(review.sentiment, review.score, review.issues)

Пример проверен с openai 2.x. Если вы используете PydanticAI, у нас есть отдельная статья про PydanticAI в России.

То же самое в Responses API

В /v1/responses схема передаётся не в response_format, а в text.format, и поля name, strict, schema лежат на одном уровне с type:

resp = client.responses.create(
    model="gpt-5.6-luna",
    input="Извлеки город и дату: «Встречаемся в Самаре 3 октября»",
    text={"format": {
        "type": "json_schema",
        "name": "meeting",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}, "date": {"type": "string"}},
            "required": ["city", "date"],
            "additionalProperties": False,
        },
    }},
)
print(json.loads(resp.output_text))  # {'city': 'Самара', 'date': '3 октября'}

Чем ещё Responses отличается от Chat Completions, мы разобрали в статье Responses API и Chat Completions.

Если модель не поддерживает json_schema

Не у всех моделей есть строгий режим. У DeepSeek, например, его нет: запрос с json_schema и strict: true к deepseek-v4.1-flash у нас завершился ошибкой 400. Для таких моделей два пути:

  1. json_object плюс описание полей в промпте и проверка ответа в коде — как в первом примере.
  2. Вызов функции с принудительным tool_choice: схема становится параметрами функции, а модель обязана её вызвать. Пример — в статье про function calling.

Поддержку заранее можно проверить через GET /v1/model-capabilities с вашим ключом: у каждой модели есть поле capabilities.jsonMode, а в supported_parameters указано, поддерживается ли response_format.

Частые ошибки

Ответ обрезан, JSON не парсится. Модели не хватило лимита токенов. У рассуждающих моделей в лимит входят и токены рассуждения, поэтому max_tokens ставьте с запасом или не ставьте совсем.

400 при строгой схеме. В схеме нет additionalProperties: false, не все поля перечислены в required или модель не поддерживает json_schema.

Поля на месте, но значения странные. Схема гарантирует форму, а не смысл. Добавьте в описание поля (description) пояснение, что туда класть, и сузьте значения через enum.

Итог

Для быстрого прототипа хватит json_object. В продакшене берите json_schema со strict: true на моделях GPT-5.x или вызов функции на моделях без строгого режима. Все три способа работают через один ключ PlusVibe. Модели и цены — в каталоге, формат параметров — в документации.

JSON по схеме через один API

GPT, Claude, DeepSeek и другие модели через OpenAI-совместимый API. Оплата в рублях.

Получить ключ →
structured outputsjson schema openairesponse_format json_schemajson_objectLLM JSON ответpydantic openai parseструктурированный вывод LLM

Попробуйте PlusVibe API

OpenAI-совместимый API: GPT, Claude, Gemini, видео и изображения — один рублёвый ключ. Работает из России без VPN, оплата рублями.

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

Structured Outputs: JSON Schema в ответах LLM | PlusVibe API