Как получать строго структурированный JSON от модели

Просить модель «ответь в формате JSON» в системном промпте работает через раз. Разбираем способ понадёжнее — через tools с жёсткой схемой.

3 мин · · API · разработка

Если единственная защита от сломанного JSON — это фраза «отвечай строго в формате JSON» в системном промпте, рано или поздно модель обернёт ответ в пояснение, добавит текст до или после скобок, или забудет закрывающую кавычку. Есть способ надёжнее — не просить, а структурно ограничить.

Почему просьба в промпте — это не гарантия

bash
{"role": "system", "content": "Отвечай только в формате JSON, без пояснений"}

Такой промпт работает в большинстве случаев, но не даёт формальной гарантии. Модель — это генератор текста, и инструкция в системном промпте — это сильная подсказка, а не ограничение на уровне протокола. На длинных или неоднозначных запросах модель может добавить преамбулу вида «Вот запрошенный JSON:», обернуть объект в блок ```json, или сгенерировать невалидный JSON с висячей запятой. Парсер на вашей стороне ломается, и приходится городить регулярки для очистки ответа перед json.loads.

Способ понадёжнее: tools с жёсткой схемой

И /v1/responses, и /v1/chat/completions принимают параметр tools — набор функций с описанием и JSON-схемой параметров. Штатное применение tools — вызов внешних функций моделью, но тот же механизм отлично работает как способ принудительно получить структурированный вывод: вы описываете «функцию», параметры которой и есть нужная вам структура данных, а модель обязана заполнить их по схеме, а не сочинить произвольный текст.

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "messages": [
      {"role": "user", "content": "Извлеки из текста имя, возраст и город: Меня зовут Игорь, мне 34, живу в Казани"}
    ],
    "tools": [{
      "type": "function",
      "function": {
        "name": "extract_person",
        "description": "Извлекает структурированные данные о человеке из текста",
        "parameters": {

Ключевой момент — tool_choice, принудительно указывающий модели вызвать именно эту функцию, а не отвечать текстом на своё усмотрение. Ответ приходит не как обычный текст, а как вызов функции с аргументами в формате JSON, строго по объявленной схеме:

bash
{
  "choices": [{
    "message": {
      "tool_calls": [{
        "function": {
          "name": "extract_person",
          "arguments": "{\"name\":\"Игорь\",\"age\":34,\"city\":\"Казань\"}"
        }
      }]
    }
  }]
}

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

То же самое на Python

bash
import json
import requests

def extract_structured(text, api_key):
    schema = {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "city": {"type": "string"},
        },
        "required": ["name", "age", "city"],
    }
    resp = requests.post(

Тот же приём через Anthropic-совместимый /v1/messages

Anthropic-стиль запросов к /v1/messages тоже поддерживает tools в том же духе: описываете инструмент со схемой и просите модель обязательно его вызвать. Формат объявления инструмента немного другой (input_schema вместо parameters, вложенных в function), но идея та же — структура гарантируется схемой, а не пожеланием в тексте.

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "Извлеки имя, возраст и город: Меня зовут Игорь, мне 34, живу в Казани"}],
    "tools": [{
      "name": "extract_person",
      "description": "Извлекает структурированные данные о человеке",
      "input_schema": {
        "type": "object",
        "properties": {

Что делать, если структура вложенная или список объектов

Схема parameters / input_schema — это обычный JSON Schema, так что вложенные объекты и массивы описываются стандартно:

bash
{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "title": {"type": "string"},
          "price": {"type": "number"}
        },
        "required": ["title", "price"]
      }
    }
  },
  "required": ["items"]
}

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

Валидация всё равно нужна

Схема резко снижает вероятность мусора в ответе, но не отменяет проверку на своей стороне: тип integer в схеме не гарантирует, что число окажется в разумном диапазоне, а обязательное поле не гарантирует, что модель не подставит туда пустую строку при недостатке данных в исходном тексте. Разумно всё равно прогонять результат через валидацию (pydantic, zod или ручные проверки) перед использованием в бизнес-логике.

Итого

Просьба в промпте — это подсказка, а не контракт. Если нужна структура, на которую можно полагаться в коде без защитного парсинга, используйте tools с явной JSON-схемой и tool_choice, принудительно указывающим нужную функцию — это доступно и в OpenAI-совместимом режиме (/v1/chat/completions, /v1/responses), и в Anthropic-совместимом (/v1/messages).

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

Структурированный JSON от модели: рабочий способ · Zukko