> ## 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（一種在用戶端與伺服器端之間保持長連線、可雙向推送資料的協定）建立持久連線，把你的音訊或文字輸入即時送入對話模型，模型再以增量方式推送文字與語音回覆，適合語音助理、即時問答、口語練習等需要來回互動的情境。

它與[即時語音轉錄](/zh-Hant/api/realtime-transcription)同樣走 WebSocket，但用途不同：

| 維度          | 即時語音轉錄                            | 即時對話（本頁）                    |
| ----------- | --------------------------------- | --------------------------- |
| 目標          | 把語音轉成文字                           | 與模型進行多輪對話，模型會產生回覆           |
| 方向          | 單向：音訊進、文字出                        | 雙向：音訊／文字進，文字＋語音出            |
| 連線參數        | `?intent=transcription&model=...` | 僅 `?model=...`（不帶 `intent`） |
| 語音活動偵測（VAD） | 不支援，必須 `null`                     | 支援，可開啟自動斷句                  |
| 典型情境        | 會議字幕、直播聽寫                         | 語音助理、即時口語互動                 |

**可用模型：**

* **gpt-realtime-2.1**：語音對話模型，支援音訊與文字輸入，即時輸出文字與語音回覆。

<Warning>
  **本 API 面向伺服器端整合，瀏覽器無法直接連線。** 基於安全考量，閘道會校驗並拒絕帶 `Origin` 標頭的連線、拒絕 `openai-insecure-api-key` 子協定，金鑰只接受標準 `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 標頭傳入金鑰：

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

### 音訊格式要求

目前音訊輸入與輸出均只支援一種格式，發送前請將音訊轉換為：

* **編碼**：PCM16（16 位元有符號整數，小端序）
* **取樣率**：24000 Hz
* **聲道**：單聲道（mono）

即 `audio/pcm@24000`。輸入或輸出宣告為其它格式（如 G.711/µ-law）會被拒絕並關閉工作階段。

<Note>
  與轉錄不同，對話工作階段**支援**語音活動偵測（turn\_detection / VAD）。開啟後模型會自動判斷一段話何時結束並觸發回覆；關閉（設為 `null`）則由你手動控制何時提交音訊、何時請求回覆。二者按需選擇即可。
</Note>

## 工作階段設定（session.update）

連線建立後，用戶端可發送一幀 `session.update` 設定對話參數（如語音音色、系統指令、是否開啟 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 一片），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">
  工作階段項目寫入完成：使用者輸入與模型回覆各產生一條工作階段項目。
</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">
  一輪回覆開始產生。
</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">
  一輪回覆完成。該事件攜帶本輪的 token 用量（`usage`），是計費的依據。
</ParamField>

<ParamField body="conversation.item.truncated" type="event">
  截斷請求已生效的確認，見[打斷與截斷](#interruption)。
</ParamField>

<ParamField body="error" type="event">
  錯誤事件，包含錯誤碼與說明。請求本身有問題（如 `output_modalities` 取值非法）時只回一幀 `error`，工作階段繼續可用；涉及策略（如改模型、餘額耗盡）時關閉工作階段。
</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`。

## 完整範例

以下給出三種寫法，任選其一：

* **OpenAI 官方 SDK（推薦）**：無需手寫 WebSocket，把 `websocket_base_url`（SDK 的 WebSocket 基底參數）指向閘道即可重用官方函式庫。
* **OpenAI Agents SDK**：官方 agent 框架的即時語音形態，把 `model_config` 裡的 `url` 換成閘道位址即可。
* **原生 websockets**：不裝 SDK，直接按協定收發幀，依賴最少、便於排查。

<Note>
  **為什麼要在連線時傳模型名？** AiHubMix 閘道在 WebSocket 握手那一刻就要用 `model` 選擇模型推理廠商、鑑權、預留額度，而 `session.update` 是握手完成之後才到、來不及。所以用 SDK 時要給 `connect()` 顯式傳 `model`（SDK 會把它拼進 URL query）；缺了它閘道在**握手期就會拒絕**，連線根本建不起來。與轉錄不同，對話端點**不需要** `intent=transcription`。
</Note>

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

  # 重用官方 SDK 只需兩處適配:
  #   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 = 取樣率 × 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 設定，再發文字訊息 + 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 的基底參數，把位址換成 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
```

**兩種設定的實測差異**

| 設定                                       | 實測結果                                                                                                         |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `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. **單一工作階段時長**：一條 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. **單輪回覆**：同一工作階段同一時刻只允許一輪進行中的回覆，上一輪未完成時再次傳送 `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
