← Назад в технический блог

OpenAI GPT 2026: Function Calling, Structured Outputs и JSON Schema

Рабочее место разработчика для OpenAI Function Calling и Structured Outputs

В 2026 году команды, которые держат OpenAI-агентов в проде, чаще всего падают не на «модель не умеет говорить», а на старом коде, где json_object всё ещё считают структурированным выводом, или на Chat Completions без строгого Function Calling. Для новых проектов официально рекомендуют стартовать с gpt-5.6. Function Calling и Structured Outputs опираются на один и тот же constrained decoding, но вход, поведение strict по умолчанию и допустимое подмножество JSON Schema — разные.

Дата сверки — 18 августа 2026. Поля и поведение — по руководству OpenAI Function Calling и руководству Structured Outputs. Здесь нет выдуманных задержек, цен и долей успеха. Где публичная документация ничего не фиксирует, явно написано: «проверить на replay живого трафика».

Если вы переводите OpenAI-совместимый бэкенд на другую модель, структурированный вывод и цикл инструментов нужно принимать отдельно — одной сменой Base URL недостаточно. См. внутренний чек-лист миграции OpenAI API на Kimi K3.

Сначала вывод: меняются три вещи сразу

Многие репозитории всё ещё живут моделью 2024 года: в промпте «верни JSON», потом regex по code fence. В проде 2026 это рвёт парсер через нарушения схемы, отсутствующие поля и галлюцинации enum.

Менять нужно три слоя, а не имя модели побольше:

Контракт поставки
Объект для пользователя или downstream — через Structured Outputs (text.format в Responses, response_format.json_schema в Chat Completions).
Контракт исполнения
Когда модель вызывает ваши инструменты, аргументы должны совпадать с JSON Schema инструмента. Это Function Calling — тот же constrained decoding, что у Structured Outputs.
Контракт совместимости
Старый JSON Mode (json_object) гарантирует только «похоже на JSON», не поля, типы и enum относительно схемы. В доке это предшественник Structured Outputs. Новым проектам не стоит делать его основным путём.

Ещё один продуктовый сдвиг, который легко пропустить: новый код должен идти через Responses API. Chat Completions по-прежнему доступен, но стратегия strict по умолчанию другая — Responses старается нормализовать схему в строгий режим и откатывается только при неудаче; у Chat Completions по умолчанию по-прежнему non-strict / best effort.

Модель и основной API-путь: gpt-5.6 + Responses

Structured Outputs доступны начиная с поколения GPT-4o. Для новых проектов в документации предлагают сразу gpt-5.6. Более старые снимки вроде gpt-4-turbo и раньше по-прежнему отсылают к JSON Mode, а не к полноценному строгому json_schema.

Сначала разделить два входа

Нужный результат Какой вход На что смотреть в 2026
Фиксированный объект пользователю / downstream Responses: text.format; или Chat Completions: response_format: json_schema Включить strict: true; SDK: Pydantic / Zod + parse()
Модель вызывает ваши функции, читает БД, меняет состояние function-инструмент в tools Схема параметров тоже strict; параллельные вызовы и мультитул-циклы — свой исполнитель
Слишком много инструментов, не хотите класть всё в контекст Отложенная загрузка tool_search Только gpt-5.4 и новее; определения инструментов считаются input-токенами
Аргументы не JSON, а свободный текст или грамматика custom tools + опциональная CFG Для DSL и языков запросов; не втискивать в JSON Schema функции

На стороне SDK привычка важнее: не писать схему вручную (легко забыть additionalProperties), а генерировать её из типов официальными хелперами. Python: client.responses.parse(..., text_format=YourModel); JavaScript: zodTextFormat. При рукописной схеме и strict: true нарушение ограничений даёт отклонение запроса, а не «модель выдала что попало, вы ретраите».

Сравнивая с линейкой Gemini, «совместимость с OpenAI SDK» не означает то же поведение схемы. Обновления возможностей Google: 10 новых функций Gemini 3.5 Pro. Когда одну и ту же JSON Schema копируют между провайдерами, правило additionalProperties на вложенных object часто взрывается первым.

JSON Mode, Structured Outputs, Function Calling

Самая частая путаница в инцидентах: в логах JSON, значит Structured Outputs уже включены. По официальной семантике:

Возможность Валидный JSON Соответствие схеме Типичное включение Модели
JSON Mode Да Нет text.format.type = json_object В т.ч. часть совместимых ступеней GPT-5; типично для старых снимков
Structured Outputs Да Да (поддерживаемое подмножество схемы) json_schema + strict: true gpt-4o-2024-08-06 / gpt-4o-mini и новее; новые проекты — gpt-5.6
Function Calling + strict Аргументы инструмента — валидный JSON Аргументы совпадают со схемой parameters strict: true у инструмента Модели с tools; strict рекомендуют всегда

