Skip to Content
Руководство для пользователейВозможностиСтруктурированный вывод (--json-schema)

Структурированный вывод (--json-schema)

Ограничьте итоговый ответ модели JSON-схемой, которую вы предоставляете. Qwen Code регистрирует синтетический терминальный инструмент, который модель обязана вызвать, разбирает аргументы вызова по вашей схеме и выводит проверенный нагрузочный объект в stdout (или в обёртку JSON / stream-json). Первый успешный вызов завершает сессию.

Только безголовый режим — работает с qwen -p, позиционным промптом или промптом, переданным через stdin.

Быстрый старт

qwen --prompt "Опиши изменения в HEAD с уровнем риска" \ --json-schema '{ "type": "object", "properties": { "summary": { "type": "string" }, "risk_level": { "type": "string", "enum": ["low", "medium", "high"] } }, "required": ["summary", "risk_level"], "additionalProperties": false }'

Вывод в stdout (по умолчанию --output-format text):

{ "summary": "…", "risk_level": "low" }

Строка — это ровно JSON-сериализованный нагрузочный объект + символ новой строки — без обёртки, без лога событий. Можно передавать напрямую в jq или другой потребитель.

В режиме text stdout зарезервирован для JSON-нагрузки при успехе и пуст при неудаче; сообщения об ошибках и строки лога отправляются в stderr. Это делает безопасными конструкции захвата вида $(qwen --json-schema …) || exit 1 в текстовом режиме — неудачи попадают в stderr, а не смешиваются с захваченной переменной. Попутная проза модели во время планирования не дублируется в stderr — текстовый режим отбрасывает её; используйте --output-format json или stream-json, если нужно её увидеть.

В режимах --output-format json и stream-json сообщение о результате неудачи выводится в stdout наряду с успешным путём (как последний элемент JSON-массива или завершающая строка result в JSONL-потоке). Не все режимы сбоя выводят результат в stdout — превышение лимита сессионных шагов (код выхода 53) и прерывания по сигналу (код выхода 130) завершаются только выводом в stderr. Сначала проверяйте код выхода; is_error у объекта результата позволяет различить подмножество сбоев, которые всё же генерируют событие результата.

Пустая схема: Передача {} выдаёт {} (пустой JSON-объект) в stdout. Модель вызывает structured_output без аргументов; путь нормализации аргументов превращает пустой вызов функции в нагрузочный объект-пустышку, который проходит валидацию по пустой схеме и выводится дословно.

Предоставление схемы

Две эквивалентные формы:

# JSON-литерал inline qwen -p "…" --json-schema '{"type":"object", "properties":{…}}' # Чтение из файла qwen -p "…" --json-schema @./schemas/summary.json

Форма @путь раскрывает ~, нормализует путь и читает файл в кодировке utf8.

Замечание по задержке: При успешных запусках возникает задержка завершения не более ~500 мс — фоновые агенты, ещё работающие, сбрасывают свои финальные уведомления, прежде чем будет выдан результат. Задержка завершается раньше, если фоновых задач нет, поэтому простые запуски её почти не замечают; пакетные конвейеры, выполняющие сотни вызовов --json-schema с занятыми агентами, должны учитывать эту верхнюю границу.

Замечание по безопасности: Схемы могут содержать пользовательские регулярные выражения в ключевых словах pattern. Ajv компилирует их с помощью движка регулярных выражений ECMAScript, который уязвим к катастрофическому возврату. Поскольку аргументы инструмента всегда являются объектами, ключевое слово pattern срабатывает только внутри строковых свойств — злонамеренная схема вида {"type":"object","properties":{"value":{"type":"string","pattern":"(a+)+b"}}} может «повесить» CLI, если модель предоставит достаточно длинное совпадающее значение. Запускайте --json-schema только со схемами из надёжных источников.

