通过 WebSocket 使用 Realtime API
不同于OpenAI API 的其他组件,Realtime API 使用 WebSocket。WebSocket 是一种通信协议,可在客户端与服务器之间建立双向通信通道。与传统的 HTTP 请求-响应模型不同,WebSocket 支持持续、实时的交互。这使得 WebSocket 特别适合实时应用,例如语音聊天。
本文将介绍 WebSocket 的工作方式,并包含多个与 Realtime API 交互的示例。
我们将使用 Node.js,因此请确保您的电脑已安装。如果尚未安装,可从其官方网站下载安装。
初始化脚本
建议创建一个包含上文 .env 文件的文件夹。进入该文件夹后,运行以下命令初始化脚本:
npm init -y && touch index.js
命令执行完成后,文件夹中应包含下列文件:

安装依赖
首先安装两个包:
ws:WebSocket 包,与 API 交互所需的核心包。dotenv:用于从.env文件加载 API 密钥的工具包。
运行以下命令进行安装:
npm install ws dotenv
连接 Realtime API
要与 Realtime API 建立连接,我们创建一个新的 WebSocket 对象,传入 API 地址以及必须的请求头:
// Import the web socket library
const WebSocket = require("ws");
// Load the .env file into memory so the code has access to the key
const dotenv = require("dotenv");
dotenv.config();
function main() {
// Connect to the API
const url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01";
const ws = new WebSocket(url, {
headers: {
"Authorization": "Bearer " + process.env.OPENAI_API_KEY,
"OpenAI-Beta": "realtime=v1",
},
});
}
main();
设置连接打开时的动作
上述代码已创建与 API 的 WebSocket 连接,但尚未进行任何操作。
WebSocket 允许我们为特定事件注册回调。我们可以监听 open 事件,在连接建立后执行指定代码。
添加事件监听器的一般语法如下:
ws.on(<event>, <function>);
将 <event> 替换为事件名字符串,将 <function> 替换为事件发生时要执行的函数。
如下示例展示了如何在连接就绪后输出文本:
// Add inside the main() function of index.js after creating ws
async function handleOpen() {
console.log("Connection is opened");
}
ws.on("open", handleOpen);
运行代码的命令为:
node index.js
如果 API 密钥配置正确,终端将看到“Connection is open”消息。由于连接保持打开,脚本会持续运行,需要手动停止。
设置收到消息时的动作
另一个可响应的事件是 message。每当从服务器接收到消息时都会触发。我们来添加一个函数以显示每条收到的消息:
// Add inside the main() function of index.js
async function handleMessage(messageStr) {
const message = JSON.parse(messageStr);
console.log(message);
}
ws.on("message", handleMessage);
现在运行脚本,还应显示会话初始化时 API 发送的session.created 事件。
其他 WebSocket 事件
上文我们学习了如何监听 open 和 message 事件。WebSocket 还支持另外两个事件,我们在示例中不会使用。
close 事件可用于在套接字关闭时添加回调:
async function handleClose() {
console.log(“Socket closed”);
}
ws.on(“close”, handleClose);
error 事件用于在出错时添加回调:
async function handleError(error) {
console.log(“Error”, error);
}
ws.on(“error”, handleError);
与 Realtime API 通信
使用 WebSocket 需要我们以事件驱动方式编程。消息在通道中来回传递,我们无法控制消息何时发送或接收。
发起通信的代码应写在 handleOpen() 中。O 否则可能在通道尚未建立前执行,导致报错。
处理消息的代码亦然,所有逻辑应写在 handleMessage() 函数中。
在接下来的示例中,我们将使用如下代码作为起点。大多数修改都发生在 handleOpen() 和 handleMessage()。
// Import the web socket library
const WebSocket = require("ws");
// Load the .env file into memory so the code has access to the key
const dotenv = require("dotenv");
dotenv.config();
function main() {
// Connect to the API
const url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01";
const ws = new WebSocket(url, {
headers: {
"Authorization": "Bearer " + process.env.OPENAI_API_KEY,
"OpenAI-Beta": "realtime=v1",
},
});
async function handleOpen() {
console.log("Connection is opened");
}
ws.on("open", handleOpen);
async function handleMessage(messageStr) {
const message = JSON.parse(messageStr);
console.log(message);
}
ws.on("message", handleMessage);
}
main();
使用 Realtime API 收发文本
与 Realtime API 的通信通过事件来完成。OpenAI 的实时文档 API列出了所支持的事件。我们使用conversation.item.create 事件开启对话。事件以 JSON 对象表示,其字段含义见文档。
以下是一个conversation.item.create 事件示例,发送提示语“用一句话解释什么是 web socket(网页套接字)”:
const createConversationEvent = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Explain in one sentence what a web socket is"
}
]
}
};
该事件表明我们希望发起文本对话。这在 content 字段中指定,类型为 “input_text”,并提供文本提示。
我们使用 ws.send() 发送消息。WebSocket 包要求参数为字符串,因此需要使用 JSON.stringify() 将 JSON 事件转换为字符串。结合起来,如下发送事件:
ws.send(JSON.stringify(createConversationEvent));
这会发起对话,但并不会自动触发 API 返回。要触发回复,我们发送一个response.create 事件。示例如下:
const createResponseEvent = {
type: "response.create",
response: {
modalities: ["text"],
instructions: "Please assist the user.",
}
}
ws.send(JSON.stringify(createResponseEvent));
该事件通过 modalities 参数请求文本回复。instructions 是最重要的部分,用于描述我们希望模型执行的任务,这里是通用的“请协助用户”。
我们将这两个事件写入 handleOpen(),这样一旦连接建立就立即开启对话。以下是本例中 handleOpen() 的完整实现:
async function handleOpen() {
// Define what happens when the connection is opened
// Create and send an event to initiate a conversation
const createConversationEvent = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Explain in one sentence what a web socket is"
}
]
}
};
// Create and send an event to initiate a response
ws.send(JSON.stringify(createConversationEvent));
const createResponseEvent = {
type: "response.create",
response: {
modalities: ["text"],
instructions: "Please assist the user.",
}
}
ws.send(JSON.stringify(createResponseEvent));
}
对于入站消息,本例中值得注意的有三个事件:response.text.delta、response.text.done 和 response.done:
-
response.text.delta事件会在delta字段中按块返回回复内容。当我们希望提供实时体验时,它尤为重要,因为可以立刻分块流式输出。 -
response.text.done事件标记文本回复结束,并在text字段中包含完整答案。 -
response.done事件标记回复整体结束。
我们可以在 handleMessage() 中用 switch 语句指定脚本如何响应这些事件:
async function handleMessage(messageStr) {
const message = JSON.parse(messageStr);
// Define what happens when a message is received
switch(message.type) {
case "response.text.delta":
// We got a new text chunk, print it
process.stdout.write(message.delta);
break;
case "response.text.done":
// The text is complete, print a new line
process.stdout.write("\n");
break;
case "response.done":
// Response complete, close the socket
ws.close();
break;
}
}
本例中,我们利用 response.text.delta 将收到的文本块实时打印到控制台。回复完成时触发 response.text.done,我们打印一个换行以表明输出结束。最后收到 response.done 时关闭 WebSocket。
要运行该示例,将这些函数粘贴进上述模板代码,并使用以下命令:
node index.js
终端会对提示语 “Explain in one sentence what a web socket is” 生成回复,类似于使用ChatGPT 的体验。
文本示例的完整代码见此处。
使用 Realtime API 收发音频
上一个示例展示了如何处理文本数据。然而,Realtime API 的真正价值在于创建能实时回复的语音助手。
处理音频较文本略为复杂。我们将略过一些与音频工作机制相关、但会分散注意力的细节。
首先安装两个包:
npm install node-record-lpcm16 speaker
node-record-lpcm16用于从麦克风录音,以便发送语音提示。speaker用于播放 AI 的语音回复。
此外还需安装 SoX(Sound eXchange),这是一个命令行音频处理工具,Node 库将用它与麦克风交互并录音。macOS 使用 brew install sox,Linux 使用 sudo apt install sox。
安装完成后,我们引入这些包,并添加一个 startRecording() 函数来录制用户的语音提示。为不偏离主题,我们不展开解释该函数的内部实现。
在加载环境变量后,将以下代码添加至 index.js:
// Add to index.js before the main() function
// Import the web socket library
const WebSocket = require("ws");
// Load the .env file into memory so the code has access to the key
const dotenv = require("dotenv");
dotenv.config();
const Speaker = require("speaker");
const record = require("node-record-lpcm16");
// Function to start recording audio
function startRecording() {
return new Promise((resolve, reject) => {
console.log("Speak to send a message to the assistant. Press Enter when done.");
// Create a buffer to hold the audio data
const audioData = [];
// Start recording in PCM16 format
const recordingStream = record.record({
sampleRate: 16000, // 16kHz sample rate (standard for speech recognition)
threshold: 0, // Start recording immediately
verbose: false,
recordProgram: "sox", // Specify the program
});
// Capture audio data
recordingStream.stream().on("data", (chunk) => {
audioData.push(chunk); // Store the audio chunks
});
// Handle errors in the recording stream
recordingStream.stream().on("error", (err) => {
console.error("Error in recording stream:", err);
reject(err);
});
// Set up standard input to listen for the Enter key press
process.stdin.resume(); // Start listening to stdin
process.stdin.on("data", () => {
console.log("Recording stopped.");
recordingStream.stop(); // Correctly stop the recording stream
process.stdin.pause(); // Stop listening to stdin
// Convert audio data to a single Buffer
const audioBuffer = Buffer.concat(audioData);
// Convert the Buffer to Base64
const base64Audio = audioBuffer.toString("base64");
resolve(base64Audio); // Resolve the promise with Base64 audio
});
});
};
startRecording() 函数会从麦克风录音,并在按下“Enter”后停止。
接下来在 main() 中初始化 Speaker(),用于播放 AI 的回复:
// Add to the main() function after ws is initialized
const speaker = new Speaker({
channels: 1, // Mono or Stereo
bitDepth: 16, // PCM16 (16-bit audio)
sampleRate: 24000, // Common sample rate (44.1kHz)
});
准备就绪后,我们可以实现 handleOpen() 和 handleMessage() 来处理音频。
在 handleOpen() 中,我们只需调用 startRecording() 来录制用户的语音提示。同时稍微调整事件内容:
- 将
createConversationEvent的 content 更新为类型“input_audio”,并用audio: base64AudioData替换text字段。 - 在
createResponseEvent的响应模态中加入“audio”。
以下是更新后的 handleOpen():
async function handleOpen() {
// Define what happens when the connection is opened
const base64AudioData = await startRecording();
const createConversationEvent = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_audio",
audio: base64AudioData,
},
],
},
};
ws.send(JSON.stringify(createConversationEvent));
const createResponseEvent = {
type: "response.create",
response: {
modalities: ["text", "audio"],
instructions: "Please assist the user.",
},
};
ws.send(JSON.stringify(createResponseEvent));
}
在实现 handleMessage() 时,我们为 “response.audio.delta” 事件添加逻辑,更新音频缓冲并播放新的音频增量:
case "response.audio.delta":
// We got a new audio chunk
const base64AudioChunk = message.delta;
const audioBuffer = Buffer.from(base64AudioChunk, "base64");
speaker.write(audioBuffer);
break;
我们从 switch 语句中移除 “response.text.done” 分支,并更新 “response.done” 事件以停止扬声器:
case "response.audio.done":
speaker.end();
ws.close();
break;
最终的 handleMessage() 实现如下:
function handleMessage(messageStr) {
const message = JSON.parse(messageStr);
// Define what happens when a message is received
switch (message.type) {
case "response.audio.delta":
// We got a new audio chunk
const base64AudioChunk = message.delta;
const audioBuffer = Buffer.from(base64AudioChunk, "base64");
speaker.write(audioBuffer);
break;
case "response.audio.done":
speaker.end();
ws.close();
break;
}
}
要运行该示例,请将上述修改应用到模板代码,并使用以下命令:
node index.js
麦克风将开始录音。请说出您的请求,并按下“Enter”发送。随后,AI 的回复会通过扬声器播放(请确保麦克风未静音且扬声器音量适当)。
音频示例的完整代码见此处。
函数调用
OpenAI API 的一项实用功能是支持函数调用。我们可以为助手添加函数,当模型判断某个函数有助于回答时,会发送事件请求调用该函数。
OpenAI 文档提供了如下图,说明函数调用的生命周期:

