Если запрос к API возвращает 400 и сообщение role 'system' is not supported on this model, не начинайте с ротации ключа, увеличения квоты или повторов. Запрос уже дошел до маршрута, который проверяет форму сообщений, и этот маршрут отклонил конкретное поле messages[n].role. Это конфликт контракта ролей, а не общий сбой авторизации или сети.
Сначала найдите param в структурированном теле ошибки и сравните его с итоговым JSON, который ушел после SDK, фреймворка, middleware, Azure deployment или совместимого gateway. Исправление должно сохранить смысл системной инструкции и перенести ее в самый высокий приоритетный канал, который поддерживает тот же маршрут.
| Признак отказа | Вероятный владелец | Первое безопасное действие | Проверка |
|---|---|---|---|
messages[n].role = system | Endpoint, gateway, Azure deployment или адаптер | Сохранить текст инструкции и перенести его в поддерживаемую instruction surface | Минимальный запрос на том же маршруте больше не падает с role error |
Отклоняется developer | API version, compatibility layer или framework route | Проверить, поддерживает ли текущий Chat Completions маршрут эту роль; иначе использовать Responses instructions или route-specific mapping | Та же форма payload проходит на выбранном маршруте |
Отклоняется tool или старый function | Несовпадение tool-calling protocol | Использовать форму tool message и call id, которую требует маршрут | Tool result принимается без role error |
| Такой роли нет в вашем коде | Agent framework, prompt wrapper, middleware или SDK adapter | Логировать итоговый outbound JSON и отключить или перенастроить injected message | Minimal repro и полный workflow используют один accepted role set |
Остановитесь на этом уровне, прежде чем менять провайдера или параметры ретраев. Доказательство исправления — минимальный запрос против того же provider, base URL, endpoint, API version, model или deployment, SDK adapter и framework route.
Сначала разберите тело ошибки
Полезная часть такой ошибки обычно находится не в человеческой фразе, а в структурированном объекте. Сохраните status, type, code, message и особенно param. Если param указывает на messages[0].role, проверьте первый объект массива. Если индекс другой, ищите сообщение, которое было добавлено историей, инструментом или адаптером.
Не ограничивайтесь массивом, который виден в бизнес-коде. Перед отправкой его могут изменить memory prompt, default system prompt, guardrail middleware, retry wrapper, OpenAI-compatible adapter или gateway. Логировать нужно финальную форму запроса рядом с HTTP boundary.
{
"endpoint": "POST /v1/chat/completions",
"model": "example-model",
"messages": [
{ "role": "system", "content": "[redacted instruction]" },
{ "role": "user", "content": "[redacted user task]" }
]
}
Такой лог должен доказывать только структуру. Не пишите в него API key, bearer token, cookie, приватные системные подсказки, пользовательский текст, содержимое файлов или tool output. Для диагностики достаточно endpoint, model или deployment, индекса сообщения, роли и слоя, который сформировал итоговый payload.

Исправляйте по endpoint, а не по привычке
Главная ошибка — считать, что существует универсальная замена. system не всегда надо менять на developer, а перенос в user не является нейтральным исправлением. Контракт ролей зависит от endpoint, API version, Azure deployment, provider gateway, SDK и иногда от agent framework.
На 21 мая 2026 года Responses API у OpenAI имеет верхнеуровневое поле instructions для разработческих инструкций. Официальные Chat Completions маршруты используют массив messages и могут поддерживать instruction-style роли на текущих маршрутах. Но это не доказывает, что тот же набор ролей принимает ваш Azure route, совместимый gateway, старый SDK adapter или модельный alias у провайдера.
Если вы действительно вызываете официальный Chat Completions маршрут, который принимает developer, перенос может выглядеть так:
{
"model": "your-selected-model",
"messages": [
{ "role": "developer", "content": "Follow the support triage rules." },
{ "role": "user", "content": "Classify this API error." }
]
}
Если маршрут Responses, долгосрочную инструкцию лучше держать в instructions, а пользовательскую задачу передавать через input:
{
"model": "your-selected-model",
"instructions": "Follow the support triage rules.",
"input": "Classify this API error."
}
Если gateway принимает только user и assistant, сначала ищите отдельное поле instruction, настройку adapter, другой upstream route или model option. Если такого канала нет, фиксируйте, что fallback меняет приоритет инструкции. Это уже изменение поведения, а не просто синтаксическая правка.

