> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# リアルタイム会話

> WebSocketで永続的な接続を確立し、音声対話モデルと低遅延の双方向音声・テキスト対話を行う

## 概要

リアルタイム会話は、WebSocket（クライアントとサーバー間で長時間接続を維持し、双方向にデータをプッシュできるプロトコル）で永続的な接続を確立し、音声またはテキストの入力をリアルタイムで対話モデルに送り、モデルがテキストと音声の返答をインクリメンタルにプッシュします。音声アシスタント、リアルタイムQ\&A、会話練習など、やり取りを繰り返すシーンに適しています。

[リアルタイム音声認識](/jp/api/realtime-transcription)と同じく WebSocket を使用しますが、用途が異なります：

| 項目          | リアルタイム音声認識                        | リアルタイム会話（本ページ）               |
| ----------- | --------------------------------- | ---------------------------- |
| 目的          | 音声をテキストに変換する                      | モデルと複数ターンの対話を行い、モデルが返答を生成する  |
| 方向          | 単方向：音声が入り、テキストが出る                 | 双方向：音声・テキストが入り、テキストと音声が出る    |
| 接続パラメータ     | `?intent=transcription&model=...` | `?model=...` のみ（`intent` なし） |
| 音声活動検出（VAD） | 非対応、`null` が必須                    | 対応、自動ターン検出を有効化できる            |
| 典型的な用途      | 会議字幕、ライブ配信の文字起こし                  | 音声アシスタント、リアルタイムの音声インタラクション   |

**利用可能なモデル：**

* **gpt-realtime-2.1**：音声対話モデル。音声とテキストの入力に対応し、テキストと音声の返答をリアルタイムで出力します。

<Warning>
  **本APIはサーバーサイド統合向けであり、ブラウザから直接接続することはできません。** セキュリティ上の理由から、ゲートウェイは `Origin` ヘッダー付きの接続を検証して拒否し、`openai-insecure-api-key` サブプロトコルを拒否します。APIキーは標準の `Authorization` ヘッダーのみを受け付けます。ブラウザから発起される WebSocket は自動的に `Origin` ヘッダーを付加するため、拒否されます。フロントエンドでリアルタイム会話を行う必要がある場合は、自身のサーバーサイドでゲートウェイへの接続を確立し、音声と結果をフロントエンドとサーバーサイドの間で転送してください。
</Warning>

## クイックスタート

### 接続エンドポイント

```text theme={null}
wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1
```

* `model=gpt-realtime-2.1`：**必須**、モデルは接続時に URL パラメータで固定され、セッション中は変更できません（下記の制約を参照）。
* **転写との違いに注意**：会話エンドポイントには `intent=transcription` を**付けません**。

### 認証

ハンドシェイク時に標準の HTTP ヘッダーでAPIキーを渡します：

