For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主要導覽

即時對話

瞭解如何管理即時語音到語音對話。

透過 WebRTCWebSocket 連線至 Realtime API 後,你就能呼叫即時模型(例如 gpt-realtime-2.1),進行語音到語音對話。你需要 傳送用戶端事件 來啟動動作,並 監聽伺服器事件 ,以回應 Realtime API 執行的動作。

本指南將逐步說明使用音訊與文字生成、圖像輸入及函式呼叫等模型能力所需的事件流程,以及如何理解即時工作階段的狀態。

如果不需要與模型對話,也就是不 預期收到任何回應,可以在轉錄 模式下使用 Realtime API。

即時語音到語音工作階段

即時工作階段是模型與已連線用戶端之間會保留狀態的互動。工作階段的主要組成部分包括:

  • 工作階段 物件,用來控制互動的參數,例如使用的模型、生成輸出時使用的語音,以及其他組態。
  • 對話,代表目前工作階段中產生的使用者輸入項目與模型輸出項目。
  • 回應,即模型生成並加入對話的音訊或文字項目。

輸入音訊緩衝區與 WebSockets

如果使用 WebRTC,向模型傳送音訊及接收模型音訊時所需的大部分媒體處理作業,都有 WebRTC API 協助完成。


如果使用 WebSockets 處理音訊,就需要透過含有 base64 編碼音訊的 JSON 事件,將音訊傳送至伺服器,藉此手動操作 輸入音訊緩衝區

這些組成部分共同構成即時工作階段。你將使用用戶端事件來更新工作階段的狀態,並監聽伺服器事件,以回應工作階段內的狀態變更。

即時工作階段狀態示意圖

工作階段生命週期事件

透過 WebRTCWebSockets 啟動工作階段後,伺服器會傳送 session.created 事件,表示工作階段已就緒。在用戶端,你可以使用 session.update 事件更新目前的工作階段組態。大多數工作階段屬性都能隨時更新,但模型用於音訊輸出的 voice 除外:模型在工作階段中首次以音訊回應後,就無法再變更此屬性。即時工作階段最長可持續 60 分鐘

以下範例示範如何使用 session.update 用戶端事件更新工作階段。如需進一步瞭解如何透過這些通道傳送用戶端事件,請參閱 WebRTCWebSocket 指南。