Разные роли требуют разных веток
Когда отклоняется system, сохраните текст инструкции и перенесите его в поддерживаемую поверхность с максимальным приоритетом. Для официального Chat Completions это может быть developer; для Responses — instructions; для gateway — route-specific поле. Перенос в обычный user message допустим только как осознанный fallback.
Когда отклоняется developer, вы часто находитесь в старом compatibility layer, у провайдера с устаревшей схемой, или в Azure/API-version связке, где эта роль еще не доступна. Сначала подтвердите точный base URL, route и version. Клиентский код может выглядеть одинаково, но серверный контракт будет другим.
Когда отклоняется tool, проверяйте весь tool protocol. Современный tool result обычно требует tool call id и формы сообщения, которая соответствует выбранному API. Старый function — отдельная ветка. Смешивание legacy function role с современным tool-call response способно дать role error даже тогда, когда модель поддерживает инструменты.
Если роль пустая, неверно сериализована, имеет неправильный регистр или потерялась при enum mapping, это клиентская ошибка сборки payload. Добавьте локальную проверку до отправки:
const allowedRoles = new Set(["developer", "user", "assistant", "tool"]);
for (const [index, message] of messages.entries()) {
if (!allowedRoles.has(message.role)) {
throw new Error("Unsupported outbound role at messages[" + index + "].role");
}
}
Локальная проверка не заменяет документацию провайдера, но она не даст случайно сломанному payload попасть в production.
Фреймворк может добавить роль после вашего кода
Фраза «я не отправлял system» часто правдива только для слоя приложения. Agent frameworks, chat UI библиотеки, RAG wrappers, tracing middleware и provider adapters могут собрать финальный массив позже. Поэтому сервер видит messages[0].role = system, хотя в вашем handler такого объекта нет.
| Слой | Что смотреть | Типовой сбой |
|---|---|---|
| Agent framework | Default system prompt, memory prompt, tool policy, guardrail message | Скрытый system или developer появляется перед user input |
| SDK adapter | OpenAI-compatible conversion, enum mapping, legacy tool support | Роли переименованы или сериализованы не так, как ожидает сервер |
| Gateway | Provider mode, upstream route, model alias | Gateway принимает запрос, upstream отклоняет контракт ролей |
| Azure route | Deployment name, api-version, region, model family | Тот же payload работает в одном deployment и падает в другом |
| Middleware | Retry wrapper, prompt decorator, request logger | Обертка добавляет instruction message на каждый вызов |
Правильная точка наблюдения — граница перед HTTP request. Если вы видите только pre-framework объект, вы диагностируете не тот артефакт. Финальный redacted JSON показывает индекс, роль, endpoint и adapter path, а значит сужает ремонт до конкретного владельца.
Проверяйте на том же маршруте
Успех на другом маршруте не доказывает исправление. Тот же маршрут означает тот же provider, base URL, endpoint, API version, model или deployment alias, SDK adapter, framework settings и tool protocol. Меняйте один параметр за раз.
Минимальный smoke test должен сохранять ту ветку, которая раньше падала:
curl "$BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-selected-model",
"messages": [
{"role": "developer", "content": "Answer in one sentence."},
{"role": "user", "content": "Say ready."}
]
}'
Если одновременно перейти с Azure на direct OpenAI, сменить API version, переименовать модель, отключить фреймворк и упростить prompt, результат перестает быть диагностическим. Идеальный тест либо проходит с исправленным payload, либо возвращает другую ошибку, показывая, что role contract больше не блокирует запрос.
После минимального теста запустите полный app или agent flow с включенным role logging. Это ловит случаи, когда фреймворк снова добавляет system, developer, tool или старый function после успешного минимального запроса.
Эскалация должна содержать доказательства
Если минимальный same-route repro все еще падает, готовьте пакет для провайдера, команды Azure, владельца gateway или автора фреймворка. Одной фразы из ошибки недостаточно: без route, version, adapter и request id владелец не поймет, какой контракт был применен.
В пакет стоит включить:
- redacted request JSON без секретов и пользовательских данных
- точный base URL и path или название provider route
- model, deployment, region и API version
- версии SDK, framework, adapter и gateway
- response body, status code,
paramиcode - request ID, correlation ID или gateway trace ID
- минимальный same-route repro и одну контролируемую правку

Не отправляйте API keys, bearer tokens, private prompts, customer text, file content или screenshots с секретами. Если владелец маршрута подтверждает, что нужная роль не поддерживается, остаются честные варианты: перейти на endpoint с подходящей instruction surface, настроить adapter, или задокументировать изменение поведения fallback route.
Чеклист быстрого восстановления
- Сохраните response body и failing
param. - Залогируйте финальный outbound JSON после framework и adapter transforms.
- Определите владельца: direct OpenAI, Azure, provider gateway, SDK или framework.
- Перенесите instruction text в самый приоритетный канал, который поддерживает маршрут.
- Проверяйте
toolи legacyfunctionотдельно от instruction roles. - Запустите минимальный same-route smoke test.
- Запустите полный workflow с включенным role logging.
- Эскалируйте только с sanitized packet, если исправленный запрос все еще падает.
Такой порядок защищает и диагностику, и системные инструкции. Он не позволяет спрятать проблему под «просто положим все в user», когда ошибка исчезает, но контроль модели становится слабее.
Часто задаваемые вопросы
Это значит, что API key неверный?
Обычно нет. Неверный ключ падает на authentication до проверки message roles. Здесь маршрут уже распарсил payload и отклонил конкретную роль. Сначала проверьте контракт ролей.
Надо ли заменить system на developer везде?
Нет. developer подходит только маршрутам Chat Completions, которые его поддерживают. Responses использует другой instruction surface, а gateway или Azure deployment может иметь собственный контракт. Маппинг выбирается по endpoint.
Безопасно ли положить system instructions в user?
Только как последний fallback и только если вы готовы принять изменение поведения. User message обычно не имеет того же приоритета, что system, developer или верхнеуровневые instructions.
Почему ошибка появилась после обновления фреймворка?
Фреймворк мог поменять default role mapping, добавить hidden instruction message или перейти с legacy function protocol на modern tool format. Смотрите final HTTP payload, а не только объект до фреймворка.
Direct OpenAI работает, а совместимый provider нет. Что делать?
Это различие контракта provider route. Сохраните тот же payload, endpoint, model alias и provider trace, затем уточните, какие roles и tool-message shapes поддерживает конкретный route.
Что сохранить для support ticket?
Сохраните sanitized request JSON, точный endpoint, model или deployment, API version, SDK и framework versions, response body, param, request ID и minimal same-route repro. Секреты и пользовательские данные удаляются до отправки.