Валидация на этапе разбора:

  • Файл должен быть обычным файлом (не FIFO, символьные устройства или директории).
  • Размер файла ограничен 4 МиБ. Реальные JSON-схемы значительно меньше этого лимита; файлы размером в несколько МиБ почти всегда означают ошибку с путём.
  • Схема должна быть корректным JSON. Для ввода через @путь сообщение об ошибке разбора будет общим («содержимое <путь> не является корректным JSON») без повторения деталей SyntaxError, чтобы процесс-обёртка, перехватывающий stderr, не мог прочитать фрагмент содержимого файла из сообщения об ошибке.
  • Схема должна компилироваться в строгой конфигурации Ajv — опечатки вроде propertees будут выявлены, но шаблоны, допустимые спецификацией (например, required без перечисления всех ключей из properties), принимаются.
  • Корень схемы должен принимать значения объектного типа. API вызова функций (Gemini, OpenAI, Anthropic) все требуют, чтобы аргументы инструмента были JSON-объектами, поэтому корень не объектного типа зарегистрировал бы непригодный для использования инструмент.

Проверка принятия корня анализирует type, const, enum, anyOf, oneOf, allOf, not и if/then/else (по возможности для разрешимых случаев). В сомнительных ситуациях решение откладывается на Ajv во время выполнения.

Корневой $ref отклоняется проверкой на этапе разбора. Если ваша схема повторно использует определение через $ref, оберните его в allOf:

// Отклонено: { "$ref": "#/$defs/MyObj", "$defs": { "MyObj": { "type": "object", "properties": { "name": { "type": "string" } } } } } // Принято (корень принимает объекты через ветвь allOf): { "allOf": [{ "$ref": "#/$defs/MyObj" }], "$defs": { "MyObj": { "type": "object", "properties": { "name": { "type": "string" } } } } }

$ref внутри anyOf / oneOf / allOf обрабатывается Ajv во время выполнения, поэтому обёрнутая форма проходит проверку на принятие корня.

Формат вывода для каждого формата

--output-formatЧто выводится в stdout
text (по умолчанию)JSON.stringify(payload) + "\n" — одна строка, проверенный объект.
jsonЕдиный JSON массив объектов сообщений (полный журнал событий). Последний элемент — сообщение type: "result", которое содержит как result (JSON.stringify(payload)), так и structured_result (исходный объект).
stream-jsonКаждое событие на отдельной строке в формате JSONL. Завершающая строка result содержит result (строка) и structured_result (исходный объект).

В обоих форматах JSON предпочтительнее читать structured_result вместо result, когда нужен объект; result — это строковое представление, предоставленное для потребителей, которые всегда ожидают строку в этом поле. Для --output-format json читайте последний элемент массива и извлекайте structured_result оттуда (например, jq '.[-1].structured_result'); для stream-json читайте последнюю строку type: "result" в потоке.

Ограничения

КомбинацияПоведение
--json-schema + -i / --prompt-interactiveОтклоняется на этапе разбора. Сообщение синтетического инструмента «session ends now» не имеет терминатора в цикле TUI.
--json-schema + --input-format stream-jsonОтклоняется на этапе разбора. Контракт однократного завершения несовместим с долгоживущим протоколом ввода stream-json.
--json-schema + --acp / --experimental-acpОтклоняется на этапе разбора. ACP запускает собственный цикл обработки, который не соблюдает контракт завершения синтетического инструмента.
--json-schema без подсказки и без stdin через конвейерОтклоняется на этапе разбора. Безголовому режиму нужна подсказка — передайте -p, позиционный аргумент или подайте через конвейер.
--bare + --json-schemaПоддерживается. Синтетический инструмент регистрируется наряду с тремя базовыми (read_file, edit, run_shell_command).
--json-schema внутри субагентаИнструмент НЕ регистрируется. Только основные / сливные ходы верхнего уровня выполняют контракт завершения; субагент, вызывающий инструмент, получит «session ends now» и продолжит работу, так как его цикл не имеет терминатора.

