# API 400 の role 未対応エラーを、システム指示を壊さず直す

> unsupported role は role contract mismatch です。param、final JSON、endpoint mapping、same-route smoke test で指示優先度を保って直します。

- URL: https://blog.laozhang.ai/ja/posts/api-400-role-system-is-not-supported-on-this-model
- Published: 2026-05-21
- Updated: 2026-05-21
- Author: LaoZhang AI Team (https://blog.laozhang.ai/ja/about)
- Category: API トラブルシューティング
- Tags: API 400, OpenAI API, System Prompt, Developer Message, Responses API

---
API が <code>400</code> を返し、<code>role 'system' is not supported on this model</code> と出る場合、最初に疑うべきものは API キー、残高、リトライ回数ではありません。リクエストはメッセージ構造を検査するルートまで届いており、そのルートが最終的に送信された <code>messages[n].role</code> を拒否しています。これはロール契約の不一致です。

まずエラー本文の <code>param</code> を読み、SDK、フレームワーク、Azure deployment、互換 gateway、middleware を通過した後の最終 outbound JSON と照合します。修正では、システム指示の意味と優先度を保ち、その同じルートが受け付ける最も高い優先度の instruction surface へ移してください。

| 失敗の手がかり | 主な責任箇所 | 最初の安全な修正 | 検証 |
| --- | --- | --- | --- |
| <code>messages[n].role = system</code> | endpoint、provider gateway、Azure deployment、adapter contract | 指示テキストを保持し、そのルートが許す instruction surface に写す | 同じルートの最小リクエストで role error が消える |
| <code>developer</code> が拒否される | API version、compatibility layer、framework route | 現在の Chat Completions ルートがその role を受けるか確認し、無理なら Responses <code>instructions</code> か route-specific mapping を使う | 同じ payload 形状が選んだルートで通る |
| <code>tool</code> または旧 <code>function</code> が失敗する | tool-calling protocol の不一致 | その API が要求する tool message と call id の形に合わせる | tool result が role error なしで受け付けられる |
| ソースコードにその role がない | agent framework、prompt wrapper、middleware、SDK adapter | 最終 outbound JSON を記録し、注入された message を無効化または再マップする | minimal repro と本番フローで同じ accepted role set を使う |

ここで止まり、キー変更、quota 調整、timeout 変更、provider 切り替えに進まないでください。それらは不正なロール契約を直しません。完了条件は、同じ provider、base URL、endpoint、API version、model または deployment、SDK adapter、framework route で最小リクエストが通ることです。

## エラー本文を読んでからコードを変える

この 400 で重要なのは自然文のエラーメッセージではなく、構造化されたエラーオブジェクトです。<code>status</code>、<code>type</code>、<code>code</code>、<code>message</code>、そして <code>param</code> を保存します。<code>param</code> が <code>messages[0].role</code> を指すなら先頭メッセージ、別の index なら履歴、tool result、middleware が追加した message を疑います。

アプリのソースに書いた配列だけを見ても足りません。送信前に memory prompt、default system prompt、tool policy、retry wrapper、OpenAI-compatible adapter、gateway が配列を作り替えることがあります。HTTP に最も近い境界で、脱秘した最終 payload を記録してください。

    {
      "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、message index、role、そしてどの層が最終 payload を作ったかです。

![unsupported role エラーの責任箇所を決めるロール契約マップ](https://blog.laozhang.ai/posts/ja/api-400-role-system-is-not-supported-on-this-model/img/role-contract-map.webp)

## 習慣ではなく endpoint で修正を選ぶ

危ない修正は、<code>system</code> を一律に <code>developer</code> へ変えること、またはすべての指示を <code>user</code> に落とすことです。ロール対応は endpoint、API version、Azure deployment、provider gateway、SDK、framework adapter の組み合わせで変わります。

2026-05-21 時点で、OpenAI Responses には開発者指示を置くための上位 <code>instructions</code> があり、公式 Chat Completions には <code>messages</code> 配列と現在の公式ルートで使われる instruction-style role があります。ただしこれは公式 OpenAI ルートの契約であり、Azure、互換 gateway、古い adapter、provider の model alias にそのまま広げることはできません。

現在の公式 Chat Completions ルートが <code>developer</code> を受けるなら、修正は次のようになります。

    {
      "model": "your-selected-model",
      "messages": [
        { "role": "developer", "content": "Follow the support triage rules." },
        { "role": "user", "content": "Classify this API error." }
      ]
    }

Responses ルートなら、継続的な指示は <code>instructions</code> に置き、ユーザータスクは <code>input</code> へ送ります。

    {
      "model": "your-selected-model",
      "instructions": "Follow the support triage rules.",
      "input": "Classify this API error."
    }

互換 gateway が <code>user</code> と <code>assistant</code> しか受けない場合でも、すぐに system 指示を user message に入れないでください。専用 instruction field、model option、adapter 設定、別 route がないか確認します。ない場合は、指示優先度を失う可能性を behavior change として扱います。

![指示優先度を保つ payload 修復の前後比較](https://blog.laozhang.ai/posts/ja/api-400-role-system-is-not-supported-on-this-model/img/payload-repair.webp)

## role ごとに直す場所を分ける

<code>system</code> が拒否される場合、修正の目的は system 指示の制御力を保つことです。公式 Chat Completions では <code>developer</code>、Responses では <code>instructions</code>、gateway では route-specific field になることがあります。普通の user message への降格は最後の手段であり、出力制御が弱くなることを記録してください。

<code>developer</code> が拒否される場合、古い compatibility layer、provider 側の旧 schema、または Azure/API version の組み合わせが原因になりやすいです。base URL と API version が違えば、クライアントコードがほぼ同じでもサーバーの role contract は変わります。

<code>tool</code> が拒否される場合は role 名だけでなく、tool-calling protocol 全体を見ます。tool call id、assistant 側の tool call、tool result message の形が現在の API と一致している必要があります。旧 <code>function</code> message は別分岐で、新旧を混ぜるとモデルが tool 対応でも role error になります。

role が null、欠落、大文字小文字違い、enum serialization のズレで壊れる場合は、client construction bug です。送信前にローカルで止めます。

    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");
      }
    }

この検証は provider docs の代わりではありませんが、不正な payload を本番まで流さないための安全網になります。

## フレームワークは見えない場所で role を足す

「自分のコードでは system を送っていない」という説明は、アプリ層だけなら正しいことがあります。agent framework、chat UI library、RAG wrapper、memory module、tracing middleware、provider adapter は、送信直前に messages 配列を完成させます。

| 層 | 確認するもの | 典型的な失敗 |
| --- | --- | --- |
| Agent framework | default system prompt、memory prompt、tool policy、guardrail message | user input の前に hidden <code>system</code> または <code>developer</code> が入る |
| SDK adapter | OpenAI-compatible conversion、enum mapping、legacy tool support | role 名が route の契約と違う形に変換される |
| Gateway | provider mode、upstream route、model alias | gateway は受けるが upstream が role contract を拒否する |
| Azure route | deployment name、<code>api-version</code>、region、model family | 同じ payload が別 deployment では通る |
| Middleware | retry wrapper、prompt decorator、request logger | wrapper が毎回 instruction message を追加する |

観測点は HTTP request に最も近い境界です。pre-framework object しか見ていないなら、サーバーが検査した artifact をまだ見ていません。脱秘済みの最終 JSON は index、role、endpoint、adapter path を示し、修正対象を絞ります。

## 同じルートで smoke test する

別ルートで成功しても、失敗したルートが直った証明にはなりません。同じルートとは、provider、base URL、endpoint、API version、model または deployment alias、SDK adapter、framework settings、tool protocol が同一であるという意味です。

最小テストは、失敗した role branch だけを残します。

    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 を変える、model alias を変える、framework を外す、prompt を短くする、これらを同時に行うと診断になりません。理想のテストは、修正後 payload が通るか、role contract ではない別エラーに変わるかのどちらかです。

最小テストが通った後、role logging を入れたまま本番に近い agent flow を一度走らせます。framework が <code>system</code>、<code>developer</code>、<code>tool</code>、旧 <code>function</code> を再注入するケースをここで検出します。

## エスカレーションには証拠を入れる

同じルートの最小再現がまだ落ちる場合、provider、Azure 管理者、gateway 担当、framework 作者へ渡す packet を作ります。エラー文だけでは弱く、相手は endpoint、version、adapter、request id を推測することになります。

含めるべきものは次の通りです。

- 秘密情報とユーザーデータを除いた request JSON
- 正確な base URL と path、または provider route 名
- model、deployment、region、API version
- SDK、framework、adapter、gateway の version
- response body、status code、<code>param</code>、<code>code</code>
- request ID、correlation ID、gateway trace ID
- minimal same-route repro と、試した一つの controlled repair

![unsupported role のエスカレーション用 evidence packet](https://blog.laozhang.ai/posts/ja/api-400-role-system-is-not-supported-on-this-model/img/escalation-packet.webp)

API key、Bearer token、private prompt、customer text、file content、tool output、秘密が写る screenshot は含めません。route が必要な role を受けないと確認されたら、正しい instruction surface を持つ endpoint へ移るか、adapter で優先度を保つか、fallback による behavior change を明記します。

## 復旧チェックリスト

1. response body と failing <code>param</code> を保存する。
2. framework と adapter 変換後の final outbound JSON を記録する。
3. direct OpenAI、Azure、gateway、SDK、framework のどこが owner か分ける。
4. instruction text をその route が支持する最優先 surface へ移す。
5. <code>tool</code> と legacy <code>function</code> は instruction role と別に検証する。
6. minimal same-route smoke test を走らせる。
7. role logging を残したまま full workflow を再実行する。
8. まだ失敗する場合だけ、sanitized packet でエスカレーションする。

この順序なら signal が濁りません。400 を消すためだけに system instruction を user message へ落として、後からモデル制御が弱くなる事故も避けられます。

## よくある質問

### API key が間違っているという意味ですか？

通常は違います。key が間違っていれば authentication の段階で失敗し、message role の検査まで進みません。このエラーは、ルートが payload を解析したうえで特定の role を拒否したという意味です。

### <code>system</code> をすべて <code>developer</code> に置き換えればよいですか？

いいえ。<code>developer</code> は、それを受け付ける Chat Completions ルートでだけ有効です。Responses には <code>instructions</code> があり、Azure や互換 gateway には別契約があり得ます。

### system instruction を <code>user</code> に入れても安全ですか？

最後の fallback としてのみ扱ってください。user message は通常、system、developer、top-level instructions と同じ優先度を持ちません。エラーは消えても挙動が変わる可能性があります。

### framework upgrade の後に出るのはなぜですか？

default role mapping、hidden instruction message、tool protocol の変換が変わった可能性があります。アプリが framework に渡した object ではなく、final HTTP payload を見てください。

### direct OpenAI では通るのに互換 provider で落ちます

それは provider route の契約差です。同じ payload、endpoint、model alias、provider trace を保存し、その route が受ける roles と tool message shapes を確認してください。

### support ticket には何を入れるべきですか？

sanitized request JSON、endpoint、model または deployment、API version、SDK と framework の version、response body、<code>param</code>、request ID、minimal same-route repro を入れます。秘密情報とユーザーデータは除きます。