```text theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

### オーディオ形式の要件

現在、音声の入力と出力はそれぞれ1つの形式のみに対応しています。送信前に音声を次に変換してください：

* **エンコード**：PCM16（16ビット符号付き整数、リトルエンディアン）
* **サンプルレート**：24000 Hz
* **チャンネル**：モノラル（mono）

つまり `audio/pcm@24000` です。入力または出力で他の形式（G.711/µ-law など）を宣言すると拒否され、セッションが閉じられます。

<Note>
  転写と異なり、会話セッションは音声活動検出（turn\_detection / VAD）に**対応しています**。有効にするとモデルが発話の終わりを自動判定して返答をトリガーします。無効（`null` を設定）にすると、音声のコミットと返答要求のタイミングを手動で制御します。必要に応じて選択してください。
</Note>

## セッション構成（session.update）

接続確立後、クライアントは `session.update` を1フレーム送信して会話パラメータ（音声のトーン、システム指示、VAD の有効化など）を構成できます。会話セッションのモデルは接続 URL で固定されているため、**`session.update` を送信しなくてもそのまま会話できます**。音色や指示をカスタマイズしたい場合に送信してください。

```json theme={null}
{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "instructions": "You are a helpful voice assistant. Keep answers concise.",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "turn_detection": { "type": "server_vad" }
      },
      "output": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "voice": "alloy"
      }
    }
  }
}
```

### 構成パラメータ

<ParamField body="session.type" type="string" required>
  セッションタイプ。会話では `realtime` です。
</ParamField>

<ParamField body="session.instructions" type="string">
  システム指示。モデルの役割、トーン、回答の制約を設定します。
</ParamField>

<ParamField body="session.output_modalities" type="string[]">
  出力モダリティ。`["audio"]` または `["text"]` を指定します。`["audio"]`（デフォルト）では音声を出力し、返答テキストは `response.output_audio_transcript.delta` イベントで返ります。`["text"]` ではテキストのみを出力し、テキストは `response.output_text.delta` イベントで返ります。その他の組み合わせ（`["audio", "text"]` など）は拒否され `error` イベントが返ります。
</ParamField>

<ParamField body="session.audio.input.format" type="object" required>
  入力オーディオ形式。`{ "type": "audio/pcm", "rate": 24000 }` で固定です。
</ParamField>

<ParamField body="session.audio.input.turn_detection" type="object | null">
  音声活動検出。`{ "type": "server_vad" }` を渡すと自動ターン検出を有効化し、`null` を渡すと無効化してクライアントから手動でコミットと返答要求を行います。
</ParamField>

<ParamField body="session.audio.output.format" type="object" required>
  出力オーディオ形式。`{ "type": "audio/pcm", "rate": 24000 }` で固定です。
</ParamField>

<ParamField body="session.audio.output.voice" type="string">
  返答音声のトーン。**最初の返答が始まると変更できません**：セッションが生成状態に入った後に再度送信された `voice` は無視されます（その他の設定は通常どおり反映されます）。音色を指定する場合は、最初の返答要求の前に設定してください。
</ParamField>

<Note>
  以下の機能は**本リリースでは未対応**で、構成するとセッションが閉じられます（クローズコード `1008`）：会話セッション内でのインライン転写の有効化（`audio.input.transcription`、理由 `input_transcription_not_supported`）、会話アイテムによる音声（理由 `item_audio_not_supported`）または画像（理由 `image_input_not_supported`）の注入、テキスト以外のコンテンツアイテム種別（理由 `unsupported_content_part`）。音声入力はすべて `input_audio_buffer.append` チャネルから送信してください。
</Note>

## 入力の送信

### 音声の送信

PCM16 音声を小さなチャンク（例：100ms ごとに1チャンク）に分割し、base64 エンコードして `input_audio_buffer.append` イベントで継続的に送信します：

```json theme={null}
{
  "type": "input_audio_buffer.append",
  "audio": "<base64 エンコードされた PCM16 音声チャンク>"
}
```

VAD を有効にすると、モデルが発話の終わりを自動判定して返答をトリガーします。VAD を無効にすると、一定の音声を送信した後に手動でコミットし返答を要求します：

```json theme={null}
{ "type": "input_audio_buffer.commit" }
{ "type": "response.create" }
```

### テキストの送信

テキストメッセージを直接注入して返答を要求することもできます：

```json theme={null}
{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [{ "type": "input_text", "text": "Introduce yourself in one sentence." }]
  }
}
{ "type": "response.create" }
```

## 返答の受信

サーバーはイベントを継続的にプッシュします。主要なイベントタイプ：

<ParamField body="session.created / session.updated" type="event">
  セッション作成と構成更新の確認。`session.created` を受け取ると、音声とテキストを送信できます。
</ParamField>

<ParamField body="conversation.item.added / conversation.item.done" type="event">
  会話アイテムの書き込み完了。ユーザー入力とモデルの返答がそれぞれ1つのアイテムになります。
</ParamField>

<ParamField body="input_audio_buffer.speech_started / input_audio_buffer.speech_stopped" type="event">
  VAD 有効時、ユーザーが話し始めたこと、話し終えたことをサーバーが検出した通知。`speech_started` は通常ユーザーがモデルに割り込んだことを意味します。処理方法は[割り込みと切り詰め](#interruption)を参照してください。
</ParamField>

<ParamField body="response.created" type="event">
  1 ターンの返答の生成が開始されました。
</ParamField>

<ParamField body="response.output_item.added / response.output_item.done" type="event">
  このターンの出力アイテムの開始と終了。`added` イベントの `item.id` は、後で音声を切り詰めるときに参照する会話アイテム ID です。
</ParamField>

<ParamField body="response.output_audio.delta / response.output_audio.done" type="event">
  返答音声の**増分**チャンク（base64 エンコードされた PCM16）と終了マーカー。受信しながら再生できます。
</ParamField>

<ParamField body="response.output_audio_transcript.delta / response.output_audio_transcript.done" type="event">
  返答音声に対応する文字起こしの**増分**と終了マーカー。`delta` フィールドに今回追加されたテキストが入ります。**音声出力を有効にした場合、返答テキストはこのイベントから取得します**。音声の再生に合わせて字幕を表示できます。
</ParamField>

<ParamField body="response.output_text.delta / response.output_text.done" type="event">
  テキストのみの返答の**増分**と終了マーカー。出力モダリティをテキストのみ（`output_modalities: ["text"]`）に設定した場合にのみ発生します。
</ParamField>

<ParamField body="response.done" type="event">
  1 ターンの返答が完了。このイベントはこのターンの token 使用量（`usage`）を伴い、課金の根拠になります。
</ParamField>

<ParamField body="conversation.item.truncated" type="event">
  切り詰め要求が反映されたことの確認。[割り込みと切り詰め](#interruption)を参照してください。
</ParamField>

<ParamField body="error" type="event">
  エラーイベント。エラーコードと説明を含みます。リクエスト自体の問題（`output_modalities` の値が不正など）では `error` イベントが 1 件返るだけでセッションは継続して利用できます。ポリシーに関わる場合（モデル変更、残高枯渇など）はセッションが閉じられます。
</ParamField>

<Note>
  **返答テキストはイベントを間違えないこと。** デフォルト（出力に音声を含む）では、モデルは `response.output_audio_transcript.delta` のみをプッシュし、`response.output_text.delta` は**プッシュしません**。出力モダリティをテキストのみに設定すると、テキストは `response.output_text.delta` に移ります。どちらのモードでも両方を監視して、文字の取りこぼしを避けてください（下記の[実行結果](#run-output)を参照）。
</Note>

<h2 id="interruption">
  割り込みと切り詰め
</h2>

モデルが話している途中でユーザーが話し始めると、生成済みでまだ再生していない内容がユーザーの次の発話とずれます。WebSocket 接続では再生をクライアントが担うため、割り込み後の後処理もクライアントが行います。

VAD 有効時、サーバーはユーザーが話し始めたことを検出すると `input_audio_buffer.speech_started` を送信します。このイベントを受信したクライアントは次の処理を行います：

1. **ローカル再生を直ちに停止**し、このターンの返答をどこまで再生したか（ミリ秒）を記録します。
2. `conversation.item.truncate` を送信し、再生していない音声を会話から取り除きます。これにより、次のターンでモデルがその内容を発話済みとして扱うことを防ぎます。

```json theme={null}
{
  "type": "conversation.item.truncate",
  "item_id": "item_ABC123",
  "content_index": 0,
  "audio_end_ms": 1500
}
```

* `item_id`：このターンの返答の会話アイテム ID。`response.output_item.added` イベントの `item.id` から取得します。
* `content_index`：音声コンテンツパートのインデックス。常に `0` です。
* `audio_end_ms`：保持する音声の長さ（ミリ秒）。クライアントが実際に再生した位置を指定します。

サーバーが処理を終えると `conversation.item.truncated` が返ります。切り詰めはこのターンの音声と対応する文字起こしにのみ影響し、セッション自体には影響しないため、そのまま次のターンへ進めます。OpenAI SDK では `conn.conversation.item.truncate(item_id=..., content_index=0, audio_end_ms=...)` に対応します。

VAD を無効にする場合（プッシュトゥトークなど）は、ボタンを押した時点が割り込みです。押下時に `response.cancel` を送信して進行中の返答をキャンセルし、上記の手順で切り詰めます。離したら `input_audio_buffer.append`、`input_audio_buffer.commit`、`response.create` の順に送信します。

## 完全なサンプル

以下に3つの書き方を示します。いずれかを選んでください：

* **OpenAI 公式 SDK（推奨）**：WebSocket を手書きする必要がなく、`websocket_base_url`（SDK の WebSocket ベース URL パラメータ）をゲートウェイに向けるだけで公式ライブラリを再利用できます。
* **OpenAI Agents SDK**：公式 agent フレームワークのリアルタイム音声形態。`model_config` の `url` をゲートウェイのアドレスに差し替えるだけです。
* **生の websockets**：SDK をインストールせず、プロトコルに従ってフレームを直接送受信します。依存が最も少なく、調査が容易です。

<Note>
  **なぜ接続時にモデル名を渡すのか？** AiHubMix ゲートウェイは WebSocket ハンドシェイクの時点で `model` を使い、モデルプロバイダーの選択、認証、割り当ての予約を行う必要がありますが、`session.update` はハンドシェイク完了後に到着するため間に合いません。したがって SDK を使う場合は `connect()` に `model` を明示的に渡してください（SDK が URL クエリに組み込みます）。これがないとゲートウェイは**ハンドシェイク時に拒否**し、接続は確立できません。転写と異なり、会話エンドポイントに `intent=transcription` は**不要**です。
</Note>

<CodeGroup>
  ```python Python (OpenAI SDK) theme={null}
  # 依存関係: pip install "openai[realtime]"
  import asyncio
  import base64
  from openai import AsyncOpenAI

  # 公式 SDK の再利用は2点の適応のみ:
  #   1) websocket_base_url を AiHubMix ゲートウェイに向ける(OpenAI の既定アドレスではない)
  #   2) connect() に model を明示的に渡す(ゲートウェイのハンドシェイクに必須、会話に intent は不要)
  client = AsyncOpenAI(
      api_key="sk-***",  # AiHubMix の API キーに置き換えてください
      websocket_base_url="wss://aihubmix.com/v1",
  )

  async def main():
      async with client.realtime.connect(model="gpt-realtime-2.1") as conn:
          # 1) セッションを構成: システム指示 + 音色 + VAD を有効化(自動ターン検出)
          await conn.session.update(session={
              "type": "realtime",
              "instructions": "You are a helpful voice assistant. Keep answers concise.",
              "audio": {
                  "input": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "turn_detection": {"type": "server_vad"},
                  },
                  "output": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "voice": "alloy",
                  },
              },
          })

          # 2) テキストメッセージを送信して返答を要求(音声入力は生サンプルの append/commit を参照)
          await conn.conversation.item.create(item={
              "type": "message",
              "role": "user",
              "content": [{"type": "input_text", "text": "What is the capital of France?"}],
          })
          await conn.response.create()

          # 3) このターンの返答を受信: テキストは転写デルタ、音声はオーディオデルタ
          async for event in conn:
              if event.type == "response.output_audio_transcript.delta":
                  print(event.delta, end="", flush=True)  # 返答テキスト(出力に音声を含む場合)
              elif event.type == "response.output_audio_transcript.done":
                  print()  # このターンの文字起こし終了
              elif event.type == "response.output_text.delta":
                  print(event.delta, end="", flush=True)  # テキストのみの出力の場合
              elif event.type == "response.output_text.done":
                  print()
              elif event.type == "response.output_audio.delta":
                  pass  # base64 PCM16 音声チャンク、デコードして再生可能
              elif event.type == "input_audio_buffer.speech_started":
                  pass  # ユーザーの割り込み: 再生を止めて conn.conversation.item.truncate(...) で切り詰め
              elif event.type == "response.done":
                  print("\n[ターン完了]", event.response.usage)
                  break
              elif event.type == "error":
                  print("\n[エラー]", event.to_dict())
                  break

  asyncio.run(main())
  ```

  ```python Python (websockets) theme={null}
  import asyncio
  import base64
  import json
  import websockets

  API_KEY = "sk-***"  # AiHubMix の API キーに置き換えてください
  URL = "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1"

  async def main():
      # websockets >= 13 は additional_headers、古いバージョンは extra_headers
      async with websockets.connect(
          URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
      ) as ws:
          # 1) セッションを構成(VAD を無効化し、返答のタイミングを手動制御)
          await ws.send(json.dumps({
              "type": "session.update",
              "session": {
                  "type": "realtime",
                  "instructions": "You are a helpful voice assistant.",
                  "audio": {
                      "input": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "turn_detection": None,
                      },
                      "output": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "voice": "alloy",
                      },
                  },
              },
          }))

          # 2) ローカルの PCM16 / 24kHz / モノラルの生音声を読み込み、チャンク送信して commit + 返答要求
          async def send_audio():
              with open("audio_pcm16_24k.raw", "rb") as f:
                  pcm = f.read()
              chunk = 24000 * 2 // 10  # 100ms = サンプルレート x 2バイト / 10
              for i in range(0, len(pcm), chunk):
                  await ws.send(json.dumps({
                      "type": "input_audio_buffer.append",
                      "audio": base64.b64encode(pcm[i:i + chunk]).decode(),
                  }))
                  await asyncio.sleep(0.1)  # リアルタイムのペースを模擬
              # VAD 無効時は手動でコミットして返答を要求
              await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
              await ws.send(json.dumps({"type": "response.create"}))

          asyncio.create_task(send_audio())

          # 3) 返答を受信
          async for msg in ws:
              evt = json.loads(msg)
              etype = evt.get("type", "")
              if etype == "response.output_audio_transcript.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # 返答テキスト(出力に音声を含む場合)
              elif etype == "response.output_audio_transcript.done":
                  print()  # このターンの文字起こし終了
              elif etype == "response.output_text.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # テキストのみの出力の場合
              elif etype == "input_audio_buffer.speech_started":
                  pass  # ユーザーの割り込み: 再生を止めて conversation.item.truncate で切り詰め
              elif etype == "response.output_audio.delta":
                  pass  # base64 PCM16 音声チャンク、デコードして再生可能
              elif etype == "response.done":
                  print("\n[ターン完了]", evt.get("response", {}).get("usage"))
                  break
              elif etype == "error":
                  print("\n[エラー]", evt.get("error"))
                  break

  asyncio.run(main())
  ```

  ```python Python (Agents SDK) theme={null}
  # 依存関係: pip install openai-agents
  import asyncio
  from agents.realtime import (
      OpenAIRealtimeWebSocketModel,
      RealtimeAgent,
      RealtimeSession,
  )

  agent = RealtimeAgent(
      name="Assistant",
      instructions="You are a helpful voice assistant. Keep answers concise.",
  )

  async def main():
      session = RealtimeSession(
          OpenAIRealtimeWebSocketModel(),
          agent,
          None,
          model_config={
              "api_key": "sk-***",  # AiHubMix の API キーに置き換えてください
              "url": "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1",
          },
          run_config={
              "model_settings": {
                  "modalities": ["audio"],
                  "output_audio_format": "pcm16",
                  # 必ず無効化: 既定で有効な入力音声転写は本APIが未対応で、無効化しないとセッションが 1008 で閉じられます
                  "input_audio_transcription": None,
              },
          },
      )
      async with session:
          await session.send_message("What is the capital of France?")
          async for event in session:
              if getattr(event, "type", "") == "raw_model_event":
                  data = getattr(event, "data", None)
                  if getattr(data, "type", "") == "transcript_delta":
                      print(getattr(data, "delta", ""), end="", flush=True)
                  elif getattr(data, "type", "") == "turn_ended":
                      print("\n[ターン完了]")
                      return

  asyncio.run(main())
  ```

  ```bash 接続テスト (wscat) theme={null}
  # wscat で接続性と認証をすばやく検証(先に npm i -g wscat が必要)
  wscat -c "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # 接続後、session.update を1フレーム貼り付け、テキストメッセージ + response.create を送れば開始できます
  ```
</CodeGroup>

<Tip>
  任意の音声を本APIが要求する生 PCM 形式に変換するには ffmpeg を使います：

  ```bash theme={null}
  ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio_pcm16_24k.raw
  ```
</Tip>

### 公式サンプルの再利用

OpenAI が公開しているリアルタイム会話のサンプルの多くは SDK のベース URL パラメータにしか依存しないため、アドレスを AiHubMix のエンドポイントに変えるだけで再利用できます：

| 公式サンプル                                       | アドレス変更のみで再利用可能か | 変更が必要な箇所                                                                                                    |
| -------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------- |
| 公式 Python SDK のリアルタイム会話サンプル                  | 可能              | `websocket_base_url` を `wss://aihubmix.com/v1` にし、`connect()` に `model` を渡す                                 |
| 公式 Node / JS SDK のリアルタイム会話サンプル               | 可能              | `baseURL` を `https://aihubmix.com/v1` にする(SDK が自動的に `wss` に変更し `/realtime?model=...` を組み立てます)               |
| 公式 Agents SDK のリアルタイム音声サンプル                  | 可能              | `model_config.url` を `wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1` にし、既定で有効な入力音声転写を無効化する          |
| 公式ブラウザ向けサンプル(リアルタイムコンソール、マルチ Agent 音声サンプルなど) | 不可              | これらはブラウザから直接接続するため、ゲートウェイの `Origin` 検証で拒否されます。またサーバー側で一時キーを発行する仕組みに依存しますが、本APIはサーバーサイド WebSocket のみを公開しています |