Повторные попытки и режимы ошибок

Замечание о затратах. Два фактора умножают расход токенов в запуске --json-schema, и оба стоит учитывать заранее:

  • Схема встроена в каждый ход. Схема передаётся как блок parameters объявления функции structured_output в каждом запросе к модели, а не только в первом. Большие схемы (до предела разбора в 4 МиБ) пропорционально увеличивают входные токены на каждый ход в течение всего сеанса.
  • Каждая новая попытка проверки — это полный ход модели. Схему, которую модель постоянно пропускает, умножают на каждый сбой (запрос + вывод + ответ). Ограничьте схемы так, чтобы они направляли модель и были достаточно простыми для успеха с первой попытки; увеличьте --max-session-turns, если ожидаются повторные попытки.

Сессия завершается при первом успешном вызове. До этого:

  • Параметры не проходят проверку. structured_output возвращает ошибку инструмента с сообщением Ajv, модель видит её на следующем ходу и может исправить аргументы и вызвать снова.
  • Модель вызывает инструмент с побочными эффектами в том же ходу, что и structured_output. Предварительный просмотр подавляет родственный вызов — он никогда не выполняется, независимо от того, прошла ли в итоге проверка структурированного вызова. Два пути расходятся в том, что модель увидит дальше:
    • Проверка успешна: сеанс завершается немедленно, и модель больше не получает хода — подавленный родственный вызов молча отбрасывается.
    • Проверка не удалась: модель получает ещё один ход и видит синтезированный tool_result с текстом «Skipped:» для подавленного вызова, чтобы она могла повторно отправить этот вызов в отдельном ходу (который не включает structured_output).
  • Модель выводит обычный текст вместо вызова structured_output. Код выхода 1. Сообщение об ошибке содержит номер хода и усечённый предварительный просмотр вывода модели, чтобы вы могли увидеть, что она сказала на самом деле.
  • Сеанс достигает maxSessionTurns. Код выхода 53. Стандартный выход «Достигнуто максимальное количество ходов сеанса», плюс подсказка, специфичная для --json-schema, которая указывает на три распространённые причины зависания: модель никогда не вызывала инструмент, structured_output отклонён правилами разрешений или схема невыполнима.
  • Сеанс прерван (SIGINT / Ctrl-C). Код выхода 130. Структурированный результат обычно не выводится, но цикл удержания завершения не опрашивает сигнал прерывания, поэтому SIGINT, пришедший после успешного захвата вызова, но до того, как результат попал в stdout, всё же может оказаться в stdout. Считайте код выхода источником истины.

Конфиденциальность

Аргументы, которые вы отправляете через structured_output, ЯВЛЯЮТСЯ структурированной полезной нагрузкой — уже выведенной в stdout. Чтобы избежать повторного сохранения той же полезной нагрузки на локальных устройствах, которые могут быть экспортированы с машины, аргументы маскируются с помощью заполнителя { __redacted: 'structured_output payload (see stdout result)' } на:

  • Путь телеметрии ToolCallEvent (экспорт OTLP, QwenLogger, поток ui-telemetry, зеркало событий чат-записи в UI).
  • Файл JSONL чат-записи на диске по адресу ~/.qwen/projects/<sanitized-cwd>/chats/<sessionId>.jsonl (повторно подаются в контекст модели при --continue / --resume), включая каждую повторную попытку при сбое валидации.

Метрики вызова инструментов (длительность, успех, решение) и метаданные окружающих событий сохраняются.

Схема отправляется провайдеру модели. Маскирование затрагивает только аргументы вызова на локальных поверхностях. Сама схема передается в каждом запросе к модели как блок parameters декларации функции structured_output — поэтому любые литеральные значения, которые вы помещаете внутрь (enum, const, default, examples, description, $comment, и т.д.), достигают провайдера в открытом виде, как и текст промпта. Схемы должны описывать форму и ограничения; относитесь к ним как к публичным для провайдера и не помещайте секреты, записи клиентов и другие конфиденциальные данные в тело схемы.

