选择您的应用使用的 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 候选项收集完成,然后将提议发送到您的服务器。
- 让您的服务器向 OpenAI 发送 POST 请求,请求中的 JSON 包含
session和transport: { type: "webrtc", sdp: ... }。 - 将返回的 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,响应中的 JSON 包含会话 ID 和 SDP 应答:
{
"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 对等连接来连接实时模型。
对于基于浏览器的语音到语音应用,我们建议从语音智能体开始,该指南介绍了 Agents SDK 中用于管理实时会话的高层辅助功能和 API。WebRTC 接口功能强大且灵活,但比 Agents SDK 更底层。
从客户端(例如网页浏览器或移动设备)连接到实时模型时,我们建议使用 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。