Когда Function Calling не нужен

Если модель не должна трогать вашу систему (нет склада, тикетов, скриптов), а ответ лишь раскладывается в карточки, шаги или оценки — Structured Outputs. Как только вывод означает «выполни этот побочный эффект», нужны tools: не маскируйте аргументы функции под схему финального ответа.

Отказ — это не «битый JSON»

При safety-отказе модель не запихивают в вашу схему. Responses / Chat Completions отдают отдельное поле refusal. Слой разбора должен считать отказ гражданином первого класса: сначала refusal, потом output_parsed — пустой объект не успех.

Жёсткие правила Strict JSON Schema

После включения strict OpenAI принимает подмножество JSON Schema, а не любой документ Draft 2020-12. Три самых частых отказа на уровне запроса:

  1. Каждое поле из properties должно быть в массиве required.
  2. Каждый object (включая вложенные) должен иметь additionalProperties: false.
  3. Корневой объект не может быть anyOf; опциональность — через «обязательное + разрешён null», например ["string", "null"].

То есть: убрать поле из required и притвориться, что оно опционально — под strict это сразу 400. Правильно: поле остаётся required, тип — nullable union, приложение трактует null как «не задано».

Типичный прод-объект «извлечь тикет» — у вложенного object тоже есть additionalProperties:

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class Ticket(BaseModel):
    title: str
    priority: str
    assignee: str | None
    tags: list[str]

response = client.responses.parse(
    model="gpt-5.6",
    input=[
        {"role": "system", "content": "Извлеки поля тикета из описания пользователя."},
        {"role": "user", "content": "Страница входа 500, назначить Noah, приоритет высокий, теги auth и api."},
    ],
    text_format=Ticket,
)
ticket = response.output_parsed
print(ticket.title, ticket.priority, ticket.assignee)
Соответствие схеме ≠ корректность бизнеса
Constrained decoding гарантирует типы, обязательные ключи и множества enum; он не гарантирует, что приоритет правда high и что табельный номер существует. Downstream по-прежнему проверяет права, существование и идемпотентность.

Если запрос отклонён, сначала смотрите в ошибке, какого ограничения не хватает — не понижайте модель сразу. Схемы из Playground уже со strict; скопировать их в репозиторий обычно быстрее, чем переделывать старые промпты json_object.

Между вендорами проверьте ещё раз: та же схема «у каждого object false» на части совместимых шлюзов или других моделях может дать HTTP 400. Тогда нужна трансформация по провайдеру, а не три бизнес-схемы.

Function Calling 2026: strict, tool_search, custom tools

Function Calling и tool calling в документации — одно и то же: JSON Schema описывает вызываемые функции, исполнитель в приложении делает побочные эффекты. Несколько пунктов, которые в 2026 подчёркивают отдельно, сразу меняют цикл агента.

Значение strict по умолчанию не угадывать

  • Рекомендуют всегда явно ставить strict: true.
  • Responses: если strict опущен, сервер пытается нормализовать схему; при неудаче откат в non-strict, в ответе у tool будет strict: false.
  • Chat Completions: без указания по умолчанию non-strict.
  • У дообученных моделей при нескольких вызовах функций за один ход документация допускает отключение strict на этот ход.

Определения инструментов входят в контекст и тарифицируются как input-токены. Слишком длинные description и 40 инструментов сразу бьют и по счёту, и по точности выбора. Если инструментов много — откладывайте редкие через tool_search: только gpt-5.4 и новее. В цикле сначала могут появиться tool_search_call / tool_search_output, и лишь потом настоящий function_call.

Custom tools: не запихивайте DSL в JSON-объект

Function tools подходят для структурированных параметров; custom tools — для свободного текста на входе и выходе, с опциональной контекстно-свободной грамматикой (CFG). Фрагменты SQL, внутренние языки запросов, форматы с взаимоисключающими терминалами: CFG стабильнее, чем «string-поле плюс простыня промпта». Если CFG ругается на unexpected tokens, сначала ищите пересекающиеся терминалы, а не вините модель.

tools = [{
    "type": "function",
    "name": "get_order",
    "description": "Запросить статус заказа по номеру. Вызывать только если пользователь указал конкретный номер заказа.",
    "strict": True,
    "parameters": {
        "type": "object",
        "properties": {
            "order_id": {"type": "string"},
            "locale": {"type": ["string", "null"]},
        },
        "required": ["order_id", "locale"],
        "additionalProperties": False,
    },
}]

Цикл исполнения не изменился: увидели finish_reason / тип item = вызов инструмента → локальная функция → вернуть результат в роли tool → новый запрос. Изменилось другое: аргументы больше не нужно ловить через json.loads наудачу. Полное сообщение assistant (включая tool_calls) всё равно нужно сохранять, иначе на втором ходе пропадут ID вызовов.