<h2 id="run-output">
  実行結果（本番実測）
</h2>

以下は OpenAI SDK サンプルを `aihubmix.com` 本番環境（モデル `gpt-realtime-2.1`）で実行した実際の結果です。セッションは `server_vad` を有効にし、prompt と音声はいずれも英語です。

**テキスト入力**

```text theme={null}
# テキスト "What is the capital of France?" を送信
[第1ターン・テキスト] 完了 2.33s | 音声出力 2.70s PCM16
  返答: The capital of France is Paris.
  usage: input_tokens 29 (text 29) / output_tokens 81 (audio 54 + text 27、reasoning 8 を含む)
```

**音声入力**

```text theme={null}
# 6.3s の英語音声をチャンク送信すると、モデルが自動でターンを検出して返答
[第2ターン・音声] 完了 9.59s | 音声出力 4.35s PCM16
  返答: Everything sounds good on my side, and I'm ready to keep this conversation going.
  usage: input_tokens 117 (audio 67 + text 50) / output_tokens 129 (audio 87 + text 42、reasoning 11 を含む)
VAD イベント: input_audio_buffer.speech_started / input_audio_buffer.speech_stopped
```

**2つの構成の実測差**

| 構成                                       | 実測結果                                                                                                                                 |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `turn_detection: {"type": "server_vad"}` | 音声を送り終えると `input_audio_buffer.speech_started` と `speech_stopped` を受信し、モデルが自動でターンを検出して返答を開始します。手動の `commit` と `response.create` は不要です |
| `output_modalities: ["text"]`            | 返答テキストは `response.output_text.delta` に移り、そのターンに音声出力イベントは発生せず、usage の `audio_tokens` は 0 になります                                        |