Хуки видят необработанные аргументы. Описанное выше маскирование применяется только к телеметрии и чат-записи. Хуки PreToolUse, PostToolUse и PostToolUseFailure (включая HTTP-хуки, которые могут пересылать полезные нагрузки с устройства) получают немодифицированный tool_input для structured_output, поскольку контракт хука — «видеть то, что видит инструмент». Если вы используете перехватывающие хуки аудит-типа, либо отключите их для structured_output (отфильтровав по tool_name), либо добавьте маскирование на стороне хука перед запуском --json-schema для конфиденциальных данных.

Возобновление сессии (--continue / --resume)

--json-schema является флагом для отдельного запуска, а не свойством сессии. Синтетический инструмент регистрируется, когда CLI разбирает свои аргументы, поэтому:

  • Повторно передавайте --json-schema при каждом --continue / --resume, к которому вы хотите применить терминальный контракт. Та же схема, что и в исходном запуске, является безопасным значением по умолчанию — смена схемы в середине сессии разрешена, но изменяет контракт, которому должна следовать модель.
  • Если вы запускаете --continue без --json-schema, возобновлённая сессия является обычной headless-сессией: structured_output просто не существует как инструмент, и модель будет отвечать произвольным текстом.
  • Заполнитель __redacted в возобновлённой чат-записи на практике не влияет на возможность возобновления. Успешный вызов structured_output немедленно завершает сессию, поэтому единственные маскированные аргументы, которые может увидеть возобновлённый запуск, — это аргументы неудачных попыток. Модель всё ещё имеет ошибку валидации Ajv каждой попытки в записанном tool_result и живую схему параметров (перерегистрированную из --json-schema), чего достаточно для повторной попытки.

Управление разрешениями

structured_output намеренно обходит белый список --core-tools: инструмент существует только тогда, когда установлен --json-schema, поэтому его исключение оставило бы запуск без терминального контракта.

Явные правила permissions.deny и настройки --exclude-tools ДЕЙСТВУЮТ — оба используют один и тот же механизм запрета и оба предотвращают регистрацию structured_output, поэтому модель никогда не видит объявления инструмента. Типичный результат — модель отвечает обычным текстом (выход 1). Если модель зацикливается на других инструментах, так и не создав текст, она в конечном итоге достигнет maxSessionTurns (выход 53), и подсказка --json-schema в сообщении об ошибке укажет вам, где искать.

Предостережение --bare. Обычный режим игнорирует большинство входных данных, полученных из настроек, включая permissions.deny и tools.exclude на уровне настроек. Синтетический инструмент остается зарегистрированным, поэтому запрет structured_output только через настройки будет молча игнорироваться в режиме --bare. Флаг --exclude-tools structured_output на уровне аргументов командной строки по-прежнему работает в bare-режиме — используйте этот флаг, а не настройки, если вам нужно заблокировать bare-запуск.

Конфликт с MCP-инструментами

Если MCP-сервер регистрирует инструмент с буквальным именем structured_output, проверка коллизий в реестре инструментов переименовывает MCP-инструмент в mcp__<server-name>__structured_output, чтобы синтетический инструмент сохранил исходное имя. Предоставленная пользователем схема всегда является той, которую видит модель.

Пример: ограничение многошагового запуска на основе структурированного вывода

RESULT=$(qwen --prompt "Audit this diff and rate its risk." \ --json-schema @./schemas/audit.json) || exit 1 risk=$(jq -r '.risk_level' <<<"$RESULT") if [ "$risk" = "high" ]; then echo "High-risk diff; pausing pipeline." >&2 exit 2 fi

См. также

  • Headless Mode — основанный на -p поток, на который опирается --json-schema.
  • Dual Output — записывает JSON-событие в sidecar рядом с TUI (другой подход к машиночитаемому выводу; не требует --json-schema).
Last updated on