使用全新的 OpenAI Realtime API 構建低延遲音訊管道,使開發人員能夠在生產環境中推出類似人類的對話式語音代理。傳統上,構建語音介面意味著將三個獨立的模型層連結在一起:自動語音識別(ASR)、基於文本的 LLM 邏輯層,以及文本轉語音(TTS)合成。該多步驟管道引入了顯著的往返網路延遲,使得自然對話變得不可能。通過持久的 WebSocket 連接進行原生音訊處理改變了這一現狀,將網路延遲降低到 300 毫秒以下。本指南闡述了如何建立連接狀態、流式傳輸原始音訊緩衝區以及優化會話配置。

[!IMPORTANT] API 安全警告:切勿在用戶端瀏覽器指令碼中直接公開您的 OpenAI API 金鑰。始終通過安全的邊緣中間件(如 Cloudflare Worker)代理 WebSocket 連接,該中間件會在將資料包轉發到 OpenAI 之前附加授權請求頭。

核心要點:

  • WebSocket 連接:使用邊緣代理直接連接到 OpenAI 的即時 WebSocket 網關。
  • 原生模態(Modalities):在初始會話更新配置有效負載中同時指定 textaudio
  • 音訊格式:將用戶語音流式傳輸為 24kHz 下以 base64 編碼的單聲道 PCM16 資料塊。
  • 語音打斷:監控伺服器發出的 speech-started 信號,以便立即停止用戶端的音訊播放。

原生音訊 LLM 的架構

傳統的語音技術棧將語音識別和語音合成模型視為中心文本模型周圍的外部包裝器。原生音訊模型通過直接處理語音來消除這一開銷。

透過 OpenAI 的即時模型,網路原生處理音訊輸入和輸出。模型接收原始音訊波形,分析音調、語調起伏以及內容,並直接生成自然的語音輸出。因此,該管道消除了 ASR 轉錄錯誤和 TTS 合成瓶頸。

管理這種持久連接依賴於 WebSockets。連接在整個通話期間保持開啟狀態,允許代理在檢測到用戶說話時中斷其輸出,從而使體驗與真實的電話通話完全匹配。

獲取 AI 集成服務

前提條件

在編寫任何程式碼之前,請確保您的環境已準備就緒。此構建假設您熟悉非同步 JavaScript 並已具備以下條件:

  • 具有即時訪問權限的 OpenAI 帳戶以及已充值且處於活動狀態的 API 金鑰。
  • Node.js 20 或更高版本,或者使用 npm create cloudflare@latest 構建的 Cloudflare Workers 專案。
  • WebSocket 用戶端 — 用於 Node.js 網關的 ws 套件,或 Workers 內部可用的原生全域 WebSocket
  • 瀏覽器前端 — 可以通過 Web Audio API(getUserMedia 加上用於重採樣的 AudioWorklet)捕獲麥克風音訊。
  • base64 和 PCM 音訊的實用知識 — 因為您發送或接收的每個訊框都是 base64 編碼的 PCM16 有效負載。

也為帳戶設置預留一點時間:必須在您的組織上啟用即時訪問和計費,網關才會接受會話。


建立 WebSocket 連接

首先,您打開與 OpenAI 即時網關的連接,並在請求頭中指定即時(realtime)模型。

