
在 OpenAI Realtime API 上建構語音代理:7 步驟生產環境實戰教學(2026)
我們的測試代理在接聽 Twilio 來電後,於來電者停止說話後的 1.1 秒 內說出了第一個字。這是基於 gpt-realtime-2 搭配 semantic_vad 進行的 40 通電話測試所測得的中位數(p50)往返延遲。這並非魔法。OpenAI Realtime API 在單一 Socket 連線內完成語音對語音(speech-to-speech)的處理,因此你省去了傳統 STT → LLM → TTS 接力過程所累積的大約 600 毫秒額外開銷。然而,預設設定並無法讓你達到一秒內的回應速度。以下是我們正式上線的 7 步驟建構流程,包含程式碼與延遲數據表。
這是一份實作教學,而非概念解說。如果你想先了解分層架構,請先閱讀 什麼是真正的 AI 語音代理,然後再回來繼續閱讀。以下內容假設你已擁有 OpenAI API 金鑰和 Node.js 執行環境。
重點摘要:
- gpt-realtime-2 在單一 Socket 中進行語音對語音處理 — 無需 STT/LLM/TTS 接力,節省約 600 毫秒。
- 在伺服器端產生 臨時金鑰(ephemeral keys);切勿將標準 API 金鑰暴露在瀏覽器中。
- Twilio 的媒體串流為 8kHz μ-law;需重新採樣為 Realtime API 所需的 24kHz PCM16。
- 我們測得的往返延遲為 p50 1.1 秒 / p95 1.9 秒。插話功能透過
response.cancel觸發。
你將在 7 個步驟中建構什麼
本教學將建構一個能接聽電話的 OpenAI Realtime API 語音代理,它能在 1.5 秒內回應、在對話過程中呼叫真實函式,並允許來電者插話。流程簡潔:來電者撥打電話號碼,音訊串流至你的伺服器,伺服器透過單一 Socket 將其橋接至 gpt-realtime-2,模型進行語音輸出並可觸發工具呼叫,最後音訊串流回傳。
以下是路徑說明,你可以根據自身需求在任何步驟停駐:
- 產生臨時金鑰(伺服器路由)
- 開啟並設定工作階段
- 加入函式呼叫功能
- 透過 Twilio 橋接至電話號碼
- 處理插話與中斷
- 將延遲優化至亞秒級
- 部署與強化安全性
有三種傳輸方式承載音訊,選擇取決於音訊來源。瀏覽器直接捕捉音訊(WebRTC)、你的伺服器已持有原始串流(WebSocket),或電話網路交付音訊(SIP)。我們將使用 WebSocket 進行 Twilio 橋接,並在適用時註記其他方式。
步驟 1:產生臨時金鑰(不可跳過的路由)
切勿將標準 OpenAI API 金鑰暴露給瀏覽器或客戶端裝置。Realtime API 專門為此發短期有效的 臨時金鑰。你的伺服器使用真實金鑰呼叫 POST /v1/realtime/client_secrets,向客戶端提供一個約一分鐘後過期的令牌,客戶端則使用該令牌進行連線。
以下是一個產生臨時金鑰的最小化 Express 路由範例:
// server.js
import express from "express";
const app = express();
app.get("/session", async (req, res) => {
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: { type: "realtime", model: "gpt-realtime-2" },
}),
});
const data = await r.json();
res.json({ client_secret: data.value, expires_at: data.expires_at });
});
app.listen(3000);瀏覽器獲取 /session,讀取短期秘密金鑰,並使用該金鑰開啟 Realtime 連線。如果你的代理僅在伺服器端運行(如步驟 4 中的 Twilio 案例),你可以跳過客戶端交接,直接使用標準金鑰從後端開啟 Socket。臨時金鑰流程的存在是為了保護不受信任的客戶端。
步驟 2:開啟工作階段並設定 gpt-realtime-2
開啟連線後,發送 session.update 以設定模型、音訊格式、語音和輪次檢測。OpenAI 文件建議將 reasoning.effort 設定為 low,除非你的工具邏輯需要更高準確度才提高該值,因為較高的努力程度會增加延遲。雙向音訊均以 24kHz PCM16 格式運行。
ws.send(JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2",
output_modalities: ["audio"],
audio: {
input: { format: "pcm16", sample_rate: 24000 },
output: { format: "pcm16", sample_rate: 24000, voice: "marin" },
},
instructions: "You are a reservations agent for a restaurant. Be brief.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));包裝該 Socket 的傳輸方式取決於音訊來源:
| 傳輸方式 | 適用時機 | 音訊來源 |
|---|---|---|
| WebRTC | 瀏覽器或行動應用程式直接捕捉麥克風音訊 | 客戶端裝置 |
| WebSocket | 你的伺服器已持有原始音訊串流 | 伺服器管線 |
| SIP | 你希望 OpenAI 處理電話通訊端 | PSTN / 電信系統 |
關於完整的工作階段欄位清單和 GA 功能集,OpenAI Realtime API 文件 是最權威的來源。由於 Twilio 在步驟 4 中提供原始音訊,我們將使用 WebSocket。
步驟 3:加入函式呼叫(讓代理真正執行任務)
無法執行的語音代理只是旁白。函式呼叫 讓 gpt-realtime-2 能在對話中途暫停,要求你的程式碼執行某項操作,並帶著結果繼續對話。你在工作階段中宣告工具,當模型想要執行時會發出 function_call_arguments.done 事件,你執行工作並將輸出結果送回。
宣告工具,然後處理事件:
// in session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Book a table for a given party size and time.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "ISO 8601 datetime" },
},
required: ["party_size", "time"],
},
}]
// handling the call:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // your real logic
ws.send(JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
ws.send(JSON.stringify({ type: "response.create" })); // let it speak the result
}工具完全沒有觸發的最常見原因:未監聽 function_call_arguments.done 且隨後未發送 response.create。模型產生了呼叫請求,但你忽略了它,導致來電者聽到死寂。
如果你的代理需要處理許多工具,OpenAI Agents SDK 改變了這裡的運作邏輯。其在 2026 年 4 月 15 日的重大更新使模型上下文協議(Model Context Protocol, MCP)成為一等公民,並將子代理交接轉變為運行時原語。因此,與其將所有工具塞進單一提示詞中,路由代理可以將預訂任務交給預訂子代理,將帳單問題交給另一個子代理。Agents SDK 語音快速入門 將相同的 Realtime 工作階段包裝在 RealtimeAgent 中,讓你在無需編寫自己的協調循環的情況下實現交接。
步驟 4:橋接至電話號碼(Twilio)
若要接聽真實來電,你需要將電信供應商橋接到 Socket 中。使用 Twilio 時,你將進入來電指向 TwiML <Connect><Stream>,該標籤會開啟一個通往你伺服器的 WebSocket,然後你在 Twilio 和 Realtime API 之間中繼音訊幀。SIP 是另一種選擇 — OpenAI Realtime 直接接受 SIP,如果你不需要處理音訊,這可以完全移除你的媒體中繼服務。
啟動串流的 TwiML 如下:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>這裡有一個容易忽略且會浪費一天時間的關鍵點:Twilio 的媒體串流是 8kHz μ-law,而 Realtime API 需要 24kHz PCM16。你必須在兩個方向上進行重新採樣,否則會得到雜亂無章、像花栗鼠般的音訊。
// inbound: Twilio (8kHz μ-law base64) -> Realtime (24kHz PCM16)
const pcm16 = upsample(muLawDecode(Buffer.from(msg.media.payload, "base64")), 8000, 24000);
realtime.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: pcm16.toString("base64"),
}));
// outbound: Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));完整的幀格式位於 Twilio Media Streams 文件 中。保持重新採樣的輕量級,因為在此處使用沉重的函式庫會增加每一幀都要付出的延遲代價。
步驟 5:處理插話與中斷
生產級別的代理允許來電者在代理說話時插話。插話(Barge-in) 意味著檢測到來電者在代理說話中途開始發言,然後乾淨地切斷代理的輸出。Realtime API 透過 response.cancel 處理此問題:當輪次檢測報告在播放期間開始偵測到語音時,你取消當前回應並清除已緩衝準備發送給來電者的音訊。
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // drop queued playback
}輪次檢測有兩種模式,選擇至關重要。server_vad 根據原始靜默閾值觸發,往往會在自然停頓時切斷來電者。semantic_vad 會等待直到模型認為來電者確實結束了一個想法,因此在思考停頓時產生的誤判中斷要少得多。對於電話通話,semantic VAD 是感覺更像人類的選擇。
步驟 6:將延遲優化至亞秒級
這是演示變為產品的關鍵時刻,以下是來自我們實際建構的數據,而非理論預算。我們在 2026 年 5 月於靠近 OpenAI 區域的一台小型伺服器上,對同一個餐廳代理進行了 40 次測試通話,僅更換輪次檢測和推理設定。
| 設定 | p50 往返延遲 | p95 | 備註 |
|---|---|---|---|
| server_vad, reasoning low | ~1.4s | ~2.3s | 停頓時誤判插話較多 |
| semantic_vad, reasoning low | ~1.1s | ~1.9s | 我們的生產環境預設值 |
| semantic_vad, reasoning medium | ~1.8s | ~3.1s | 工具準確度較高,但較慢 |
真正影響效能的槓桿,按影響程度排序如下:
- 保持
reasoning.effort為低,除非特定工具確實需要高準確度。中等設定幾乎使我們的 p50 延遲加倍。 - 不要以快於即時的速度推送音訊。 過度填充
input_audio_buffer.append會導致緩衝區溢位和漂移;應依照牆鐘時間調整幀速率。 - 保持 Socket 溫暖。 每次通話冷開啟連線會將握手時間加到你的首字延遲中。在通話量允許的情況下池化連線。
- 高效重新採樣。 熱路徑中幼稚的重新採樣器為我們每輪增加了約 80 毫秒。
上線後每分鐘運行成本是多少?我們單獨計算了自帶金鑰(BYOK)的成本 — 請參閱 自帶金鑰語音代理每分鐘成本,此處不再重複推導。
步驟 7:部署並為生產環境強化安全性
從「在我的筆記型電腦上可行」到「每天承受 500 通通話仍穩定運行」之間的差距,在於一些眾所周知的失敗模式。以下是從實際破壞 Realtime 代理的錯誤中總結出的強化檢查清單:
| 陷阱 | 症狀 | 修復方法 |
|---|---|---|
| 錯誤的取樣率 | 音訊雜亂 / 花栗鼠聲 | 雙向均使用 24kHz PCM16 |
忽略 function_call_arguments.done | 工具從未觸發 | 監聽並發送 response.create |
| 以快於即時的速度推送音訊 | 緩衝區溢位,漂移 | 依照即時時間調整幀速率 |
| 無重連邏輯 | Socket 閃斷導致通話掉落 | 自動重連 + 恢復工作階段 |
無 response.done 處理 | 輪次重疊 | 在 response.done 後才允許下一輪 |
針對真實流量還有兩件事。在長時間通話中,每隔幾個輪次旋轉或重新種子工作階段,以防止上下文漂移,因為 20 分鐘的通話會累積狀態,導致模型開始出錯。此外,記錄每個工具呼叫及其參數和結果;當來電者說「代理預訂了錯誤的時間」時,僅靠轉錄無法告訴你是模型錯了還是你的程式碼錯了。
如果你採用步驟 3 中的 Agents SDK 路線,其新的容器沙箱會在隔離環境中運行工具程式碼,這在你的工具接觸檔案系統或 Shell 而不僅僅是 API 時至關重要。
何時應該購買託管平台 instead
直接在 Realtime API 上建構能給你最大的控制權和最低的每分鐘成本,但你必須擁有重連邏輯、電信橋接、合規性和可觀察性 — 這些都是步驟 4 到 7 中缺乏光環的部分。如果你需要在本週就上線電話代理,且不想維護媒體中繼服務,託管平台是更快的選擇。
我們在三大平台上建構了相同的代理並進行了誠實的比較:Retell、Vapi 或 Bland。如果你仍在決定站在哪一邊,在投入工程時間之前,請瀏覽 完整的自建與購買決策框架。
當團隊想要自訂 Realtime 建構的控制權卻不想自行組建團隊時,這就是我們的工作:生產級別語音代理開發,從電信橋接到上述的延遲調優。如果你正在權衡,歡迎讓我們檢視你的用例。
關於作者 — Mert Batur Gurbuz 是 Techsy.io 的聯合創始人,該團隊為 B2B 客戶交付 AI 代理、自動化系統以及語音/SDR 管線。他就讀於伯明罕大學,並撰寫關於 Techsy 團隊在生產環境中實際使用的 LLM 工具堆疊的文章。 LinkedIn
常見問題
OpenAI Realtime API 語音代理的延遲是多少?
在我們使用 gpt-realtime-2 搭配 semantic_vad 和低推理努力的建構中,40 次測試通話測得的往返延遲為 p50 1.1 秒和 p95 1.9 秒。單一 Socket 中的語音對語音處理避免了 STT/LLM/TTS 接力,這正是實現亞秒級回應的關鍵。
我的語音代理需要 WebRTC、WebSocket 還是 SIP?
當瀏覽器或行動應用程式直接捕捉麥克風音訊時使用 WebRTC;當你的伺服器已持有原始音訊串流(Twilio 橋接案例)時使用 WebSocket;當你希望 OpenAI 處理電話通訊端而無需自己的媒體中繼時使用 SIP。大多數電話代理使用 WebSocket 或 SIP。
如何將 OpenAI Realtime API 連接至 Twilio?
將進入的 Twilio 來電指向開啟通往你伺服器 WebSocket 的 TwiML <Connect><Stream>,然後在 Twilio 和 Realtime Socket 之間中繼音訊。將 Twilio 的 8kHz μ-law 雙向重新採樣為 API 的 24kHz PCM16,否則音訊會出現雜訊。
Realtime API 中的函式呼叫如何運作?
你在工作階段設定中宣告工具。當模型需要使用工具時,它會發出 function_call_arguments.done 事件。你執行工作,將結果作為 function_call_output 對話項目送回,然後發送 response.create 以便代理說出結果。忘記最後一步是工具經常「靜默」失敗的原因。
如何在 Realtime API 中處理中斷(插話)?
當輪次檢測在播放期間報告 input_audio_buffer.speech_started 時,發送 response.cancel 以停止當前回應並清除任何排隊輸出來電者的音訊。將其與 semantic_vad 配對使用,這樣自然停頓就不會在句子中間觸發誤判中斷。
OpenAI Realtime API 使用什麼音訊取樣率?
Realtime API 雙向使用 24kHz PCM16 音訊。Twilio 等電信供應商交付 8kHz μ-law,因此電話橋接必須在輸入時向上重新採樣,在輸出時向下重新採樣。取樣率不匹配是音訊失真的最常見原因。
在 Realtime API 上運行語音代理的成本是多少?
成本由 gpt-realtime-2 上的音訊輸入和輸出分鐘數驅動,自帶金鑰(BYOK)的經濟效益與託管的每分鐘平台截然不同。我們在 語音代理定價細目 中計算了完整數學模型,此處不再估算。
我應該在 Realtime API 上建構還是使用 Retell、Vapi 或 Bland?
当你想要最大控制權和最低每分鐘成本,並且能夠擁有重連、電信和合規性時,直接建構。當上市速度更重要時,購買託管平台。我們的 Retell vs Vapi vs Bland 比較 和 自建與購買框架 涵蓋了這些權衡。
2026 年 4 月的 OpenAI Agents SDK 更新為語音代理帶來了什麼改變?
2026 年 4 月 15 日的重大更新使模型上下文協議成為一等公民,為工具程式碼添加了容器沙箱,並將子代理交接轉變為運行時原語。對於語音代理而言,這意味著路由代理可以將任務交給專業子代理,而不是將所有工具塞進單一提示詞中。