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

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

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

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

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

qwen --prompt "Summarize the changes in HEAD with risk_level" \ --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 или другой потребитель.

В текстовом режиме 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 qwen -p "…" --json-schema '{"type":"object", "properties":{…}}' # Чтение из файла qwen -p "…" --json-schema @./schemas/summary.json

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

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

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

Валидация во время разбора:

  • Файл должен быть обычным файлом (не FIFO, символьные устройства или каталоги).
  • Размер файла ограничен 4 МиБ. Реальные JSON-схемы значительно меньше этого; файлы размером в несколько МБ почти всегда указывают на ошибку с путём.
  • Схема должна быть допустимым JSON. Для ввода @path сообщение об ошибке разбора является общим («содержимое <путь> не является допустимым 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Отклоняется во время разбора. Сообщение синтетического инструмента «сеанс заканчивается сейчас» не имеет терминатора в цикле TUI.
--json-schema + --input-format stream-jsonОтклоняется во время разбора. Однократный терминальный контракт несовместим с долгоживущим протоколом ввода stream-json.
--json-schema + --acp / --experimental-acpОтклоняется во время разбора. ACP запускает собственный цикл шагов, который не соблюдает терминальный контракт синтетического инструмента.
--json-schema без промпта и без ввода через stdinОтклоняется во время разбора. Headless-режиму нужен промпт — передайте -p, позиционный аргумент или подайте через stdin.
--bare + --json-schemaПоддерживается. Синтетический инструмент регистрируется вместе с базовыми тремя (read_file, edit, run_shell_command).
--json-schema внутри под-агентаИнструмент НЕ регистрируется. Только основные / дренажные шаги верхнего уровня соблюдают терминальный контракт; под-агент, вызвавший инструмент, получит сообщение «сеанс заканчивается сейчас» и продолжит работу, так как его цикл не имеет терминатора.

Повторные попытки и сценарии сбоя

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

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

Сеанс завершается на первом валидном вызове. До тех пор:

  • Аргументы не проходят валидацию. structured_output возвращает ошибку результата инструмента с сообщением Ajv; модель видит её на следующем шаге и может исправить аргументы и вызвать снова.
  • Модель вызывает инструмент с побочным эффектом на том же шаге, что и structured_output. Предварительное сканирование подавляет этот вызов — он никогда не выполняется, независимо от того, пройдёт ли структурированный вызов валидацию в итоге. Два пути расходятся на том, что модель увидит далее:
    • Валидация успешна: сеанс немедленно завершается, и модель не получает следующего шага — подавленный вызов молча отбрасывается.
    • Валидация не удалась: модель получает следующий шаг и видит синтезированный tool_result с пометкой «Пропущено:» для подавленного вызова, чтобы она могла повторно выполнить этот вызов на отдельном шаге (который не включает 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. 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 "Высокорисковый diff; приостановка пайплайна." >&2 exit 2 fi

Смотрите также

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