来源:OpenAI
从图中可以看到,客户端必须提供 LLM 可调用的函数定义。同时,函数实际执行发生在客户端:AI 会发送事件请求调用某个函数及其参数,随后我们负责将结果返回。
让我们为助手添加一个将两个数相加的函数。我们将在上面的音频示例基础上扩展。
要指定可用函数,我们需向 LLM 提供一个工具列表。每个工具是一个 JSON 对象,描述该函数的信息。以下是定义求和工具的方法:
const sumTool = {
type: "function",
name: "calculate_sum",
description: "Use this function when asked to add numbers together, for example when asked 'What's 4 + 6'?.",
parameters: {
type: "object",
properties: {
"a": { "type": "number" },
"b": { "type": "number" }
},
required: ["a", "b"]
}
}
对象结构说明如下:
type指明我们在定义一个函数。name是函数的标识。LLM 会用它告知我们希望调用哪个函数。description用于指示 LLM 何时应使用此函数。parameters用于指定函数参数。本例为两个数字a和b。
下一步在代码中定义该函数。我们用一个以 calculate_sum 为键的字典,便于在响应函数调用事件时调用相应函数:
const functions = {
calculate_sum: (args) => args.a + args.b,
}
API 会以与上述 parameters 相同结构的字典提供函数参数。例如将 3 与 5 相加,字典为 {“a”: 3, “b”: 5}。
sumTool 和 functions 常量可以添加到 index.js 顶部(导入之后、main() 之前)。
接着我们更新 response.create 事件,告知 LLM sumTools 可用。这通过在 response 中添加 tools 和 tool_choice 字段实现:
const createResponseEvent = {
type: "response.create",
response: {
modalities: ["text", "audio"],
instructions: "Please assist the user.",
tools: [sumTool], // New
tool_choice: "auto", // New
},
};
当 LLM 决定调用函数时,会发出 response.function_call_arguments.done 事件。我们需要通过以下步骤响应:
- 获取函数信息并调用该函数。
- 发送函数调用结果。
- 请求生成回复。
我们在 hanldeMessage()(原文如此)函数的 switch 中添加如下分支来处理:
case "response.function_call_arguments.done":
console.log(Using function ${message.name} with arguments ${message.arguments});
// 1. Get the function information and call the function
const function_name = message.name;
const function_arguments = JSON.parse(message.arguments);
const result = functions[function_name](function_arguments);
// 2. Send the result of the function call
const functionOutputEvent = {
type: "conversation.item.create",
item: {
type: "function_call_output",
role: "system",
output: ${result},
}
};
ws.send(JSON.stringify(functionOutputEvent));
// 3. Request a response
ws.send(JSON.stringify({type: "response.create"}));
break;
现在运行脚本并请求两个数相加,模型应会调用该函数并给出结果。
该函数较为简单,但由于函数在客户端执行,它可以是任何逻辑。下一节我们将看到两个更复杂的函数示例。
本示例的完整代码见此处。
使用 OpenAI Realtime API 的演示应用
OpenAI 团队提供了一个用于展示 Realtime API 的 React 演示应用。这里我们将学习如何搭建并探索其工作原理。这是构建更复杂应用的良好起点。
应用搭建
无需掌握 React 知识即可跑起来。但若要修改或扩展,则需要对 React 有所了解。
该应用托管在此仓库。要进行设置,首先使用以下克隆命令(Git):
git clone [email protected]:openai/openai-realtime-console.git
或者也可以通过 GitHub 界面手动下载。
安装应用使用以下 NPM(Node 包管理器)命令:
npm install
安装完成后,在项目根目录创建 .env 文件,并粘贴以下内容:
OPENAI_API_KEY=<openai_api_key>
REACT_APP_LOCAL_RELAY_SERVER_URL=http://localhost:8081
将 <openai_api_key> 替换为您的 OpenAI API 密钥。
应用由两部分组成,准备就绪后即可运行:
- React 前端,即应用的网页界面。
- 中继服务器,用作前端与 OpenAI API 之间的中间层。