下面的 JavaScript 程式碼演示了如何初始化連接、配置會話模態以及處理輸入和輸出流音訊緩衝區:

 1import WebSocket from "ws";
 2
 3export async function startVoiceAgent(env) {
 4  // Connect to the OpenAI Realtime WebSocket gateway
 5  const url = "wss://api.openai.com/v1/realtime?model=gpt-realtime";
 6  const ws = new WebSocket(url, {
 7    headers: {
 8      "Authorization": `Bearer ${env.OPENAI_API_KEY}`,
 9      "OpenAI-Beta": "realtime=v1"
10    }
11  });
12
13  ws.on("open", () => {
14    console.log("WebSocket connection established with OpenAI Realtime API");
15    
16    // Configure session modalities and voice parameters
17    const sessionConfig = {
18      type: "session.update",
19      session: {
20        modalities: ["text", "audio"],
21        instructions: "You are a helpful customer service assistant for Mecanik.",
22        voice: "alloy",
23        input_audio_format: "pcm16",
24        output_audio_format: "pcm16",
25        temperature: 0.7
26      }
27    };
28    ws.send(JSON.stringify(sessionConfig));
29  });
30
31  ws.on("message", (data) => {
32    const event = JSON.parse(data);
33    
34    // Handle incoming audio content from the server
35    if (event.type === "response.audio.delta") {
36      const audioBuffer = Buffer.from(event.delta, "base64");
37      // Output buffer to client audio player
38      playAudioChunk(audioBuffer);
39    }
40  });
41}

構建此處理常式時,請確保您的 API 金鑰對用戶端瀏覽器保持不可見。具體來說,在您的伺服器上建立邊緣中間件,以便在代理 WebSocket 連接之前處理授權。要瞭解邊緣 API 配置,請閱讀我們關於 使用 Cloudflare Workers 構建無伺服器 API 的指南。


構建安全的邊緣代理

上面的程式碼段從受信任的伺服器連接,但它沒有顯示保證金鑰安全的部分:代理本身。在 Cloudflare Workers 上,您無法將自訂請求頭附加到 new WebSocket() 建構函式中,因此您可以使用 fetchUpgrade 請求頭打開上游連接。Worker 接受瀏覽器的通訊端,使用附加的金鑰呼叫 OpenAI,然後在兩者之間傳輸訊框資料。

 1export default {
 2  async fetch(request, env) {
 3    if (request.headers.get("Upgrade") !== "websocket") {
 4      return new Response("Expected a WebSocket upgrade", { status: 426 });
 5    }
 6
 7    // 1. Accept the browser <-> Worker socket
 8    const [client, server] = Object.values(new WebSocketPair());
 9    server.accept();
10
11    // 2. Open the Worker <-> OpenAI socket with the secret key attached
12    const upstreamResponse = await fetch(
13      "https://api.openai.com/v1/realtime?model=gpt-realtime",
14      {
15        headers: {
16          Upgrade: "websocket",
17          Authorization: `Bearer ${env.OPENAI_API_KEY}`,
18          "OpenAI-Beta": "realtime=v1"
19        }
20      }
21    );
22
23    const upstream = upstreamResponse.webSocket;
24    if (!upstream) {
25      return new Response("Upstream refused the upgrade", { status: 502 });
26    }
27    upstream.accept();
28
29    // 3. Pipe frames in both directions
30    server.addEventListener("message", (e) => upstream.send(e.data));
31    upstream.addEventListener("message", (e) => server.send(e.data));
32
33    const close = () => { try { server.close(); upstream.close(); } catch {} };
34    server.addEventListener("close", close);
35    upstream.addEventListener("close", close);
36
37    return new Response(null, { status: 101, webSocket: client });
38  }
39};

使用 wrangler secret put OPENAI_API_KEY 將金鑰存儲為加密的機密資料(secret),而不是放在 wrangler.toml 中,以防止它進入您的版本控制庫。瀏覽器現在連接到 wss://your-worker.workers.dev 且永遠無法獲取您的 API 金鑰憑據。Cloudflare 在其 Workers WebSockets 參考文件 中詳細記錄了這種雙向模式。


處理用戶音訊流式傳輸

一旦會話更新被接受,您的用戶端必須捕獲麥克風輸入,將其壓縮為 24kHz 單聲道 PCM16 資料,並將其作為 base64 分段進行流式傳輸。由於連接是持久的,處理網路斷開至關重要。本地分塊緩衝區可確保短暫的行動連接中斷不會導致音訊包丟失或代理響應抖動:用戶端將保留緩衝區並在重新連接後立即重放。