<Note>
  実測での確認事項：デフォルト（出力に音声を含む）では返答テキストは `response.output_audio_transcript.delta` からのみ文字単位で届き、`response.output_text.delta` は出現しません。`response.done` はそのターンの `usage` を運びます。ハンドシェイクには約2〜4秒かかり、これはセッション確立時の割り当て予約のコストです。
</Note>

## 課金

* **token 課金**：会話セッションは各ターンの返答完了時に `response.done` イベントでそのターンの token 使用量（`usage`）を返し、これを基に課金されます。使用量は**音声入力 / 音声出力 / テキスト入力 / テキスト出力**などのコンポーネントごとに個別に計量され、各コンポーネントの単価は[モデル詳細ページ](https://aihubmix.com/model/gpt-realtime-2.1)のリアルタイム表示価格に準じます。
* **使った分だけ随時決済**：これは長時間接続であり、料金はセッション中に各ターンの返答ごとにリアルタイムで差し引かれ、セッション終了時にまとめて決済する必要はありません。セッション確立時には約1分相当の使用量の**割り当て予約**を先に行い（入場審査のみで実際の課金ではありません）、セッション終了後に残りの予約を解放します。**そのため、アカウントの利用可能残高が約1分相当の使用量を賄える場合にのみセッションを確立できます。**
* [使用量と請求](https://aihubmix.com)の利用明細で、リアルタイム会話ごとの課金記録を1件ずつ確認できます。

## 制限と制約

1. **セッションの最大時間**：1つの WebSocket 接続は最長 **62 分**で、到達するとサーバーが切断します（クローズコード `1000`、理由 `session_duration_limit`）。より長く必要な場合は分割して再接続してください。
2. **アイドル切断**：クライアントとモデルの双方に **5 分**間まったく活動がない場合、サーバーがセッションを閉じます（クローズコード `1008`、理由 `idle_timeout`）。どちらかの活動でタイマーはリセットされるため、出力を続ける長い返答が途中で切れることはありません。
3. **残高不足**：**セッション確立時**に、利用可能残高が約1分の予約分を賄えない場合、ハンドシェイクは直接拒否され（HTTP 403）、セッションは確立されません。**セッション中**に残高が尽きると、確立済みの接続は即座に閉じられます。
4. **サーバーサイド専用**：ブラウザからの直接接続は非対応です（`Origin` ヘッダーを検証）。サーバーサイドで統合してください。
5. **モデル固定**：モデルは接続 URL で固定され、セッション中に `session.update` で変更すると拒否されセッションが閉じられます。
6. **形式固定**：音声の入力と出力はいずれも `audio/pcm@24000` モノラルのみに対応し、他の形式は拒否されます。
7. **音色固定**：`voice` は最初の返答開始後は変更できません。最初の返答要求の前に設定してください。
8. **未対応**：会話セッション内のインライン転写、会話アイテムによる音声または画像コンテンツの注入、テキスト以外のコンテンツアイテム種別。
9. **1 ターン 1 返答**：同一セッションで同時に進行できる返答は 1 つだけです。前のターンが完了する前に `response.create` を再度送信すると拒否され、セッションが閉じられます（クローズコード `1008`、理由 `response_already_active`）。

## よくあるエラー

| 状況                                  | クローズコード / ステータス                            | 説明                                                                        |
| ----------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------- |
| サービスが無効                             | HTTP 403 `realtime_disabled`               | リアルタイム会話がその環境で開放されていません                                                   |
| `Origin` ヘッダー付き / ブラウザから直接接続        | ハンドシェイク拒否                                  | サーバーサイドからの接続に変更してください                                                     |
| 接続時の残高不足                            | HTTP 403 `insufficient_user_quota`         | 約1分の予約分を賄えない残高です。チャージ後に再試行してください                                          |
| セッション中のモデル変更                        | `1008` `model_override_forbidden`          | モデルは接続 URL でのみ指定できます                                                      |
| 音声形式が PCM 以外                        | `1008` `audio_format_unsupported`          | `audio/pcm@24000` に変換してください                                               |
| 最初の返答後の音色変更                         | `voice` フィールドが無視される                        | 最初の返答要求の前に `voice` を設定してください                                              |
| インライン転写の有効化                         | `1008` `input_transcription_not_supported` | 会話セッションでは本リリース未対応です                                                       |
| 会話アイテムへの音声注入                        | `1008` `item_audio_not_supported`          | 音声は `input_audio_buffer.append` から送信してください                                |
| 会話アイテムへの画像注入                        | `1008` `image_input_not_supported`         | 本リリース未対応です                                                                |
| 会話アイテムでテキスト以外のコンテンツ種別を使用            | `1008` `unsupported_content_part`          | 会話アイテムの内容はテキストのみに対応                                                       |
| `output_modalities` の値が不正           | `error` イベント `invalid_value`               | `["audio"]` と `["text"]` のみ対応。セッションは閉じません                                 |
| セッションが最大時間に到達                       | `1000` `session_duration_limit`            | 62 分の上限に到達。再接続して続行してください                                                  |
| 双方が 5 分間アイドル                        | `1008` `idle_timeout`                      | どちらかの活動でタイマーがリセットされます                                                     |
| 返答の完了前に再リクエスト                       | `1008` `response_already_active`           | `response.done` を待ってから `response.create` を送信してください                        |
| `event_id` の重複                      | `1008` `duplicate_event_id`                | クライアントイベントの `event_id` はセッション内で一意にしてください                                  |
| `response.conversation` に非デフォルト値を設定 | `1008` `conversation_mode_not_supported`   | デフォルトの会話モードのみ対応                                                           |
| クライアントがサーバー専用イベントを送信                | `1008` `client_forged_lifecycle_event`     | `response.*` 名前空間でクライアントが送信できるのは `response.create` と `response.cancel` のみ |
| セッション中の残高枯渇                         | 接続が閉じられる                                   | チャージ後に再接続してください                                                           |

***

更新日：2026-09-21