在前端与 OpenAI API 之间实现中继服务器的主要目的是安全存储 API 密钥。没有该密钥无法与 API 交互。
然而,如果密钥存储在前端,则任何用户都能访问。因此,需要搭建一个安全存储密钥的服务器,并在 API 与前端之间中转数据。在当前场景下,由于仅在本地运行,安全风险相对较小。
要启动应用,需要同时启动中继服务器与前端。启动中继服务器的命令:
npm run relay
启动 React 前端的命令:
npm start
加载完成后,浏览器会自动打开一个标签页并运行该应用。

使用应用
开始使用前,请确保电脑未静音,并允许应用使用麦克风。
首先点击“connect”按钮。这会向 Realtime API 发送“Hello”消息,我们会收到一个问候。

连接建立后,中央会出现一个新按钮,允许我们与 AI 助手对话。

使用方法:长按按钮并说话,松开按钮后消息即发送。
应用还提供 VAD(语音活动检测)模式,在该模式下无需按键。应用会持续聆听,使我们可以与助手连续对话。启用方法:直接点击“vad”按钮并开始说话。

函数
如前所述,Realtime API 支持让 AI 执行特定函数。该演示包含两个函数:查询指定地点的天气预报,以及添加记忆项来个性化助手。
您可以通过询问“明天纽约的天气怎么样?”以及表达偏好“我最喜欢的颜色是蓝色”来体验这些功能。助手会以语音作答,同时信息也会显示在应用右侧。

