選擇應用程式使用的 API。每個 API 都有各自的身分驗證、工作階段建立流程與事件規範。
將瀏覽器連接至 GPT-Live
在瀏覽器語音應用程式中使用 WebRTC。麥克風輸入與生成的語音透過協商後的媒體軌道傳輸。資料通道則負責傳送逐字稿、工作階段更新與委派工作的 JSON 事件。
瀏覽器會建立工作階段描述協定 (SDP) 提議。應用程式伺服器使用專案 API 金鑰,透過 POST /v1/live/sessions 以該提議換取回覆。請將金鑰與工作階段組態保留在受信任的伺服器上。
開始之前
你需要:
- 具有 GPT-Live 存取權的專案 API 金鑰。
- 可執行所選 SDK 範例的伺服器執行環境。Node.js 範例需要 Node.js 22.6 或更新版本。
- 已取得麥克風權限,且透過 HTTPS 或 localhost 執行應用程式的瀏覽器。
此範例使用 Responses 委派功能,搭配 gpt-5.6-terra 與託管網頁搜尋。關於後端指示與應用程式工具,請參閱委派與工具。關於語音與後端用量,請參閱成本最佳化。
瞭解連線順序
- 透過使用者操作要求麥克風存取權,並將麥克風軌道加入對等連線。
- 建立 SDP 提議之前,先建立資料通道並註冊事件監聽器。
- 設定本機描述,等待 ICE 候選項目收集完成,然後將提議傳送至伺服器。
- 讓伺服器以 POST 將包含
session與transport: { type: "webrtc", sdp: ... }的 JSON 傳送至 OpenAI。 - 將傳回的 SDP 回覆設為遠端描述。等到資料通道收到
session.started後,再傳送應用程式指令。
HTTP 請求會啟動工作階段。 請勿透過資料通道傳送 session.start。 範例中的 oai-events 字串是資料通道的標籤。
使用 POST /v1/live/sessions 建立 WebRTC 工作階段時,初始化期間會先收取 15 秒的語音時長費用。工作階段開始執行後,這筆費用會抵扣時長費用,並非在執行中的工作階段額外加計 15 秒。費用計算方式請參閱 WebRTC 初始化費用。
建立應用程式伺服器
將伺服器範例儲存至新目錄,並在其環境中設定 OPENAI_API_KEY。若使用 Node.js,請使用 server.mjs,並透過 npm install openai express 安裝 openai 與 express。若使用 Python,請安裝 openai。此範例會綁定至 127.0.0.1,接受來自 http://localhost:3000 的工作階段請求,並從執行範例的目錄提供 index.html。
在下方選擇伺服器語言;每個版本都會在連接埠 3000 提供 index.html 與相同的 /api/session 端點。請使用支援 Live 的 SDK 版本。一次只能執行一個版本。
import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");
app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
response.type("html").send(await readFile(indexPath, "utf8"));
});
// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
if (request.headers.origin !== origin) {
response.status(403).json({ error: "Unexpected request origin" });
return;
}
if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
response.status(400).json({ error: "An SDP offer is required" });
return;
}
if (!process.env.OPENAI_API_KEY) {
response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
return;
}
try {
const result = await client.live.create({
session: {
model: "gpt-live-1",
instructions:
"Be concise. Delegate requests needing current information to the backend, which can search the web.",
delegation: {
type: "responses",
responses: {
model: "gpt-5.6-terra",
instructions:
"Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
tools: [{ type: "web_search" }],
tool_choice: "auto",
},
},
},
transport: {
type: "webrtc",
sdp: request.body.sdp,
},
});
// Preserve the SDK's typed session ID and SDP answer.
response.status(201).json(result);
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.error("Live session creation failed", error.status);
response
.status(error.status ?? 502)
.json({ error: "Live session creation failed" });
}
});
app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));讓其他使用者存取伺服器之前,請透過應用程式的身分驗證、授權、請求限制及 HTTPS 保護 /api/session。此本機範例中的來源檢查並不會驗證使用者身分。
建立瀏覽器用戶端
在執行伺服器的目錄中建立 index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GPT-Live connection</title>
</head>
<body>
<script type="module">
// Paste the browser code below here.
</script>
</body>
</html>將下列程式碼貼入模組指令碼中。這段程式碼會新增開始與結束控制項、連接麥克風與音訊輸出,並處理工作階段事件。/api/session 是應用程式伺服器上的路由。
const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);
let peer;
let events;
let microphone;
let closeTimeout;
let ready = false;
let finalized = false;
function cleanup() {
clearTimeout(closeTimeout);
microphone?.getTracks().forEach((track) => track.stop());
events?.close();
peer?.close();
audio.srcObject = null;
ready = false;
start.disabled = false;
stop.disabled = true;
}
start.addEventListener("click", async () => {
start.disabled = true;
finalized = false;
status.textContent = "Connecting…";
try {
const connection = new RTCPeerConnection();
peer = connection;
connection.addEventListener("track", (event) => {
audio.srcObject = new MediaStream([event.track]);
audio.play().catch(() => {
status.textContent =
"Select play on the audio controls to hear the assistant.";
});
});
microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
for (const track of microphone.getAudioTracks()) {
connection.addTrack(track, microphone);
}
// Create the event channel before creating the SDP offer.
events = connection.createDataChannel("oai-events");
events.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
if (event.type === "session.started") {
ready = true;
stop.disabled = false;
status.textContent = "Connected: " + event.session.id;
} else if (event.type === "session.closed") {
finalized = true;
console.log("Final session usage", event.usage);
status.textContent = "Conversation ended.";
cleanup();
} else {
// Save transcript and nested Responses events as needed by your app.
console.log(event);
}
});
events.addEventListener("close", (event) => {
if (event.target !== events) return;
if (!finalized) {
status.textContent = "Disconnected without final session usage.";
cleanup();
}
});
const offer = await connection.createOffer();
await connection.setLocalDescription(offer);
if (connection.iceGatheringState !== "complete") {
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
connection.removeEventListener("icegatheringstatechange", onState);
reject(new Error("Timed out while gathering ICE candidates"));
}, 10_000);
function onState() {
if (connection.iceGatheringState !== "complete") return;
clearTimeout(timeout);
connection.removeEventListener("icegatheringstatechange", onState);
resolve(undefined);
}
connection.addEventListener("icegatheringstatechange", onState);
onState();
});
}
const sdp = connection.localDescription?.sdp;
if (!sdp) throw new Error("Missing local SDP offer");
const response = await fetch("/api/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sdp }),
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
console.log("Created session", result.session.id);
await connection.setRemoteDescription({
type: "answer",
sdp: result.transport.sdp,
});
// The HTTP request started this session. Do not send session.start here.
} catch (error) {
status.textContent =
error instanceof Error ? error.message : String(error);
cleanup();
}
});
stop.addEventListener("click", () => {
if (!ready || !events || events.readyState !== "open") return;
stop.disabled = true;
status.textContent = "Finishing the conversation…";
// The session.closed handler is already registered. Keep media and events
// alive while pending work drains; only clean up after the final event.
events.send(JSON.stringify({ type: "session.close" }));
closeTimeout = setTimeout(() => {
status.textContent = "Incomplete finalization: no session.closed event.";
cleanup();
}, 15_000);
});執行所選的伺服器(node server.mjs 或 python server.py),開啟 http://localhost:3000,然後選取 開始對話。狀態變更為 已連線後,提出需要最新資訊才能回答的問題,以試用託管搜尋。如果瀏覽器阻擋自動播放,請使用音訊控制項。
讀取工作階段回應
請求成功時會傳回 HTTP 201,以及包含工作階段 ID 與 SDP 回覆的 JSON:
{
"session": { "id": "live_123" },
"transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}讀取 result.session.id,並將 result.transport.sdp 傳入 setRemoteDescription。請將工作階段 ID 視為不應解析的識別值,完整保留原樣,包括其前綴。
處理媒體與事件
透過媒體軌道傳送麥克風音訊並接收生成的語音。WebRTC 會透過 SDP 協商音訊格式,因此請勿在工作階段組態中設定 audio.format。請勿透過資料通道傳送 session.input_audio.append,也不要預期會在資料通道收到 session.output_audio.delta。
使用資料通道傳輸逐字稿增量、工作階段指令,以及巢狀的 response.event 訊息。逐字稿處理與生命週期事件的相關資訊,請參閱管理工作階段;如果伺服器需要自己的事件連線,請參閱伺服器端控制。
若要結束對話,請傳送 session.close,並持續接收,直到收到 session.closed 後,再關閉對等連線與麥克風軌道。此範例會在傳送指令之前註冊最終事件監聽器。如果連線在此之前失敗或逾時,最終用量便無法確認。最終用量的處理方式請參閱用量與正常關閉。
WebRTC 是一組功能強大的標準介面,可用來建立即時應用程式。OpenAI Realtime API 支援透過 WebRTC 對等連線連接至 Realtime 模型。
若要開發瀏覽器中的語音到語音應用程式,建議先閱讀語音智慧體,瞭解 Agents SDK 提供的高階輔助功能,以及用於管理即時工作階段的 API。WebRTC 介面功能強大且靈活,但比 Agents SDK 更底層。
從用戶端(例如網頁瀏覽器或行動裝置)連接至 Realtime 模型時,建議使用 WebRTC 而非 WebSockets,以獲得更穩定的效能。
如需更多以 WebRTC 建立使用者介面的指引,請參閱 MDN 文件。
概覽
Realtime API 支援兩種從瀏覽器連線的方式:使用短效 API 金鑰(透過 OpenAI REST API 產生),或使用新的統一介面。一般而言,使用統一介面較簡單,但工作階段的初始化流程必須經過應用程式伺服器。
使用統一介面連線
使用統一介面初始化 WebRTC 連線的流程如下(假設用戶端為網頁瀏覽器):
- 瀏覽器使用其 WebRTC 對等連線的 SDP 資料,向開發者控制的伺服器發出請求。
- 伺服器將該 SDP 與工作階段組態整合至多部分表單中,傳送至 OpenAI Realtime API,並使用其標準 API 金鑰進行身分驗證。
透過統一介面建立工作階段
若要透過統一介面建立 Realtime API 工作階段,你需要建立小型伺服器端應用程式(或整合至現有應用程式),以向 /v1/realtime/calls 發出請求。你將在後端伺服器上使用標準 API 金鑰驗證此請求。
以下是使用 Node.js express 建立簡單伺服器的範例,可用來建立 Realtime API 工作階段:
import express from "express";
const app = express();
// Parse raw SDP payloads posted from the browser
app.use(express.text({ type: ["application/sdp", "text/plain"] }));
const sessionConfig = JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
audio: { output: { voice: "marin" } },
});
// An endpoint which creates a Realtime API session.
app.post("/session", async (req, res) => {
const fd = new FormData();
fd.set("sdp", req.body);
fd.set("session", sessionConfig);
try {
const r = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: fd,
});
// Send back the SDP we received from the OpenAI REST API
const sdp = await r.text();
res.send(sdp);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);如果應用程式會為每位終端使用者指派安全識別碼,
請在此伺服器端請求中,將該識別碼設為 OpenAI-Safety-Identifier 標頭的值。
請使用穩定且能保護隱私的值,
例如經雜湊處理的內部使用者 ID。此標頭應由受信任的後端設定,
而非由瀏覽器設定。
連接至伺服器
在瀏覽器中,你可以使用標準 WebRTC API,透過應用程式伺服器連接至 Realtime API。用戶端會直接以 POST 將其 SDP 資料傳送至伺服器。
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.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]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("/session", {
method: "POST",
body: offer.sdp,
headers: {
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);使用短效 Token 連線
使用短效 API 金鑰初始化 WebRTC 連線的流程如下(假設用戶端為網頁瀏覽器):
- 瀏覽器向開發人員控管的伺服器發出請求,以產生短效 API 金鑰。
- 開發人員的伺服器使用標準 API 金鑰,向 OpenAI REST API 請求短效金鑰,並將新金鑰傳回瀏覽器。
- 瀏覽器使用短效金鑰,直接向 OpenAI Realtime API 驗證工作階段,並建立 WebRTC 對等連線。
建立短效 Token
若要建立供用戶端使用的短效 Token,你需要建置一個小型伺服器端應用程式(或整合至現有應用程式),向 OpenAI REST API 發出請求以取得短效金鑰。你將在後端伺服器上使用標準 API 金鑰來驗證此請求。
以下是一個簡單的 Node.js express 伺服器範例,會使用 REST API 產生短效 API 金鑰:
import express from "express";
const app = express();
const sessionConfig = JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1",
audio: {
output: {
voice: "marin",
},
},
},
});
// An endpoint which would work with the client code above - it returns
// the contents of a REST API request to this protected endpoint
app.get("/token", async (req, res) => {
try {
const response = await fetch(
"https://api.openai.com/v1/realtime/client_secrets",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: sessionConfig,
}
);
const data = await response.json();
res.json(data);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);任何能傳送及接收 HTTP 請求的平台,都可以建立這樣的伺服器端點。請務必 只在伺服器上使用標準 OpenAI API 金鑰,切勿在瀏覽器中使用。
使用短效 Token 時,請在伺服器端建立用戶端密鑰的請求中設定 OpenAI-Safety-Identifier。
Realtime API 會將此識別碼綁定至
產生的短效 Token,因此瀏覽器之後使用該 Token 連線時,
無需傳送安全識別碼。
連線至伺服器
在瀏覽器中,你可以使用標準 WebRTC API,搭配短效 Token 連線至 Realtime API。用戶端會先從你的伺服器端點取得 Token,再透過 POST 將 SDP 資料(連同短效 Token)傳送至 Realtime API。
// Get a session token for OpenAI Realtime API
const tokenResponse = await fetch("/token");
const data = await tokenResponse.json();
const EPHEMERAL_KEY = data.value;
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.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]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);傳送及接收事件
Realtime API 工作階段透過兩類事件共同管理:由你這位開發人員發出的用戶端事件,以及由 Realtime API 產生、用來表示工作階段生命週期事件的伺服器端事件。
透過 WebRTC 連線至 Realtime 模型時,你不必像使用 WebSockets 時一樣,處理來自模型的各項音訊事件細節。只要依照上述方式設定,WebRTC 對等連線物件就會為你完成這些工作。
若要傳送及接收其他用戶端與伺服器端事件,你可以使用 WebRTC 對等連線的資料通道。
// This is the data channel set up in the browser code above...
const dc = pc.createDataChannel("oai-events");
// Listen for server events
dc.addEventListener("message", (e) => {
const event = JSON.parse(e.data);
console.log(event);
});
// Send client events
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "hello there!",
},
],
},
};
dc.send(JSON.stringify(event));若要進一步瞭解如何管理 Realtime 對話,請參閱 Realtime 對話指南。
透過這個輕量的範例應用程式,瞭解如何搭配 WebRTC 使用 Realtime API。