1// Example of streaming user microphone data
2function streamMicrophoneChunk(ws, base64AudioChunk) {
3  const audioEvent = {
4    type: "input_audio_buffer.append",
5    audio: base64AudioChunk
6  };
7  ws.send(JSON.stringify(audioEvent));
8}

每當用戶停止說話時,伺服器會自動處理累積的音訊緩衝區並觸發模型響應。有關完整的 API 事件列表,請查看 OpenAI 即時指南

此外,在前端播放器中實施回聲消除。如果麥克風拾取了揚聲器的音訊輸出,代理就會將自己的聲音解釋為用戶的打斷,從而導致會話循環失敗。要瞭解有關前端優化的更多資訊,請查看我們關於 WordPress 與自訂 Web 開發對比 的指南。


管理發言權檢測與打斷

預設情況下,您必須告訴模型發言何時結束。啟用伺服器端語音活動檢測(VAD)可將該工作交給 OpenAI:網關監控輸入緩衝區,確定用戶何時停止說話,並自動觸發響應。在握手期間發送的會話更新中添加一個 turn_detection 塊。

 1const sessionConfig = {
 2  type: "session.update",
 3  session: {
 4    modalities: ["text", "audio"],
 5    voice: "alloy",
 6    input_audio_format: "pcm16",
 7    output_audio_format: "pcm16",
 8    turn_detection: {
 9      type: "server_vad",
10      threshold: 0.5,
11      prefix_padding_ms: 300,
12      silence_duration_ms: 500
13    }
14  }
15};

啟用 VAD 後,一旦伺服器在代理說話時聽到用戶發聲,它就會發出 input_audio_buffer.speech_started 信號。請將該事件視為立即停止播放的指令:清空前端播放器中所有排隊的音訊塊,否則前面的響應會在新的響應到達時繼續播放。

 1let playbackQueue = [];
 2
 3ws.on("message", (raw) => {
 4  const event = JSON.parse(raw);
 5  if (event.type === "input_audio_buffer.speech_started") {
 6    // User is interrupting: drop everything still buffered
 7    playbackQueue = [];
 8    stopSpeaker();
 9  }
10});

提高 silence_duration_ms 會使代理在回覆前等待更長時間,這適合說話慢或猶豫不決的人;降低它會使交流感覺更敏捷,但有在半句中打斷說話者的風險。


語音代理部署步驟

要部署您的語音代理中間件,首先在無伺服器邊緣節點上配置安全的 WebSocket 路由網關以保護您的 OpenAI 憑證。這可以防止爬蟲程式發現您的金鑰。

接下來,仔細起草系統提示詞和規則。在初始負載更新會話事件中設置音調、詞彙和響應指示。因此,這為對話流建立了清晰的範圍。

然後實施強大的說話者打斷邏輯。您應該監聽 input_audio_buffer.speech_started 事件並立即在前端播放器中停止音訊播放。最後,通過在離用戶較近的區域部署 Workers 來減少路由躍點(routing hops),從而優化全域延遲。要探索邊緣託管選項,請閱讀我們的 Cloudflare Workers AI 教程


常見陷阱與故障排除

大多數首次運行失敗都是由少數可預測的錯誤引起的。下表將您看到的症狀對應到其常見原因和修復方法。

異常現象可能的原因解決方法
連接關閉且立即提示 401缺失或格式錯誤的 Authorization 請求頭確認代理附加了 Bearer <key> 且該金鑰具有即時訪問權限
代理只能聽見靜音或雜亂的語音音訊發送的採樣率或位深度錯誤在進行 base64 編碼之前重採樣為 24kHz 單聲道 PCM16
用戶說話後模型從不回應turn_detection 已禁用且沒有發送手動提交指令啟用 server_vad,或者發送 input_audio_buffer.commit 然後發送 response.create
代理在用戶說話時疊音播放speech_started 處理常式未清除排隊音訊監聽到該事件時清空播放緩衝區並停止播放器聲音
響應在半句中切斷max_response_output_tokens 設置得太低提高上限或將其保留為 inf