有时我请求天气报告时,回复会提示当前无法访问,但右侧信息区始终能显示出来。作为演示应用,其目标并非完全可用的产品,而是展示 API 的能力。
理解客户端
本节需要对 React 有一定了解,我们会覆盖一些实现细节。
我们将查看ConsolePage.tsx 文件。应用的大部分逻辑定义在这里。与我们在 Node.js 命令行示例中直接使用 WebSocket 不同,演示应用构建了一个实时客户端来辅助与 API 交互。文件顶部引入的是:
import { RealtimeClient } from '@openai/realtime-api-beta';
与 API 的集成定义在这个 useEffect() 调用中。该 useEffect() 中的代码在控制台页面初次渲染时执行。与我们的 Node.js 脚本类似,它定义了如何响应 API 事件。主要区别在于使用了 RealtimeClient 封装。
定义工具
RealtimeClient.addTool() 用于定义工具。它接收两个参数:
- 工具定义的 JSON 对象。
- 要执行的函数。
这种方式简化了工具集成,因为客户端已具备处理事件与自动调用函数的能力。记忆工具定义在此处,天气工具定义在此处。
例如,要添加前面定义的求和工具,可以这样做:
client.addTool(
{
name: "calculate_sum",
description: "Use this function when asked to add numbers together, for example when asked 'What's 4 + 6'?.",
parameters: {
type: "object",
properties: {
"a": { "type": "number" },
"b": { "type": "number" }
},
required: ["a", "b"]
}
},
(a: number, b: number): number => a + b
);
请注意,该应用使用 TypeScript,因此需要在函数定义中注明类型。
监听事件
监听事件使用 RealtimeClient.on(),它接收两个参数:
- 事件名称。
- 要执行的回调函数。
这种方式与之前使用的 WebSocket.on() 类似,但实现的事件集不同。其 GitHub 页面列出了支持的事件。
本例中使用了以下事件:
realtime.event事件(见此)用于记录所有事件日志。error事件(见此)用于将错误打印到控制台,便于调试。conversation.interrupted事件(见此)用于在对话被打断时取消请求。- 最后,
conversation.updated事件(见此)用于当 API 返回新音频块时,将其追加到音频流。
结语
在本教程中,我们探索了 OpenAI Realtime API 以及其如何通过 WebSocket 实现实时通信。我们完成了 Node.js 环境的搭建,学习了文本与音频消息的收发,并实现了增强功能的函数调用。
我们还介绍了 OpenAI 的 React 演示应用,展示了如何部署一个基础的语音助手应用。
如欲进一步了解最新的 OpenAI 开发工具,推荐阅读以下教程:

新推出的OpenAI Realtime API使我们能够将快速、低延迟、多模态的体验集成到应用中。借助该 API,我们可以在用户与大语言模型(LLM)之间创建无缝的语音交互。
该 API 将语音体验所需的多种模型整合为一体,无需再拼接多个服务。它不仅旨在降低延迟,还能更好地保留情感细节和对话的自然流畅性。
在本文中,我们将学习如何使用 OpenAI Realtime API 构建语音控制的 AI 助手。我们会使用 Node.js 创建持久的 WebSocket 连接,并展示如何在终端中与 API 通信。此外,我还会带您部署一个使用该 API 能力的 React 应用。
为 OpenAI Realtime API 生成 API 密钥
要使用OpenAI Realtime API,我们首先需要生成一个 API 密钥。前往API 密钥页面(需要账号)。在页面顶部,点击“Create new secret key”。
会弹出一个窗口。使用默认选项并点击“Create secret key”。
密钥创建后,您将有机会复制它。请在关闭窗口前完成复制,因为这是唯一一次显示它的时机。
如果密钥丢失,我们可以随时删除并重新创建一个新的。
为了存储密钥,我们建议创建一个名为
.env的文件,并按以下格式保存密钥:本文接下来都会通过该文件来连接 API。
Realtime API 的成本考量
在继续之前请注意,Realtime API 并非免费使用,我们需要在账号中充值才可调用。可在个人资料中的计费页面进行充值。
为便于了解成本,以下是我在撰写本文时试验 API 的花费概览:
我总共花了大约 5 美元。这并不算多,但相比 OpenAI 提供的其他 API 要昂贵不少。请在阅读本文时留意这一点。
大部分费用(第一根柱状)来自我在本文结尾体验的 React 控制台应用。其余约半美元,是通过 WebSocket 使用 API 的花费。因此,跟着本文实践的成本完全可以控制在 1 美元以内。
有关 API 定价的更多详情,请查看其定价页面中“Realtime API”部分。