При параллельных вызовах инструментов порядок задаёт call ID

В одном ответе может быть несколько tool call. Ответ выравнивайте по call_id, не по индексу массива. В canary-логах минимум: имя инструмента, хеш аргументов, длительность, был ли strict, был ли откат схемы.

Чек-лист миграции старых проектов

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

  1. Модель: новая цепочка явно на gpt-5.6 (или эквивалентный уровень, уже открытый на аккаунте); шлюз не должен молча маппить на старый снимок.
  2. Вывод: заменить json_object на json_schema + strict: true или на text.format в Responses.
  3. Инструменты: у каждой function полный required и вложенный additionalProperties: false; опциональные поля — nullable union.
  4. Разбор: подключить parse() и ветку refusal; в стриминге сверить инкрементальный JSON с финальным parsed-объектом.
  5. Поверхность инструментов: больше десятка — оценить tool_search; сначала укоротить description, потом отложенную загрузку.
  6. Сравнение: один и тот же набор фиксированных задач — ретраи, доля отсутствующих полей, ручная доработка между старым JSON Mode и новым путём схемы.

Критерий прохождения — не HTTP 200, а: парсер без regex-подстраховки, стабильные типы аргументов инструментов, наблюдаемые отказы, уже отработанный переключатель отката. Долгие сессии SDK, скрипты replay и вкладки браузера обрываются, когда ноутбук засыпает — отсюда облачный Mac mini ниже.


FAQ

Можно ли смешивать JSON Mode и Structured Outputs?

Не на одной цепочке. JSON Mode гарантирует только валидный JSON; Structured Outputs — схему. Вперемешку мониторинг не отличит сбой парсера от сбоя контракта. Новый код только json_schema / text.format.

Новым проектам ещё писать Chat Completions?

Если можно Responses — берите Responses. Официальные примеры, parse-хелперы и нормализация strict идут туда в первую очередь. Существующий Chat Completions можно оставить, но со явным strict и с пониманием non-strict по умолчанию.

Почему схема даёт 400, как только включаю strict?

Чаще всего: нет required, у вложенного object нет additionalProperties:false, корень — anyOf, или опциональность сделана как «нет в required». Допишите ограничение из текста ошибки; не выключайте strict, чтобы спрятать ошибку схемы, если только вы сознательно не хотите non-strict fallback.

Function Calling обязательно со strict?

Официально — всегда включать. Без strict аргументы best effort: исполнителю всё равно ловить дыры в полях и дрейф типов. Responses при опущенном strict может переписать его на сервере; в логах фиксируйте итоговое значение strict.

Когда имеет смысл tool_search?

Когда определения инструментов заметно занимают контекст или большинство инструментов в одной задаче не нужны. Нужен gpt-5.4 и новее. Перед продом прогоните двухтактную трассу «сначала поиск инструмента, потом вызов»; старый исполнитель, который знает только function_call, оборвётся.

Схема держится — всё равно проверять бизнес-значения?

Да. Constrained decoding не проверяет внешние ключи, права и идемпотентность. Легальный enum не значит, что значение осмысленно для вашего склада. Разведите логи схемы и бизнес-проверок — иначе инцидент не разберёте.

Чем gpt-5.6 отличается от более раннего GPT-5.x на структурированном выводе?

Документация ставит gpt-5.6 дефолтом для новых проектов. Совпадает ли это на вашем аккаунте, в регионе, batch и fine-tune — смотрите текущий список моделей и минимальный parse-запрос, а не алиасы из блога как маппинг шлюза.

Одна JSON Schema на Claude / Grok и OpenAI?

Диалект близок к Draft 2020-12, подмножества разные. OpenAI strict требует additionalProperties:false на каждом object; часть провайдеров отклоняет это поле на вложенном уровне. Одна бизнес-схема, слой трансформации по провайдеру.

Читать дальше

На облачном Mac mini приёмку схемы можно держать 24/7

Регрессия Function Calling и Structured Outputs — это длинный сравнительный эксперимент: два SDK, фиксированный replay-набор, песочница инструментов, стриминговый фронт — и ноутбук, который не засыпает от крышки. Unified memory на Apple Silicon удобна, чтобы параллельно крутить локальный прокси и отладку в браузере; Homebrew, Docker и SSH на macOS готовы сразу. M4 Mac mini в простое около 4 Вт — среда приёмки спокойно висит на ночь.

Если нужен Mac, который не отъедает домашний канал и доступен по SSH для replay агентов, облачный Mac mini M4 от Nuvcloud — низкофрикционный способ развести машину разработки и стенд сравнения — смотреть тарифы, чтобы canary строгого Schema не был привязан к вашему ноутбуку.

Акция →