Структурированный вывод: это ответ в форме, которую вы определили заранее. JSON-объект с именованными ключами, таблица с фиксированными столбцами или шаблон с одними и теми же заголовками каждый раз. Он нужен всегда, когда ответ читает программа, и помогает, когда его читает человек, потому что все ответы выглядят одинаково, и их легко просматривать и сравнивать.
Секрет в том, чтобы описать форму настолько точно, чтобы модели нечего было выбирать. Слово «JSON» само по себе не описание. Блок ниже извлекает данные из баг-репорта. Сравните просьбу о «ключевых деталях» с просьбой, в которой прописана схема.
Вот ключевые детали из баг-репорта:
- Проблема: приложение падает при экспорте проектов
- Условие: нажатие «Экспорт» в проектах, где больше 50 фотографий
- Началось: после последнего обновления
- Платформа: Android 14
- Версия приложения: 3.2.0
- Влияние: высокое, так как блокирует сдачу работы клиенту
Проекты поменьше, судя по всему, экспортируются без проблем.
Ответ свободным текстом точен и читается легко, но в двух запусках метки будут разными, серьёзность описана фразой, а не значением, и программе пришлось бы угадывать, где начинается каждое поле. JSON-ответ можно сразу отправить в баг-трекер. Обратите внимание, что ответ всё равно пришёл внутри блока кода: чат-приложения обычно так оборачивают JSON, и это важно, когда вы разбираете его в коде.
Как просить JSON
Хороший запрос JSON отвечает на каждый вопрос, на который иначе ответила бы за вас модель:
- Каждый ключ в точном написании. Пишите имена ключей в кавычках так, как они должны выглядеть. Говорите «ровно такие ключи», чтобы модель не добавила лишних.
- Тип каждого значения. Строка, число, логическое значение, массив строк, вложенный объект.
- Допустимые значения для любого поля с фиксированным набором, например серьёзность или категория. Без списка за четыре запуска вы получите "High", "high", "severe" и "P1".
- Что делать, когда во входных данных нет значения. Если не сказать «null, если не указано», модель обычно заполняет пробел правдоподобной догадкой, а выдуманная версия приложения выглядит точно так же, как настоящая.
- Ничего вокруг. «Верни только JSON, без текста до или после него» убирает дружелюбную вводную фразу.
Когда форма вложенная или необычная, показать один полный пример объекта работает лучше, чем описывать его. Это few-shot промптинг применительно к формату. Делайте значения в примере явно непохожими на настоящие входные данные, чтобы модель их не скопировала.
Промпт для извлечения данных, который можно переиспользовать
В этом блоке тот же запрос разбит на части. Отключите часть с форматом, и модель всё равно вернёт JSON, потому что его требуют ограничения, но выберет свои имена ключей, например title вместо job_title, и код, который ждёт ваши ключи, сломается. Часть с ограничениями не даёт ей дописать недописанный номер телефона или вывести компанию из домена почты. Вставьте в поле ввода настоящую подпись, чтобы проверить.
{
"name": "строка",
"job_title": "строка или null",
"company": "строка или null",
"email": "строка или null",
"phone": "строка или null"
}{
"name": "Прия Наир",
"job_title": "Руководитель отдела данных",
"company": "Northwind Labs",
"email": "priya.nair@northwind.example",
"phone": null
}
Таблицы и фиксированные шаблоны
Структурированный вывод нужен не только программам. Когда ответ читаете вы сами, таблица markdown или фиксированный шаблон дают то же преимущество: вы знаете, где будет каждая часть информации, ещё до того, как посмотрите.
| Тип | Упорядочен | Изменяемый | Допускает дубликаты | Типичное применение |
|---|---|---|---|---|
| list | Да | Да | Да | Последовательность, в которую добавляют, из которой удаляют или которую сортируют |
| tuple | Да | Нет | Да | Фиксированная группа значений, например координаты |
| set | Нет | Да | Нет | Удаление дубликатов и быстрая проверка вхождения |
Шаблон работает так же для длинного текста: дайте заголовки по порядку и скажите, что идёт под каждым. «Ответь тремя жирными подзаголовками: Причина, Исправление, Как проверить» даёт одни и те же три подзаголовка каждый раз, и пачку ответов легко сравнивать.
JSON mode в API
У нескольких API моделей есть настройка, которая принуждает к синтаксически корректному JSON. В Python SDK от OpenAI это response_format. JSON mode требует, чтобы слово "JSON" встречалось где-то в ваших сообщениях, поэтому системный промпт ниже его называет и перечисляет ключи.
import json
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
report_text = "Since yesterday's update the app crashes when I tap Export..."
response = client.chat.completions.create(
model=MODEL,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"Extract the bug report into JSON with the keys "
"summary (string), severity (one of low, medium, high, critical) "
"and steps_to_reproduce (array of strings)."
),
},
{"role": "user", "content": report_text},
],
)
data = json.loads(response.choices[0].message.content)
JSON mode гарантирует, что текст разбирается (если ответ не обрезан на лимите токенов), но не то, что он соответствует вашей схеме. Ключ всё ещё может отсутствовать, а серьёзность всё ещё может оказаться "urgent". Несколько провайдеров также принимают полную JSON Schema, как формат вывода или как входную схему определения инструмента (функции), и некоторые из этих режимов ограничивают ответ схемой. Точный параметр смотрите в документации своего провайдера: эти функции отличаются от API к API.
Проверяйте результат в коде
Относитесь к JSON от модели так же, как к любым внешним входным данным вашей программы: разберите, затем проверьте. Разбор ловит сломанный синтаксис. Проверка ловит корректный объект с неверным содержимым.
ALLOWED_SEVERITIES = {"low", "medium", "high", "critical"}
def problems(data):
if not isinstance(data, dict):
return ["the answer must be a JSON object"]
found = []
if not isinstance(data.get("summary"), str):
found.append("summary must be a string")
if data.get("severity") not in ALLOWED_SEVERITIES:
found.append("severity must be low, medium, high or critical")
steps = data.get("steps_to_reproduce")
if not isinstance(steps, list) or not all(isinstance(s, str) for s in steps):
found.append("steps_to_reproduce must be an array of strings")
return found
Когда проверка не проходит, часто помогает один повтор: отправьте модели её же вывод вместе со списком проблем и попросите исправленный JSON. Ограничьте число повторов и логируйте неудачи, потому что поле, которое часто не проходит проверку, указывает на неясный промпт. В больших проектах вместо написанной вручную функции используют библиотеку проверки, например Pydantic, или валидатор JSON Schema.
Ещё две привычки предотвращают тихие ошибки. Ставьте лимит выходных токенов достаточно высоким для самого большого ожидаемого ответа, потому что обрезанный объект никогда не разберётся. А когда входные данные длинные или приходят от пользователей, отделяйте их от инструкций разделителями или XML-тегами, чтобы текст внутри реже принимался за инструкцию. Если одному промпту нужно и рассуждать, и выдавать JSON, подумайте о цепочке промптов: пусть один шаг думает свободным текстом, а второй превращает результат в структуру.
Часто задаваемые вопросы
Как заставить ChatGPT отвечать в JSON?
Скажите, что ответ должен быть в JSON, перечислите все ключи с их типами и дайте допустимые значения для каждого поля с фиксированным набором. Добавьте «Верни только JSON, без текста до или после него». В API также включите JSON mode через response_format={"type": "json_object"}, для этого слово JSON должно встречаться в ваших сообщениях.
Почему модель добавляет текст вокруг JSON?
Чат-модели обучены вести разговор, поэтому часто начинают с фразы вроде «Вот JSON» или оборачивают объект в блок кода markdown. Просите только JSON, а в коде либо используйте JSON mode в API, либо убирайте окружающий блок кода перед разбором.
Что такое JSON mode?
JSON mode: это настройка API, при которой модель выдаёт синтаксически корректный JSON, если ответ не обрезан лимитом выходных токенов. Следовать вашей схеме она модель не заставляет: ключи всё ещё могут отсутствовать, быть написаны с ошибкой или иметь не тот тип. Некоторые провайдеры предлагают и более строгий режим, который принимает полную JSON Schema и ограничивает вывод ею.
Может ли LLM всегда возвращать корректный JSON?
Только за счёт промпта нет. Даже ясная инструкция время от времени не срабатывает, а ответ, обрезанный лимитом выходных токенов, всегда некорректен. Разбирайте каждый ответ настоящим JSON-парсером, проверяйте нужные поля и повторяйте запрос или громко падайте, когда проверка не проходит.