有兩個更隱蔽的問題值得注意。帶低餘額到達的 rate_limits.updated 事件是併發會話即將被限制的早期警告;記錄該事件並進行限流,而不是強行發起重連。此外,如果代理偶爾會回答自己上一句話,則是麥克風捕獲到了揚聲器輸出,請加強回聲消除或使用耳機測試。


測試與生產環境注意事項

本地測試很麻煩,因為您需要雙向流動的真實音訊。最快的循環方式是使用 wrangler dev 運行 Worker,將一個簡單的瀏覽器頁面指向它,並在關注音質之前在主控台中查看事件流。在開發過程中記錄每個入站事件類型;一旦 session.updatedspeech_startedresponse.audio.deltaresponse.done 的序列看起來正確,您就掌握了可靠的集成通路。

在您發版前,權衡幾個生產實際情況:

  • 成本:即時音訊按輸入和輸出音訊 Token 的分鐘計費,比純文字 Token 貴得多。設置會話時長上限,並對較長的信息型回答考慮使用文本備選。
  • 延遲:在靠近用戶的地方部署代理並使其保持輕量。瀏覽器、邊緣和 OpenAI 之間的每個額外躍點都會增加您努力縮小的往返延遲。
  • 重連:行動網路上的套接字容易斷開。在用戶端上保留一段較短的輪轉緩衝區,並在重連後重放,這樣丟失的一訊框就不會破壞整個對話。
  • 可觀測性:針對會話啟動、中斷次數和錯誤事件輸出結構化日誌,以便您在用戶投訴前發現降級行為。
  • 安全與隱私:語音是個人資料。明確資料保留期限,如果您的代理處理敏感的工作流,請在轉錄文本上添加伺服器端內容審查。

在進行任何廣泛發布之前,與真實通話者進行短暫試運行,這會暴露沒有指令碼測試覆蓋的口音、噪音和中斷等極端情況。


核心要點

  • OpenAI Realtime API 通過持久的 WebSockets 原生處理音訊,將延遲保持在 300 毫秒以下。
  • 始終在初始 WebSocket 握手期間配置模態、格式和提示詞。
  • 即時將用戶音訊流式傳輸為單聲道 PCM16 base64 分段。
  • 通過監視開始講話事件來處理說話者打斷事件。
  • 通過使用邊緣 Worker 路由代理連接細節來維護 API 憑證安全。

常見問題(FAQ)

什麼是 OpenAI Realtime API? OpenAI Realtime API 是一種 WebSocket 介面,允許開發人員將原始音訊流式傳輸輸入和輸出模型,從而繞過獨立的 ASR/TTS 模組。通過原生處理音訊,模型保留了情感音調、口音起伏和說話細微差別,實現 300 毫秒以下的延遲時間。

我該如何處理語音打斷? 監聽 input_audio_buffer.speech_started 伺服器事件。收到後,清空您的前端音訊緩衝區並立即停止播放器音訊。因此,這創建了自然的對話流,使得 AI 代理在用戶開始說話時立即停止發聲。

API 支持哪些音訊格式? API 原生支持 24kHz 單聲道 PCM16(原始 16 位有符號整數)和 G.711(u-law 和 a-law)音訊格式。開發人員必須在用戶端捕獲麥克風資料,轉換為這些特定格式,並在 JSON WebSocket 訊框內以 base64 編碼的字串形式流式傳輸。

我需要單獨的伺服器來協調 WebSocket 流量嗎? 需要。強烈建議運行無伺服器邊緣協調器(例如 Cloudflare Workers 或輕量級 Node.js 網關)。該代理接收來自用戶端瀏覽器的麥克風輸入,附加安全的授權請求頭,並將二進位流重定向到 OpenAI 的網關。

如何防止即時語音代理中的回聲循環? 當揚聲器音訊洩漏回用戶端麥克風,從而觸發虛假的說話者打斷事件時,就會發生回聲循環。開發人員必須在用戶端實施回聲消除算法,或在測試期間使用耳機,以防止反饋循環破壞會話。