更新模型在此工作階段中使用的系統指示
const event = {
  type: "session.update",
  session: {
    type: "realtime",
    model: "gpt-realtime-2.1",
    // Lock the output to audio (set to ["text"] if you want text without audio)
    output_modalities: ["audio"],
    audio: {
      input: {
        format: {
          type: "audio/pcm",
          rate: 24000,
        },
        turn_detection: {
          type: "semantic_vad",
        },
      },
      output: {
        format: {
          type: "audio/pcm",
        },
        voice: "marin",
      },
    },
    // Use a server-stored prompt by ID. Optionally pin a version and pass variables.
    prompt: {
      id: "pmpt_123", // your stored prompt ID
      version: "89", // optional: pin a specific version
      variables: {
        city: "Paris", // example variable used by your prompt
      },
    },
    // You can still set direct session fields; these override prompt fields if they overlap:
    instructions:
      "Speak clearly and briefly. Confirm understanding before taking actions.",
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

工作階段更新後,伺服器會發出 session.updated 事件,其中包含工作階段的新狀態。

相關用戶端事件 相關伺服器事件

session.update

session.created

session.updated

文字輸入與輸出

若要使用即時模型生成文字,可以將文字輸入加入目前的對話,要求模型生成回應,並監聽伺服器傳送的事件,以瞭解模型回應的進度。若要生成文字,工作階段必須設定為使用 text 模態(預設已啟用)。

使用 conversation.item.create 用戶端事件建立新的文字對話項目。這類似於在 REST API 中透過 Chat Completions 傳送使用者訊息(提示詞)

建立含有使用者輸入的對話項目
const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_text",
        text: "What Prince album sold the most copies?",
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

將使用者訊息加入對話後,傳送 response.create 事件,讓模型開始生成回應。如果目前的工作階段同時啟用音訊與文字,模型就會同時以音訊和文字內容回應。如果只想生成文字,可以在傳送 response.create 用戶端事件時指定,如下所示。

生成純文字回應
const event = {
  type: "response.create",
  response: {
    output_modalities: ["text"],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

回應完全結束後,伺服器會發出 response.done 事件。此事件包含模型生成的完整文字,如下所示。

監聽 response.done 以查看最終結果
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (serverEvent.type === "response.done") {
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
// ws.on("message", handleEvent);

模型生成回應的過程中,伺服器會發出多個生命週期事件。你可以監聽這些事件,例如 response.output_text.delta,在回應生成時向使用者提供即時回饋。伺服器發出的完整事件清單列於下方的 相關伺服器事件。這些事件大致依發出順序排列,並一併列出與文字生成相關的用戶端事件。

相關用戶端事件 相關伺服器事件

conversation.item.create

response.create

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

音訊輸入與輸出

Realtime API 最強大的功能之一,是能直接與模型進行語音互動,無需在中間加入文字轉語音或語音轉文字的步驟。這能降低語音介面的延遲,也讓模型取得更多語音輸入中的語氣與語調資訊。

語音選項

即時工作階段可設定為使用多種內建語音之一來產生音訊輸出。你可以在建立工作階段時(或在 response.create 事件中)設定 voice,以控制模型的聲音。目前的語音選項有 alloyashballadcoralechosageshimmerversemarincedar。模型一旦在工作階段中輸出過音訊,就無法再修改該工作階段的 voice。為獲得最佳品質,我們建議使用 marincedar

使用 WebRTC 處理音訊

如果使用 WebRTC 連線至 Realtime API,Realtime API 就會與你的用戶端建立對等連線。模型的音訊輸出會以遠端媒體串流的形式傳送至用戶端。提供給模型的音訊輸入則透過音訊裝置(getUserMedia)擷取,再將媒體串流以軌道的形式加入對等連線。

WebRTC 連線指南中的範例程式碼示範了如何使用瀏覽器 API 設定本機與遠端音訊的基本做法:

// Create a peer connection
const pc = new RTCPeerConnection();

// Set up to play remote audio from the model
const audioEl = document.createElement("audio");
audioEl.autoplay = true;
pc.ontrack = (e) => (audioEl.srcObject = e.streams[0]);

// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
  audio: true,
});
pc.addTrack(ms.getTracks()[0]);

上述程式碼片段可讓你與 Realtime API 互動,而你還能在此基礎上實作更多功能。如需各類使用者介面的更多範例,請查看 WebRTC 範例程式碼庫。你也可以在這裡找到這些範例的線上示範。

在瀏覽器中使用媒體擷取與串流,就能將麥克風靜音或取消靜音、選擇用來擷取輸入的裝置,以及執行其他操作。

WebRTC 音訊的用戶端與伺服器事件

依預設,WebRTC 用戶端在傳送音訊輸入之前,不需要向 Realtime API 傳送任何用戶端事件。只要將本機音訊軌道加入對等連線,使用者就能直接開始說話!

不過,音訊透過對等連線在用戶端與伺服器之間往返傳送時,WebRTC 用戶端仍會收到伺服器傳送的多個生命週期事件。例如:

使用 WebRTC API 操作媒體串流,可能就足以滿足所有控制需求。不過,有時仍需要使用較低階的介面來處理音訊輸入與輸出。請參閱下方的 WebSockets 章節,取得更多資訊,以及精細控制音訊輸入所需的事件清單。

使用 WebSockets 處理音訊

透過 WebSocket 傳送與接收音訊時,要從用戶端傳送媒體並接收伺服器傳來的媒體,需要多做一些處理。下表說明 WebSocket 工作階段中,透過 WebSocket 收發音訊所需的事件流程。

以下事件依生命週期順序排列,但某些事件(例如 delta 事件)可能會同時發生。

生命週期階段 用戶端事件 伺服器事件
工作階段初始化

session.update

session.created

session.updated

使用者音訊輸入

conversation.item.create


  (傳送完整音訊訊息)

input_audio_buffer.append


  (分塊串流傳送音訊)

input_audio_buffer.commit


  (停用 VAD 時使用)

response.create


  (停用 VAD 時使用)

input_audio_buffer.speech_started

input_audio_buffer.speech_stopped

input_audio_buffer.committed

伺服器音訊輸出

input_audio_buffer.clear


  (停用 VAD 時使用)

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_audio.delta

response.output_audio.done

response.output_audio_transcript.delta

response.output_audio_transcript.done

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

將音訊輸入串流傳送至伺服器

若要將音訊輸入串流傳送至伺服器,可以使用 input_audio_buffer.append 用戶端事件。此事件要求你透過通訊端,將 以 Base64 編碼的音訊位元組 分塊傳送至 Realtime API。每個區塊的大小不得超過 15 MB。

你可以為整個工作階段設定輸入區塊的格式,也可以針對每個回應個別設定。

將音訊輸入位元組附加至對話
import fs from "fs";
import decodeAudio from "audio-decode";

// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
  const buffer = new ArrayBuffer(float32Array.length * 2);
  const view = new DataView(buffer);
  let offset = 0;
  for (let i = 0; i < float32Array.length; i++, offset += 2) {
    let s = Math.max(-1, Math.min(1, float32Array[i]));
    view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
  return buffer;
}

// Converts a Float32Array to base64-encoded PCM16 data
function base64EncodeAudio(float32Array) {
  const arrayBuffer = floatTo16BitPCM(float32Array);
  let binary = "";
  let bytes = new Uint8Array(arrayBuffer);
  const chunkSize = 0x8000; // 32KB chunk size
  for (let i = 0; i < bytes.length; i += chunkSize) {
    let chunk = bytes.subarray(i, i + chunkSize);
    binary += String.fromCharCode(...chunk);
  }
  return btoa(binary);
}

// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
  "fixtures/sample1.wav",
  "fixtures/sample2.wav",
  "fixtures/sample3.wav",
];

for (const filename of files) {
  const audioFile = fs.readFileSync(filename);
  const audioBuffer = await decodeAudio(audioFile);
  const channelData = audioBuffer.channelData[0];
  const base64Chunk = base64EncodeAudio(channelData);
  ws.send(
    JSON.stringify({
      type: "input_audio_buffer.append",
      audio: base64Chunk,
    })
  );
}

ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
ws.send(JSON.stringify({ type: "response.create" }));

傳送完整音訊訊息

你也可以使用完整錄音建立對話訊息。使用 conversation.item.create 用戶端事件,即可建立包含 input_audio 內容的訊息。

建立包含完整音訊輸入的對話項目
const fullAudio = "<a base64-encoded string of audio bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_audio",
        audio: fullAudio,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

處理來自 WebSocket 的音訊輸出

若要在網頁瀏覽器等用戶端裝置上播放輸出的音訊,我們建議使用 WebRTC,而非 WebSockets。在網路狀況不穩定時,WebRTC 能更可靠地將媒體傳送至用戶端裝置。

不過,若要在伺服器對伺服器的應用程式中透過 WebSocket 處理音訊輸出,就必須監聽 response.output_audio.delta 事件。這些事件包含模型傳回、以 Base64 編碼的音訊資料區塊。你需要先緩衝這些區塊,再將其寫入檔案,或立即以串流方式轉送至其他目標,例如 Twilio 電話通話

請注意,response.output_audio.doneresponse.done 事件實際上不包含音訊資料,只有音訊內容的轉錄文字。若要取得實際的位元組,必須監聽 response.output_audio.delta 事件。

你可以為整個工作階段設定輸出區塊的格式,也可以針對每個回應個別設定。

監聽 response.output_audio.delta 事件
function handleEvent(message) {
  const serverEvent = JSON.parse(message.toString());
  if (serverEvent.type === "response.output_audio.delta") {
    // Access Base64-encoded audio chunks
    // console.log(serverEvent.delta);
  }
}

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

圖像輸入

gpt-realtime-2gpt-realtime 也支援圖像輸入。你可以將圖像附加為使用者訊息中的一個內容部分,模型就能在回應時將圖像內容納入考量。

將圖像加入對話
const base64Image = "<a base64-encoded string of image bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_image",
        image_url: `data:image/{format};base64,${base64Image}`,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

語音活動偵測

即時工作階段預設啟用 語音活動偵測(VAD) ,這表示 API 會判斷使用者何時開始或停止說話,並自動回應。

如需進一步了解如何設定 VAD,請參閱我們的語音活動偵測指南。

停用 VAD

你可以透過 session.update 用戶端事件,將 turn_detection 設為 null 來停用 VAD。這適用於需要精細控制音訊輸入的介面,例如按住說話介面。

停用 VAD 後,用戶端必須手動發出一些額外的用戶端事件,才能觸發音訊回應:

保留 VAD,但停用自動回應

如果你想保持 VAD 模式啟用,同時保留手動決定何時產生回應的能力,可以透過 session.update 用戶端事件,將 turn_detection.interrupt_responseturn_detection.create_response 設為 false。這會保留 VAD 的所有行為,但不會自動建立新的回應。用戶端可以透過 response.create 事件手動觸發回應。

這適用於內容審核、輸入驗證或 RAG 模式等情境,前提是你願意接受稍長的互動延遲,以換取對輸入的控制。

在預設對話之外建立回應

依預設,工作階段期間產生的所有回應都會加入該工作階段的對話狀態(即「預設對話」)。不過,你可能希望模型在不使用工作階段預設對話上下文的情況下產生回應,或同時產生多個回應。你也可能希望更精細地控制模型產生回應時會參考哪些對話項目,例如只參考最近 N 個回合。

使用 response.create 用戶端事件建立回應時,將 response.conversation 欄位設為字串 none,即可產生不會加入預設對話狀態的「帶外」回應。

建立帶外回應時,你可能也需要一種方式來辨識伺服器傳送的哪些事件屬於此回應。你可以為模型回應提供 metadata,協助辨識哪個回應是針對這個用戶端事件所產生的。

建立帶外模型回應
const prompt = `
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
`;

const event = {
  type: "response.create",
  response: {
    // Setting to "none" indicates the response is out of band
    // and will not be added to the default conversation
    conversation: "none",

    // Set metadata to help identify responses sent back from the model
    metadata: { topic: "classification" },

    // Set any other available response fields
    output_modalities: ["text"],
    instructions: prompt,
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

現在,當你監聽 response.done 伺服器事件時,就能辨識帶外回應的結果。

建立帶外模型回應
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (
    serverEvent.type === "response.done" &&
    serverEvent.response.metadata?.topic === "classification"
  ) {
    // this server event pertained to our OOB model response
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
// ws.on("message", handleEvent);

為回應建立自訂上下文

你也可以在預設或目前的對話之外,建構自訂上下文,供模型產生回應時使用。只要使用 response.create 用戶端事件中的 input 陣列即可。你可以使用新的輸入,也可以透過 ID 參照對話中現有的輸入項目。

監聽使用自訂上下文的帶外模型回應
const event = {
  type: "response.create",
  response: {
    conversation: "none",
    metadata: { topic: "pizza" },
    output_modalities: ["text"],

    // Create a custom input array for this request with whatever context
    // is appropriate
    input: [
      // potentially include existing conversation items:
      {
        type: "item_reference",
        id: "some_conversation_item_id",
      },
      {
        type: "message",
        role: "user",
        content: [
          {
            type: "input_text",
            text: "Is it okay to put pineapple on pizza?",
          },
        ],
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

建立不含上下文的回應

你也可以將回應插入預設對話,並忽略所有其他指示和上下文。只要將 input 設為空陣列即可。

將不含上下文的模型回應插入預設對話
const prompt = `
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
`;

const event = {
  type: "response.create",
  response: {
    // An empty input array removes existing context
    input: [],
    instructions: prompt,
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

函式呼叫

Realtime 模型也支援 函式呼叫,讓你執行自訂程式碼來擴充模型的能力。其運作流程概述如下:

  1. 更新工作階段建立回應時,你可以指定模型可呼叫的函式清單。
  2. 如果模型在處理輸入時判斷應呼叫函式,就會在對話中新增項目,用來表示函式呼叫的引數。
  3. 當用戶端偵測到包含函式呼叫引數的對話項目時,就會使用這些引數執行自訂程式碼
  4. 自訂程式碼執行完畢後,用戶端會建立包含函式呼叫輸出的新對話項目,並要求模型回應。

接下來,我們新增一個可供模型呼叫的函式,為使用者提供今日星座運勢,看看實際運作方式。我們會示範需要傳送的用戶端事件物件結構,以及伺服器隨後會發出的事件。

設定可呼叫的函式

首先,我們必須提供一組函式,讓模型根據使用者輸入選擇要呼叫哪個函式。你可以在工作階段層級或個別回應層級設定可用的函式。

以下是 session.update 用戶端事件的承載資料範例,用來設定一個產生星座運勢的函式。此函式只接受一個引數,也就是要產生運勢的星座:

session.update

{
  "type": "session.update",
  "session": {
    "tools": [
      {
        "type": "function",
        "name": "generate_horoscope",
        "description": "Give today's horoscope for an astrological sign.",
        "parameters": {
          "type": "object",
          "properties": {
            "sign": {
              "type": "string",
              "description": "The sign for the horoscope.",
              "enum": [
                "Aries",
                "Taurus",
                "Gemini",
                "Cancer",
                "Leo",
                "Virgo",
                "Libra",
                "Scorpio",
                "Sagittarius",
                "Capricorn",
                "Aquarius",
                "Pisces"
              ]
            }
          },
          "required": ["sign"]
        }
      }
    ],
    "tool_choice": "auto"
  }
}

函式及其參數的 description 欄位可協助模型判斷是否要呼叫該函式,以及每個參數應包含哪些資料。如果模型收到的輸入表示使用者想知道自己的星座運勢,就會使用 sign 參數呼叫此函式。

偵測模型何時想要呼叫函式

模型可能會根據收到的輸入,決定呼叫函式以產生最佳回應。假設我們的應用程式透過 conversation.item.create 事件新增下列對話項目,接著建立回應:

{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "What is my horoscope? I am an aquarius."
      }
    ]
  }
}

接著傳送 response.create 用戶端事件來產生回應:

{
  "type": "response.create"
}

模型不會立即傳回文字或音訊回應,而是會產生一個包含引數的回應,這些引數應傳遞給開發者應用程式中的函式。你可以透過 response.function_call_arguments.delta 伺服器事件監聽函式呼叫引數的即時更新,而 response.done 也會包含呼叫函式所需的完整資料。

response.done

{
    "type": "response.done",
    "event_id": "event_AeqLA8iR6FK20L4XZs2P6",
    "response": {
        "object": "realtime.response",
        "id": "resp_AeqL8XwMUOri9OhcQJIu9",
        "status": "completed",
        "status_details": null,
        "output": [
            {
                "object": "realtime.item",
                "id": "item_AeqL8gmRWDn9bIsUM2T35",
                "type": "function_call",
                "status": "completed",
                "name": "generate_horoscope",
                "call_id": "call_sHlR7iaFwQ2YQOqm",
                "arguments": "{\"sign\":\"Aquarius\"}"
            }
        ],
        ...
    }
}

我們可以從伺服器發出的 JSON 偵測到模型想要呼叫自訂函式:

屬性在函式呼叫中的用途
response.output[0].type設為 function_call 時,表示此回應包含呼叫指定名稱函式所需的引數。
response.output[0].name要呼叫的已設定函式名稱,在此範例中為 generate_horoscope
response.output[0].arguments包含函式引數的 JSON 字串。在此範例中為 "{\"sign\":\"Aquarius\"}"
response.output[0].call_id系統為此函式呼叫產生的 ID。 將函式呼叫結果傳回模型時,需要使用這個 ID

有了這些資訊,我們就能在應用程式中執行程式碼以產生星座運勢,再將資訊傳回模型,讓模型產生回應。

將函式呼叫結果提供給模型

收到模型包含函式呼叫引數的回應後,應用程式就可以執行程式碼來完成該函式呼叫。這段程式碼可以執行任何你需要的操作,例如與外部 API 通訊或存取資料庫。

準備好將自訂程式碼的結果提供給模型後,你可以透過 conversation.item.create 用戶端事件,建立包含該結果的新對話項目。

{
  "type": "conversation.item.create",
  "item": {
    "type": "function_call_output",
    "call_id": "call_sHlR7iaFwQ2YQOqm",
    "output": "{\"horoscope\": \"You will soon meet a new friend.\"}"
  }
}
  • 對話項目的類型為 function_call_output
  • item.call_id 與上述 response.done 事件傳回的 ID 相同
  • item.output 是包含函式呼叫結果的 JSON 字串

新增包含函式呼叫結果的對話項目後,我們再次從用戶端發出 response.create 事件。這會觸發模型使用函式呼叫的資料產生回應。

{
  "type": "response.create"
}

錯誤處理

在工作階段期間,只要伺服器遇到錯誤狀況,就會發出 error 事件。有時,這些錯誤可追溯至應用程式發出的某個用戶端事件。

在 HTTP 請求與回應中,回應本身就對應至用戶端的某個請求;但在這裡,我們需要透過用戶端事件的 event_id 屬性,才能知道是哪個事件在伺服器上觸發了錯誤。下列程式碼示範了這個方法,其中用戶端嘗試發出不受支援的事件類型。

const event = {
  event_id: "my_awesome_event",
  type: "scooby.dooby.doo",
};

dataChannel.send(JSON.stringify(event));

用戶端傳送的這個事件處理失敗後,會引發如下的錯誤事件:

{
  "type": "invalid_request_error",
  "code": "invalid_value",
  "message": "Invalid value: 'scooby.dooby.doo' ...",
  "param": "type",
  "event_id": "my_awesome_event"
}

中斷與截斷

在許多語音應用程式中,使用者可以在模型說話時打斷它。啟用 VAD 後,Realtime API 會處理這類中斷:偵測使用者語音、取消進行中的回應,並開始新的回應。不過,在這種情況下,你會希望模型知道自己說到哪裡時被打斷,才能自然地繼續對話(例如使用者問:「你剛才最後說的是什麼?」)。我們將這個操作稱為 截斷 模型的最後一次回應,也就是從對話中移除該回應尚未播放的部分。

在 WebRTC 和 SIP 連線中,伺服器負責管理輸出音訊緩衝區,因此能知道任一時間點已播放多少音訊。使用者打斷模型時,伺服器會自動截斷尚未播放的音訊。

使用 WebSocket 連線時,音訊播放由用戶端管理,因此用戶端必須停止播放並處理截斷。流程如下:

  1. 用戶端會監聽伺服器發出的新 input_audio_buffer.speech_started 事件,這表示使用者已開始說話。伺服器會自動取消任何進行中的模型回應,並發出 response.cancelled 事件。
  2. 用戶端偵測到此事件時,應立即停止播放目前正在播放的所有模型音訊,並記錄上一則音訊回應在中斷前已播放的長度。
  3. 用戶端應傳送 conversation.item.truncate 事件,從對話中移除模型上一則回應尚未播放的部分。

範例如下:

{
    "type": "conversation.item.truncate",
    "item_id": "item_1234", # this is the item ID of the model's last response
    "content_index": 0,
    "audio_end_ms": 1500 # truncate audio after 1.5 seconds
}

那麼,轉錄文字也能一併截斷嗎?Realtime 模型沒有足夠的資訊能精確對齊轉錄文字與音訊,因此 conversation.item.truncate 會在指定位置截斷音訊,並移除未播放部分的轉錄文字。這能解決移除未播放音訊的問題,但不會提供截斷後的轉錄文字。

按住說話

Realtime API 預設使用語音活動偵測(VAD),也就是由音訊輸入觸發模型回應。你也可以停用 VAD,並在應用程式層級控制何時將音訊輸入傳送給模型,藉此實作按住說話的互動方式。例如,按住空白鍵時擷取音訊,放開時觸發回應。這種方式在某些應用程式中效果出乎意料地好:使用者可以掌控互動過程,也能避免 VAD 偵測失敗,而且不必等待 VAD 逾時,因此操作起來反應迅速。

在 WebSockets 和 WebRTC 上實作按住說話的方式略有不同。Realtime API 的 WebSocket 連線會透過同一個通道,依相同順序傳送所有事件;WebRTC 連線則使用不同通道來傳送音訊和控制事件。

WebSockets

若要透過 WebSocket 連線實作按住說話,用戶端需要負責停止音訊播放、處理中斷,以及啟動新的回應。詳細流程如下:

  1. session.update 事件中設定 "turn_detection": null,以關閉 VAD。
  2. 按下時,在用戶端開始錄音。
    1. 如果模型有正在進行的回應,請傳送 response.cancel 事件將其取消。
    2. 如果模型的輸出仍在播放,請立即停止播放,並傳送 conversation.item.truncate 事件,從對話中移除所有尚未播放的音訊。
  3. 放開時,傳送包含音訊的 input_audio_buffer.append 訊息,將新的音訊放入輸入緩衝區。
  4. 傳送 input_audio_buffer.commit 事件,以提交已寫入輸入緩衝區的音訊,並啟動輸入轉錄(如已啟用)。
  5. 接著透過 response.create 事件觸發回應。

WebRTC 和 SIP

使用 WebRTC 實作按住說話的方式類似,但必須明確清空輸入音訊緩衝區。步驟如下:

  1. session.update 事件中設定 "turn_detection": null,以關閉 VAD。
  2. 按下按鈕時,傳送 input_audio_buffer.clear 事件,以清除先前的所有音訊輸入。
    1. 如果模型有正在進行的回應,請傳送 response.cancel 事件將其取消。
    2. 如果模型的輸出仍在播放,請傳送 output_audio_buffer.clear 事件,清除尚未播放的音訊。這也會截斷對話。
  3. 放開按鈕時,傳送 input_audio_buffer.commit 事件。這會提交已寫入輸入緩衝區的音訊,並啟動輸入音訊轉錄(若已啟用)。
  4. 接著,使用 response.create 事件觸發回應。