OpenAI Realtime 对话
通过 WebRTC 或 WebSocket 连接 Realtime API 后,可以调用 gpt-realtime-2.1 等模型进行语音到语音对话。需要发送客户端事件发起操作,并监听服务端事件,响应 API 执行的动作。
本指南介绍音频和文本生成、图像输入、函数调用所需的事件流程,以及如何理解 Realtime 会话状态。
如果不需要与模型对话,也就是不期待模型回复,可使用转录模式。
Realtime 语音到语音会话
Realtime Session 是模型与已连接客户端之间的有状态交互,主要由以下部分组成:
- Session 对象:控制模型、输出声音等交互参数。
- Conversation:表示当前会话中的用户输入项与模型输出项。
- Responses:模型生成并加入 Conversation 的音频或文本项。
输入音频缓冲区与 WebSockets:
WebRTC API 会协助处理大部分模型音频收发。WebSocket 则需要手动操作输入音频缓冲区,通过 JSON 事件发送 Base64 编码音频。
以上组成 Realtime 会话。客户端事件用来更新状态,服务端事件用于响应状态变化。
会话生命周期事件
通过 WebRTC 或 WebSockets 发起会话后,服务器发送 session.created,表示会话就绪。客户端可用 session.update 更新配置。多数属性可随时更改,但模型在会话内输出过音频后,声音 voice 不可再改。Realtime 会话最长 60 分钟。
下例通过 session.update 更新系统指令。通道事件收发方式见 WebRTC 或 WebSocket 指南。
来源示例 1(javascript):
程序文字译释50:清晰、简短地说话;行动前确认理解。
// [中文] 示例 1:会话生命周期事件;以下保留本语言原始示例。
// [中文] 原代码明确设置的模型值:gpt-realtime-2.1;各值的作用按其所在配置对象区分。
// [中文] 本段构造的事件类型:session.update;不要将构造载荷等同于收到成功确认。
const event = {
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1",
// Lock the output to audio (set to ["text"] if you want text without audio)
output_modalities: ["audio"],
audio: {
input: {
format: {
type: "audio/pcm",
rate: 24000,
},
turn_detection: {
type: "semantic_vad",
},
},
output: {
format: {
type: "audio/pcm",
rate: 24000,
},
voice: "marin",
},
},
// Use a server-stored prompt by ID. Optionally pin a version and pass variables.
prompt: {
id: "pmpt_123", // your stored prompt ID
version: "89", // optional: pin a specific version
variables: {
city: "Paris", // example variable used by your prompt
},
},
// You can still set direct session fields; these override prompt fields if they overlap:
instructions:
"Speak clearly and briefly. Confirm understanding before taking actions.",
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));
来源示例 2(python):
沿用程序文字译释50。
## [中文] 按原代码构造或处理这些事件:session.update、realtime、audio/pcm、semantic_vad、audio/pcmu;保留请求与服务端确认的区别。
event = {
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1",
# Choose ["audio"] or ["text"]; do not combine them.
"output_modalities": ["audio"],
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 24000,
},
"turn_detection": {"type": "semantic_vad"},
},
"output": {
"format": {
"type": "audio/pcmu",
},
"voice": "marin",
},
},
# Use a server-stored prompt by ID. Optionally pin a version and pass variables.
"prompt": {
"id": "pmpt_123", # Your stored prompt ID.
"version": "89", # Optional: pin a specific version.
"variables": {
"city": "Paris", # Example variable used by your prompt.
},
},
# Direct session fields override prompt fields if they overlap.
"instructions": "Speak clearly and briefly. Confirm understanding before taking actions.",
},
}
ws.send(json.dumps(event))
来源示例 3(java):
沿用程序文字译释50。
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
// Replace pmpt_123 and 89 with an existing saved prompt and version.
// The saved prompt must define the city variable.
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfSessionUpdate(realtime.RealtimeSessionCreateRequestParam{
Model: "gpt-realtime-2.1",
Prompt: responses.ResponsePromptParam{
ID: "pmpt_123",
Version: openai.String("89"),
Variables: map[string]responses.ResponsePromptVariableUnionParam{
"city": {
OfString: openai.String("Paris"),
},
},
},
OutputModalities: []string{"audio"},
Instructions: openai.String("Speak clearly and briefly. Confirm before taking action."),
Audio: realtime.RealtimeAudioConfigParam{
Input: realtime.RealtimeAudioConfigInputParam{
Format: realtime.RealtimeAudioFormatsUnionParam{
OfAudioPCM: &realtime.RealtimeAudioFormatsAudioPCMParam{
Rate: 24000,
},
},
TurnDetection: realtime.RealtimeAudioInputTurnDetectionUnionParam{
OfSemanticVad: &realtime.RealtimeAudioInputTurnDetectionSemanticVadParam{},
},
},
Output: realtime.RealtimeAudioConfigOutputParam{
Format: realtime.RealtimeAudioFormatsUnionParam{
OfAudioPCM: &realtime.RealtimeAudioFormatsAudioPCMParam{
Rate: 24000,
},
},
Voice: realtime.RealtimeAudioConfigOutputVoiceUnionParam{
OfString: openai.String("marin"),
},
},
},
})); err != nil {
return err
}
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonValue;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
// Replace pmpt_123 with your stored prompt ID.
var pcm =
RealtimeAudioFormats.AudioPcm.builder()
.type(RealtimeAudioFormats.AudioPcm.Type.AUDIO_PCM)
.rate(RealtimeAudioFormats.AudioPcm.Rate._24000)
.build();
connection.send(
RealtimeClientEvent.ofSessionUpdate(
SessionUpdateEvent.builder()
.session(
RealtimeSessionCreateRequest.builder()
.model("gpt-realtime-2.1")
.addOutputModality(RealtimeSessionCreateRequest.OutputModality.AUDIO)
.audio(
RealtimeAudioConfig.builder()
.input(
RealtimeAudioConfigInput.builder()
.format(pcm)
.turnDetection(
RealtimeAudioInputTurnDetection.SemanticVad.builder()
.build())
.build())
.output(
RealtimeAudioConfigOutput.builder()
.format(pcm)
.voice("marin")
.build())
.build())
.prompt(
com.openai.models.responses.ResponsePrompt.builder()
.id("pmpt_123")
.version("89")
.variables(
com.openai.models.responses.ResponsePrompt.Variables.builder()
.putAdditionalProperty("city", JsonValue.from("Paris"))
.build())
.build())
.instructions(
"Speak clearly and briefly. Confirm understanding before taking actions.")
.build())
.build()));
来源示例 4(ruby):
程序文字译释51:清晰、简短地说话;行动前确认。
## [中文] 示例 4:会话生命周期事件;以下保留本语言原始示例。
## [中文] 原代码明确设置的模型值:gpt-realtime-2.1;各值的作用按其所在配置对象区分。
## [中文] 沿原示例执行这些方法:connection.session.update;异步调用及事件接收顺序保留。
## Replace the illustrative IDs and URLs below with your own resource values.
connection.session.update(
type: :realtime,
model: "gpt-realtime-2.1",
output_modalities: [:audio],
audio: {
input: {
format: {
type: :"audio/pcm",
rate: 24_000
},
turn_detection: { type: :semantic_vad }
},
output: {
format: {
type: :"audio/pcm",
rate: 24_000
},
voice: :marin
}
},
prompt: {
id: "pmpt_123",
version: "89",
variables: { city: "Paris" }
},
instructions: "Speak clearly and briefly. Confirm before taking action."
)
更新后,服务器发出带新状态的 session.updated。
| 相关客户端事件 | 相关服务端事件 |
|---|---|
session.update |
session.created、session.updated |
文本输入与输出
生成纯文本时,向当前对话加入文本输入,请求模型响应,再监听生成进度事件。正式版使用 output_modalities: ["text"] 选择纯文本输出;默认值是 ["audio"],会返回音频及其文本转录。
译注:原指南将文本模态描述为默认启用;这里按当前正式版接口参考说明默认值。音频附带转录,不等于同时请求
text和audio两种输出模态。
用 conversation.item.create 创建文本对话项,类似在 REST API 中向 Chat Completions 发送用户提示。
创建包含用户输入的对话项:
来源示例 5(javascript):
程序文字译释52:Prince哪张专辑销量最高?
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "What Prince album sold the most copies?",
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));
来源示例 6(python):
沿用程序文字译释52。
## [中文] 示例 6:文本输入与输出;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:conversation.item.create;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
event = {
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "What Prince album sold the most copies?",
}
],
},
}
ws.send(json.dumps(event))
来源示例 7(java):
沿用程序文字译释52。
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfConversationItemCreate(realtime.RealtimeConversationItemUserMessageParam{
Content: []realtime.RealtimeConversationItemUserMessageContentParam{
{
Text: openai.String("What Prince album sold the most copies?"),
Type: "input_text",
},
},
})); err != nil {
return err
}
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonMissing;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
connection.send(
RealtimeClientEvent.ofConversationItemCreate(
ConversationItemCreateEvent.builder()
.item(
RealtimeConversationItemUserMessage.builder()
.object_(JsonMissing.of())
.addContent(
RealtimeConversationItemUserMessage.Content.builder()
.type(RealtimeConversationItemUserMessage.Content.Type.INPUT_TEXT)
.text("What Prince album sold the most copies?")
.build())
.build())
.build()));
来源示例 8(csharp):
沿用程序文字译释52。
// [中文] 示例 8:文本输入与输出;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:session.SendCommandAsync;异步调用及事件接收顺序保留。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
RealtimeMessageItem item = RealtimeItem.CreateUserMessageItem("What Prince album sold the most copies?");
item.Id = Guid.NewGuid().ToString("N");
await session.SendCommandAsync(new RealtimeClientCommandConversationItemCreate(item), timeout.Token);
来源示例 9(ruby):
程序文字译释53:今天天气怎么样?
## [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
connection.conversation.items.create(
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "What is the weather like today?"
}
]
)
receipt = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
next unless event.is_a?(OpenAI::Realtime::ConversationItemDone)
event.item.is_a?(OpenAI::Realtime::RealtimeConversationItemUserMessage)
end
raise "Connection closed before the conversation item was acknowledged" unless receipt
加入用户消息后,发送 response.create 发起生成。正式版的 output_modalities 选择 ["audio"] 或 ["text"],不能同时请求两者。音频输出自带转录;只生成文字时,可在本次响应中明确指定 ["text"]:
来源示例 10(javascript):
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
const event = {
type: "response.create",
response: {
output_modalities: ["text"],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));
来源示例 11(python):
## [中文] 示例 11:文本输入与输出;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:response.create;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
event = {"type": "response.create", "response": {"output_modalities": ["text"]}}
ws.send(json.dumps(event))
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
OutputModalities: []string{"text"},
},
},
}); err != nil {
return err
}
来源示例 12(java):
// [中文] 示例 12:文本输入与输出;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
connection.send(
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.build())
.build()));
来源示例 13(csharp):
// [中文] 示例 13:文本输入与输出;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:session.SendCommandAsync;异步调用及事件接收顺序保留。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = new()
{
OutputModalities =
{
RealtimeOutputModality.Text
}
}
}, timeout.Token);
来源示例 14(ruby):
程序文字译释54:用简洁文本回复。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
connection.response.create(
output_modalities: [:text],
instructions: "Respond with a concise text message."
)
response_done = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
next unless event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
raise "Response failed: #{event.response.status}" unless event.response.status == :completed
true
end
raise "Connection closed before the response completed" unless response_done
响应完全结束后发出 response.done,包含完整生成文字。监听最终结果:
来源示例 15(javascript):
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
function handleEvent(message) {
const data = "data" in message ? message.data : message.toString();
const serverEvent = JSON.parse(data);
if (serverEvent.type === "response.done") {
console.log(serverEvent.response.output[0]);
}
}
// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);
来源示例 16(python):
## [中文] 示例 16:文本输入与输出;以下保留本语言原始示例。
## [中文] 解析收到的 JSON,仅在 response.done 时打印 response.output 的第一项;本例假定 output 非空。
## [中文] 这里只读取终结载荷,没有检查完成状态或遍历所有输出项;实际处理器需按应用要求处理失败、取消及空输出。
def on_message(ws, message):
server_event = json.loads(message)
if server_event["type"] == "response.done":
print(server_event["response"]["output"][0])
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
func receiveUntil(ctx context.Context, conn *realtime.RealtimeConnection, terminal string) (realtime.RealtimeServerEventUnion, error) {
for {
event, err := conn.Recv(ctx)
if err != nil {
return event, fmt.Errorf("waiting for %s: %w", terminal, err)
}
if event.Type == "error" {
return event, fmt.Errorf("Realtime API error: %s", event.OfRealtimeErrorEvent.Error.Message)
}
if event.Type == "response.done" && event.OfResponseDoneEvent.Response.Status != "completed" {
return event, fmt.Errorf("response ended with status %s", event.OfResponseDoneEvent.Response.Status)
}
if event.Type == "response.done" {
response := event.OfResponseDoneEvent.Response
fmt.Println("Response status:", response.Status)
for _, output := range response.Output {
item := output.OfRealtimeConversationItemAssistantMessage
if item.Type == "message" {
for _, content := range item.Content {
if content.Type == "output_text" {
fmt.Print(content.Text)
}
}
}
}
}
if event.Type == terminal {
return event, nil
}
}
}
来源示例 17(java):
程序文字译释55:响应以所示状态结束。
// [中文] 示例 17:文本输入与输出;以下保留本语言原始示例。
// [中文] 仅处理 response.done 对应的 SDK 事件;先检查状态为 COMPLETED,否则抛出含状态和详情的异常。
// [中文] 状态通过后打印全部 output;这只是事件处理函数,需要由现有 Realtime 连接分发事件来调用。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
static void handleEvent(RealtimeServerEvent event) {
event
.responseDone()
.ifPresent(
done -> {
if (!done.response()
.status()
.filter(RealtimeResponse.Status.COMPLETED::equals)
.isPresent())
throw new IllegalStateException(
"Response ended with status "
+ done.response().status()
+ ": "
+ done.response().statusDetails());
System.out.println(done.response().output());
});
}
来源示例 18(csharp):
程序文字译释56:响应以所示状态结束,后接完整序列化响应供检查。
// [中文] 示例 18:文本输入与输出;以下保留本语言原始示例。
// [中文] 遇到响应终结更新时先检查 Completed 状态,失败或取消会抛出异常。
// [中文] 完成后逐项序列化并打印输出;本段没有建立连接,也没有播放音频。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
internal static void HandleEvent(RealtimeServerUpdate update)
{
if (update is RealtimeServerUpdateResponseDone completion && completion.Response.Status != RealtimeResponseStatus.Completed)
throw new InvalidOperationException($"Response ended with status {completion.Response.Status}: {System.ClientModel.Primitives.ModelReaderWriter.Write(completion.Response)}");
if (update is RealtimeServerUpdateResponseDone done)
{
foreach (RealtimeItem item in done.Response.OutputItems)
Console.WriteLine(System.ClientModel.Primitives.ModelReaderWriter.Write(item));
}
}
来源示例 19(ruby):
程序文字译释57:输出响应状态。
## [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
response_done = connection.find do |event|
next unless event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
puts("Response status: #{event.response.status}")
Array(event.response.output).each do |item|
next unless item.is_a?(OpenAI::Realtime::RealtimeConversationItemAssistantMessage)
item.content.each do |content|
puts(content.text) if content.type == :output_text
end
end
true
end
raise "Connection closed before the response completed" unless response_done
生成期间还会发出多个生命周期事件。例如监听 response.output_text.delta 可向用户实时反馈。下表按大致产生顺序列出相关事件:
| 相关客户端事件 | 相关服务端事件 |
|---|---|
conversation.item.create、response.create |
conversation.item.added、conversation.item.done、response.created、response.output_item.added、response.content_part.added、response.output_text.delta、response.output_text.done、response.content_part.done、response.output_item.done、response.done、rate_limits.updated |
音频输入与输出
Realtime API 可直接与模型语音交互,无需中间语音转文字或文字转语音步骤。这可降低语音界面延迟,也让模型获取声音语气和抑扬变化等更多信息。
声音选项
创建会话时或在 response.create 中设置 voice,选择内置输出声音。当前选项为 alloy、ash、ballad、coral、echo、sage、shimmer、verse、marin、cedar。模型一旦在会话中发出音频,就不能再修改声音。为获得较好质量,推荐 marin 或 cedar。
通过 WebRTC 处理音频
Realtime API 作为客户端的对等连接,将模型输出作为远端媒体流交付。输入用 getUserMedia 从音频设备采集,再把媒体流作为轨道加入连接。
WebRTC 连接指南中的浏览器示例配置本地和远端音频:
来源示例 20(javascript):
// [中文] 示例 20:通过 WebRTC 处理音频;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:pc.addTrack;异步调用及事件接收顺序保留。
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
const audioEl = document.createElement("audio");
audioEl.autoplay = true;
pc.ontrack = (e) => (audioEl.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]);
以上可实现基本交互。更多界面示例见 WebRTC samples 仓库及其在线演示。
浏览器媒体采集与流 API还可静音、取消静音、选择输入设备等。
WebRTC 音频的客户端与服务端事件
默认情况下,WebRTC 客户端在输入音频前无需发送客户端事件。只要把本地音轨加入对等连接,用户就能开始讲话。
音频经对等连接往返时,客户端仍会收到生命周期事件,例如:
- 本地媒体轨道发送输入后,收到
input_audio_buffer.speech_started。 - 本地输入停止后,收到
input_audio_buffer.speech_stopped。 - 收到生成中的音频转录增量。
- 模型完成转录并发送完响应时,收到
response.done。
媒体流 WebRTC API 可能已足够提供所需控制。有时仍需更底层的输入输出接口,细粒度音频处理和事件见下方 WebSocket 部分。
通过 WebSockets 处理音频
WebSocket 音频收发需要额外处理。以下按生命周期顺序列出必要事件;某些增量事件可能并发发生。
| 阶段 | 客户端事件 | 服务端事件 |
|---|---|---|
| 会话初始化 | session.update |
session.created、session.updated |
| 用户音频输入 | conversation.item.create(完整音频消息);input_audio_buffer.append(分块传输);input_audio_buffer.commit、response.create(关闭 VAD 时) |
input_audio_buffer.speech_started、input_audio_buffer.speech_stopped、input_audio_buffer.committed |
| 服务端音频输出 | input_audio_buffer.clear(关闭 VAD 时) |
conversation.item.added、conversation.item.done、response.created、response.output_item.added、response.content_part.added、response.output_audio.delta、response.output_audio.done、response.output_audio_transcript.delta、response.output_audio_transcript.done、response.output_text.delta、response.output_text.done、response.content_part.done、response.output_item.done、response.done、rate_limits.updated |
向服务器流式发送输入音频
用 input_audio_buffer.append 发送 Base64 编码音频字节块,单块不得超过 15 MB。
正式版通过 session.update 设置 session.audio.input.format,例如 {"type":"audio/pcm","rate":24000} 表示 24 kHz PCM。发送的音频字节必须与该输入格式一致;仅更改配置不会重采样音频。不要使用 response.input_audio_format 作为正式版响应参数。
译注:早期指南快照此处曾列出
session.input_audio_format和response.input_audio_format。以上操作说明依据正式版客户端事件参考;原字段差异及历史适用范围继续保留在 CH7-P02,不据此断定它们属于某个特定 Beta 版本。
向对话追加音频字节:
来源示例 21(javascript):
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
import fs from "fs";
import decodeAudio from "audio-decode";
// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
const buffer = new ArrayBuffer(float32Array.length * 2);
const view = new DataView(buffer);
let offset = 0;
for (let i = 0; i < float32Array.length; i++, offset += 2) {
const sample = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(
offset,
Math.round(sample * (sample < 0 ? 0x8000 : 0x7fff)),
true
);
}
return buffer;
}
// Converts a Float32Array to base64-encoded PCM16 data
function base64EncodeAudio(float32Array) {
const arrayBuffer = floatTo16BitPCM(float32Array);
let binary = "";
let bytes = new Uint8Array(arrayBuffer);
const chunkSize = 0x8000; // 32KB chunk size
for (let i = 0; i < bytes.length; i += chunkSize) {
let chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
return btoa(binary);
}
// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
"fixtures/sample1.wav",
"fixtures/sample2.wav",
"fixtures/sample3.wav",
];
// Manual commit and response creation require automatic turn detection off.
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
audio: { input: { turn_detection: null } },
},
})
);
for (const filename of files) {
const audioFile = fs.readFileSync(filename);
const audioBuffer = await decodeAudio(audioFile);
if (
audioBuffer.sampleRate !== 24000 ||
audioBuffer.channelData.length !== 1
) {
throw new Error("Expected 24 kHz mono audio: " + filename);
}
const channelData = audioBuffer.channelData[0];
const base64Chunk = base64EncodeAudio(channelData);
ws.send(
JSON.stringify({
type: "input_audio_buffer.append",
audio: base64Chunk,
})
);
}
ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
ws.send(
JSON.stringify({
type: "response.create",
response: { output_modalities: ["audio"] },
})
);
来源示例 22(python):
## [中文] 按会话配置发送输入音频并处理提交或转录事件;音频字节格式必须匹配配置,不能仅修改参数而不转换采样数据。
import base64
import json
import struct
import soundfile as sf
from websocket import create_connection
## ... create websocket-client named ws ...
def float_to_16bit_pcm(float32_array):
clipped = [max(-1.0, min(1.0, x)) for x in float32_array]
pcm16 = b"".join(struct.pack("<h", min(32767, int(x * 32768))) for x in clipped)
return pcm16
def base64_encode_audio(float32_array):
pcm_bytes = float_to_16bit_pcm(float32_array)
encoded = base64.b64encode(pcm_bytes).decode("ascii")
return encoded
files = ["./path/to/sample1.wav", "./path/to/sample2.wav", "./path/to/sample3.wav"]
for filename in files:
data, samplerate = sf.read(filename, dtype="float32")
if samplerate != 24000 or data.ndim != 1:
raise ValueError(f"Expected 24 kHz mono audio: {filename}")
base64_chunk = base64_encode_audio(data)
# Send the client event
event = {"type": "input_audio_buffer.append", "audio": base64_chunk}
ws.send(json.dumps(event))
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
// audio contains mono PCM16 samples at 24 kHz.
for start := 0; start < len(audio); start += 9600 {
end := min(start+9600, len(audio))
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfInputAudioBufferAppend(base64.StdEncoding.EncodeToString(audio[start:end]))); err != nil {
return err
}
}
来源示例 23(java):
// [中文] 示例 23:向服务器流式发送输入音频;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
// [中文] 需要发送的原始字节先编码为 Base64;编码不会替代重采样或改变编解码格式。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.Base64;
// Call for each PCM16, 24 kHz, mono chunk supplied by your audio source.
static void appendAudio(RealtimeConnection connection, byte[] pcmAudio) throws Exception {
String base64Audio = Base64.getEncoder().encodeToString(pcmAudio);
send(
connection,
RealtimeClientEvent.ofInputAudioBufferAppend(
InputAudioBufferAppendEvent.builder().audio(base64Audio).build()));
}
// Retry only explicit admission rejections; these guarantee nothing was sent.
private static void send(RealtimeConnection connection, RealtimeClientEvent event)
throws Exception {
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
while (true) {
try {
connection.send(event);
return;
} catch (com.openai.core.http.WebSocketWriteNotAttempted.Busy busy) {
if (System.nanoTime() >= deadline) throw busy;
Thread.sleep(10);
}
}
}
来源示例 24(csharp):
// [中文] 示例 24:向服务器流式发送输入音频;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:session.SendCommandAsync;异步调用及事件接收顺序保留。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
// Call for each PCM16, 24 kHz, mono chunk supplied by your audio source.
internal static async Task AppendAudioAsync(RealtimeSessionClient session, BinaryData pcmAudio, CancellationToken cancellationToken)
{
await session.SendCommandAsync(new RealtimeClientCommandInputAudioBufferAppend(pcmAudio), cancellationToken);
}
来源示例 25(ruby):
## [中文] 示例 25:向服务器流式发送输入音频;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:connection.input_audio_buffer.append_bytes;异步调用及事件接收顺序保留。
File.open("speech.pcm", "rb") do |audio|
while (chunk = audio.read(9_600))
connection.input_audio_buffer.append_bytes(chunk)
end
end
发送完整音频消息
也可以用 conversation.item.create 创建 input_audio 内容的完整录音消息:
来源示例 26(javascript):
程序文字译释58:这里替换为Base64编码音频字节串。
// [中文] 示例 26:发送完整音频消息;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:conversation.item.create;不要将构造载荷等同于收到成功确认。
const fullAudio = "<a base64-encoded string of audio bytes>";
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_audio",
audio: fullAudio,
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));
来源示例 27(python):
沿用程序文字译释58。
## [中文] 示例 27:发送完整音频消息;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:conversation.item.create;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
fullAudio = "<a base64-encoded string of audio bytes>"
event = {
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_audio",
"audio": fullAudio,
}
],
},
}
ws.send(json.dumps(event))
来源示例 28(java):
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfConversationItemCreate(realtime.RealtimeConversationItemUserMessageParam{
Content: []realtime.RealtimeConversationItemUserMessageContentParam{
{
Audio: openai.String(base64.StdEncoding.EncodeToString(audio)),
Type: "input_audio",
},
},
})); err != nil {
return err
}
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonMissing;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.Base64;
// Pass the complete PCM16, 24 kHz, mono recording from your application.
static void sendAudioItem(RealtimeConnection connection, byte[] pcmAudio) throws Exception {
String fullAudio = Base64.getEncoder().encodeToString(pcmAudio);
var event =
RealtimeClientEvent.ofConversationItemCreate(
ConversationItemCreateEvent.builder()
.item(
RealtimeConversationItemUserMessage.builder()
.object_(JsonMissing.of())
.addContent(
RealtimeConversationItemUserMessage.Content.builder()
.type(RealtimeConversationItemUserMessage.Content.Type.INPUT_AUDIO)
.audio(fullAudio)
.build())
.build())
.build());
// Retry only explicit admission rejections; these guarantee nothing was sent.
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
while (true) {
try {
connection.send(event);
return;
} catch (com.openai.core.http.WebSocketWriteNotAttempted.Busy busy) {
if (System.nanoTime() >= deadline) throw busy;
Thread.sleep(10);
}
}
}
来源示例 29(csharp):
// [中文] 示例 29:发送完整音频消息;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:session.SendCommandAsync;异步调用及事件接收顺序保留。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
// Pass the complete PCM16, 24 kHz, mono recording from your application.
internal static async Task<string> SendAudioItemAsync(RealtimeSessionClient session, BinaryData pcmAudio, CancellationToken cancellationToken)
{
RealtimeMessageItem item = RealtimeItem.CreateUserMessageItem([new RealtimeInputAudioMessageContentPart(pcmAudio)]);
string itemId = Guid.NewGuid().ToString("N");
item.Id = itemId;
await session.SendCommandAsync(new RealtimeClientCommandConversationItemCreate(item), cancellationToken);
return itemId;
}
来源示例 30(ruby):
## [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
audio = Base64.strict_encode64(File.binread("speech.pcm"))
connection.conversation.items.create(
type: :message,
role: :user,
content: [
{
type: :input_audio,
audio: audio
}
]
)
receipt = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
event.is_a?(OpenAI::Realtime::ConversationItemDone) &&
event.item.is_a?(OpenAI::Realtime::RealtimeConversationItemUserMessage)
end
raise "Connection closed before the audio item was acknowledged" unless receipt
处理 WebSocket 输出音频
向浏览器等客户端设备播放音频,推荐 WebRTC。网络条件不确定时,WebRTC 的媒体传输更稳健。
服务端到服务端使用 WebSocket 时,监听 response.output_audio.delta 获取模型 Base64 音频块。可缓冲后写文件,也可立即转发到 Twilio 电话通话等目标。
response.output_audio.done 和 response.done 不包含实际音频字节,只含音频内容的转录信息。实际字节必须从 response.output_audio.delta 获取。
输出块格式可在会话级 session.audio.output.format(session.update)或响应级 response.audio.output.format(response.create)设置。
监听音频增量:
来源示例 31(javascript):
// [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
function handleEvent(message) {
const serverEvent = JSON.parse(message.toString());
if (serverEvent.type === "response.output_audio.delta") {
const audio = Buffer.from(serverEvent.delta, "base64");
console.log(`Received ${audio.length} audio bytes`);
}
}
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);
来源示例 32(python):
## [中文] 示例 32:处理 WebSocket 输出音频;以下保留本语言原始示例。
## [中文] 只在 response.output_audio.delta 事件中读取 delta;它是 Base64 编码的音频字节块。
## [中文] 本例打印编码字符串,没有解码、拼接或播放;实际播放需解码并按会话输出格式处理。
def on_message(ws, message):
server_event = json.loads(message)
if server_event["type"] == "response.output_audio.delta":
# Access Base64-encoded audio chunks:
# [中文] delta 是编码载荷;下一行仅打印,不执行解码或播放。
print(server_event["delta"])
// [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
func receiveUntil(ctx context.Context, conn *realtime.RealtimeConnection, terminal string) (realtime.RealtimeServerEventUnion, error) {
for {
event, err := conn.Recv(ctx)
if err != nil {
return event, fmt.Errorf("waiting for %s: %w", terminal, err)
}
if event.Type == "response.output_audio.delta" {
audio, err := base64.StdEncoding.DecodeString(event.OfResponseAudioDeltaEvent.Delta)
if err != nil {
return event, err
}
fmt.Printf("Received %d audio bytes\n", len(audio))
}
if event.Type == "error" {
return event, fmt.Errorf("Realtime API error: %s", event.OfRealtimeErrorEvent.Error.Message)
}
if event.Type == "response.done" && event.OfResponseDoneEvent.Response.Status != "completed" {
return event, fmt.Errorf("response ended with status %s", event.OfResponseDoneEvent.Response.Status)
}
if event.Type == terminal {
return event, nil
}
}
}
来源示例 33(java):
// [中文] 示例 33:处理 WebSocket 输出音频;以下保留本语言原始示例。
// [中文] 通过 SDK 的 responseOutputAudioDelta 筛选音频增量事件,只打印 delta 载荷。
// [中文] 打印的是 Base64 字符串;本段未实现音频解码、缓冲或播放,需要接入应用音频处理器。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
static void handleEvent(RealtimeServerEvent event) {
event.responseOutputAudioDelta().ifPresent(delta -> System.out.println(delta.delta()));
}
来源示例 34(csharp):
// [中文] 示例 34:处理 WebSocket 输出音频;以下保留本语言原始示例。
// [中文] 需要发送的原始字节先编码为 Base64;编码不会替代重采样或改变编解码格式。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
internal static void HandleEvent(RealtimeServerUpdate update)
{
if (update is RealtimeServerUpdateResponseOutputAudioDelta delta)
Console.WriteLine(Convert.ToBase64String(delta.Delta.ToArray()));
}
来源示例 35(ruby):
程序文字译释59:已收到指定字节数的音频。
## [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
response_done = connection.find do |event|
case event
when OpenAI::Realtime::ResponseAudioDeltaEvent
audio_bytes = Base64.strict_decode64(event.delta)
puts("Received #{audio_bytes.bytesize} audio bytes")
when OpenAI::Realtime::RealtimeErrorEvent
raise event.error.message
when OpenAI::Realtime::ResponseDoneEvent
raise "Response failed: #{event.response.status}" unless event.response.status == :completed
true
end
end
raise "Connection closed before the response completed" unless response_done
图像输入
gpt-realtime-2 与 gpt-realtime 也支持图像输入。可在用户消息中附加图像内容部分,模型会结合图像回答。
向对话添加图像:
来源示例 36(javascript):
程序文字译释60:这里替换为Base64编码图像字节串。
// [中文] 示例 36:图像输入;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:conversation.item.create;不要将构造载荷等同于收到成功确认。
const base64Image = "<a base64-encoded string of image bytes>";
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_image",
image_url: `data:image/{format};base64,${base64Image}`,
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));
来源示例 37(ruby):
程序文字译释61:描述这幅图像。
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfConversationItemCreate(realtime.RealtimeConversationItemUserMessageParam{
Content: []realtime.RealtimeConversationItemUserMessageContentParam{
{
ImageURL: openai.String("data:image/png;base64," + base64.StdEncoding.EncodeToString(image)),
Type: "input_image",
},
{
Text: openai.String("Describe this image."),
Type: "input_text",
},
},
})); err != nil {
return err
}
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
OutputModalities: []string{"text"},
},
},
}); err != nil {
return err
}
语音活动检测
默认 Realtime 会话启用 VAD,API 判断用户开始或停止讲话并自动回复。配置见语音活动检测指南。
关闭 VAD
用 session.update 将 session.audio.input.turn_detection 设为 null,可关闭 VAD,适合按键说话等需要细粒度输入控制的界面。
关闭后,客户端必须手动发送额外事件:
input_audio_buffer.commit:为对话创建新的用户输入项。response.create:触发模型音频响应。- 新输入开始前发送
input_audio_buffer.clear。
保留 VAD,但关闭自动回复
要保留检测,同时自行决定何时生成响应,可用 session.update 把 session.audio.input.turn_detection.interrupt_response 与 session.audio.input.turn_detection.create_response 设为 false。VAD 行为仍保留,但不自动创建 Responses;客户端用 response.create 手动触发。
适用于审核、输入验证或 RAG 等愿意用少量额外延迟换取输入控制的场景。
在默认对话之外创建响应
会话生成的响应默认加入会话对话状态,即默认对话。有时需要在默认上下文之外生成,或并发生成多个响应,也可能希望精细选择参与生成的对话项,如只用最近 N 轮。
用 response.create 把 response.conversation 设为字符串 none,可生成不加入默认对话状态的带外响应。
通常还需识别哪些服务端事件属于带外响应,可为响应提供 metadata,关联此次客户端请求。
创建带外响应:
来源示例 38(javascript):
程序文字译释62:分析截至目前的对话:涉及客服输出support,涉及销售输出sales。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
encoded_image = Base64.strict_encode64(File.binread("image.png"))
connection.conversation.items.create(
type: :message,
role: :user,
content: [
{
type: :input_image,
image_url: "data:image/png;base64,#{encoded_image}"
},
{
type: :input_text,
text: "Describe this image."
}
]
)
connection.response.create(output_modalities: [:text])
completion = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
end
unless completion.is_a?(OpenAI::Realtime::ResponseDoneEvent) && completion.response.status == :completed
raise "The Realtime response did not complete"
end
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
const prompt = `
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
`;
const event = {
type: "response.create",
response: {
// Setting to "none" indicates the response is out of band
// and will not be added to the default conversation
conversation: "none",
// Set metadata to help identify responses sent back from the model
metadata: { topic: "classification" },
// Set any other available response fields
output_modalities: ["text"],
instructions: prompt,
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));
来源示例 39(python):
沿用程序文字译释62。
## [中文] 示例 39:在默认对话之外创建响应;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:response.create;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
prompt = """
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
"""
event = {
"type": "response.create",
"response": {
# Setting to "none" indicates the response is out of band,
# and will not be added to the default conversation
"conversation": "none",
# Set metadata to help identify responses sent back from the model
"metadata": {"topic": "classification"},
# Set any other available response fields
"output_modalities": ["text"],
"instructions": prompt,
},
}
ws.send(json.dumps(event))
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
Instructions: openai.String("\nAnalyze the conversation so far. If it is related to support, output\n\"support\". If it is related to sales, output \"sales\".\n"),
Metadata: shared.Metadata{
"topic": "classification",
},
Conversation: "none",
OutputModalities: []string{"text"},
},
},
}); err != nil {
return err
}
来源示例 40(java):
程序文字译释63:分析当前对话:客服输出support,销售输出sales。
// [中文] 示例 40:在默认对话之外创建响应;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonValue;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
connection.send(
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.conversation("none")
.metadata(
RealtimeResponseCreateParams.Metadata.builder()
.putAdditionalProperty("topic", JsonValue.from("classification"))
.build())
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.instructions(
"Analyze the conversation so far. If it is related to support, output \"support\". If it is related to sales, output \"sales\".")
.build())
.build()));
来源示例 41(csharp):
沿用程序文字译释63。
// [中文] 示例 41:在默认对话之外创建响应;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:session.SendCommandAsync;异步调用及事件接收顺序保留。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = new()
{
DefaultConversationConfiguration = RealtimeResponseDefaultConversationConfiguration.None,
Metadata = new Dictionary<string, BinaryData>
{
["topic"] = BinaryData.FromObjectAsJson("classification")
},
OutputModalities =
{
RealtimeOutputModality.Text
},
Instructions = "Analyze the conversation so far. If it is related to support, output \"support\". If it is related to sales, output \"sales\"."
}
}, timeout.Token);
来源示例 42(ruby):
程序文字译释64:把对话分类为support(客服)或sales(销售)。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
connection.response.create(
conversation: :none,
metadata: { topic: "classification" },
output_modalities: [:text],
instructions: "Classify the conversation as support or sales."
)
response_done = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
next unless event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
raise "Response failed: #{event.response.status}" unless event.response.status == :completed
true
end
raise "Connection closed before the response completed" unless response_done
监听 response.done 时即可识别该响应的结果:
来源示例 43(javascript):
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
function handleEvent(message) {
const data = "data" in message ? message.data : message.toString();
const serverEvent = JSON.parse(data);
if (
serverEvent.type === "response.done" &&
serverEvent.response.metadata?.topic === "classification"
) {
const { response } = serverEvent;
if (response.status !== "completed" || response.output.length === 0) {
throw new Error(
"Classification response ended with status " + response.status
);
}
console.log(response.output[0]);
}
}
// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);
来源示例 44(python):
程序文字译释65:尚未设置主题。
## [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
def on_message(ws, message):
server_event = json.loads(message)
if server_event["type"] != "response.done":
return
response = server_event["response"]
if (response.get("metadata") or {}).get("topic") != "classification":
return
if response["status"] != "completed" or not response["output"]:
raise RuntimeError(
f"Classification response ended with status {response['status']}"
)
print(response["output"][0])
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
func receiveUntil(ctx context.Context, conn *realtime.RealtimeConnection, terminal string) (realtime.RealtimeServerEventUnion, error) {
for {
event, err := conn.Recv(ctx)
if err != nil {
return event, fmt.Errorf("waiting for %s: %w", terminal, err)
}
if event.Type == "error" {
return event, fmt.Errorf("Realtime API error: %s", event.OfRealtimeErrorEvent.Error.Message)
}
if event.Type == "response.done" && event.OfResponseDoneEvent.Response.Metadata["topic"] != "classification" {
continue
}
if event.Type == "response.done" && event.OfResponseDoneEvent.Response.Status != "completed" {
return event, fmt.Errorf("response ended with status %s", event.OfResponseDoneEvent.Response.Status)
}
if event.Type == "response.done" {
response := event.OfResponseDoneEvent.Response
fmt.Println("Response status:", response.Status)
for _, output := range response.Output {
item := output.OfRealtimeConversationItemAssistantMessage
if item.Type == "message" {
for _, content := range item.Content {
if content.Type == "output_text" {
fmt.Print(content.Text)
}
}
}
}
}
if event.Type == terminal {
return event, nil
}
}
}
来源示例 45(java):
沿用程序文字译释55。
// [中文] 示例 45:在默认对话之外创建响应;以下保留本语言原始示例。
// [中文] 仅接受 response.done,读取 metadata 中的 topic 并筛选 classification,其他事件返回 false。
// [中文] 匹配后确认响应状态为 COMPLETED,随后打印输出并返回 true;非完成状态抛出异常。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonValue;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
static boolean handleEvent(RealtimeServerEvent event) {
if (event.responseDone().isEmpty()) return false;
var response = event.responseDone().orElseThrow().response();
if (!response
.metadata()
.map(m -> m._additionalProperties().get("topic"))
.flatMap(JsonValue::asString)
.filter("classification"::equals)
.isPresent()) return false;
if (!response.status().filter(RealtimeResponse.Status.COMPLETED::equals).isPresent())
throw new IllegalStateException(
"Response ended with status " + response.status() + ": " + response.statusDetails());
System.out.println(response.output());
return true;
}
来源示例 46(csharp):
程序文字译释66:响应以所示状态结束,后接序列化响应。
// [中文] 示例 46:在默认对话之外创建响应;以下保留本语言原始示例。
// [中文] 先同时检查终结事件类型与 metadata.topic,非 classification 响应直接返回 false。
// [中文] 分类响应必须为 Completed;通过后逐项打印序列化输出,再返回 true。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
internal static bool HandleEvent(RealtimeServerUpdate update)
{
if (update is not RealtimeServerUpdateResponseDone done ||
!done.Response.Metadata.TryGetValue("topic", out BinaryData? topic) ||
topic.ToObjectFromJson<string>() != "classification")
return false;
if (done.Response.Status != RealtimeResponseStatus.Completed)
throw new InvalidOperationException($"Response ended with status {done.Response.Status}: {System.ClientModel.Primitives.ModelReaderWriter.Write(done.Response)}");
foreach (RealtimeItem item in done.Response.OutputItems)
Console.WriteLine(System.ClientModel.Primitives.ModelReaderWriter.Write(item));
return true;
}
来源示例 47(ruby):
程序文字译释67:分类响应已完成,显示其状态。
## [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
response_done = connection.find do |event|
next unless event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
next unless event.response.metadata&.fetch(:topic, nil) == "classification"
puts("Classification response completed: #{event.response.status}")
Array(event.response.output).each do |item|
next unless item.is_a?(OpenAI::Realtime::RealtimeConversationItemAssistantMessage)
item.content.each do |content|
puts("Classification: #{content.text}") if content.type == :output_text
end
end
true
end
raise "Connection closed before the response completed" unless response_done
为响应创建自定义上下文
可在默认或当前对话之外,为模型构造自定义上下文。在 response.create 的 input 数组中使用新输入,或按 ID 引用已有对话输入项。
监听使用自定义上下文的带外响应:
来源示例 48(javascript):
程序文字译释68:披萨上放菠萝可以吗?
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
const existingItemId = "item_pizza";
ws.send(
JSON.stringify({
type: "conversation.item.create",
item: {
id: existingItemId,
type: "message",
role: "user",
content: [
{ type: "input_text", text: "We are ordering pizza for dinner." },
],
},
})
);
const event = {
type: "response.create",
response: {
conversation: "none",
metadata: { topic: "pizza" },
output_modalities: ["text"],
// Create a custom input array for this request with whatever context
// is appropriate
input: [
// potentially include existing conversation items:
{
type: "item_reference",
id: existingItemId,
},
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Is it okay to put pineapple on pizza?",
},
],
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));
来源示例 49(python):
沿用程序文字译释68。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
import json
existing_item_id = "item_pizza"
ws.send(
json.dumps(
{
"type": "conversation.item.create",
"item": {
"id": existing_item_id,
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "We are ordering pizza for dinner."}
],
},
}
)
)
event = {
"type": "response.create",
"response": {
"conversation": "none",
"metadata": {"topic": "pizza"},
"output_modalities": ["text"],
# Create a custom input array for this request with whatever
# context is appropriate
"input": [
# potentially include existing conversation items:
{"type": "item_reference", "id": existing_item_id},
# include new content as well
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Is it okay to put pineapple on pizza?",
}
],
},
],
},
}
ws.send(json.dumps(event))
来源示例 50(ruby):
程序文字译释69:给问题分类:我的订单迟到了。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
existing_item_id = "item_order"
connection.conversation.items.create(
id: existing_item_id,
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "Order ORD-123 has not arrived."
}
]
)
connection.response.create(
conversation: :none,
metadata: { topic: "classification" },
output_modalities: [:text],
input: [
{
type: :item_reference,
id: existing_item_id
},
{
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "Classify this issue: my order is late."
}
]
}
]
)
创建无上下文响应
原文说明,也可把 input 设为空数组,在默认对话中插入不使用其他对话上下文的响应。关于原文“忽略其他指令”的适用边界,已单列待核,不据此认定会话级指令失效。
来源示例 51(javascript):
程序文字译释70:严格说出:“我是小茶壶,矮矮又胖胖;这是我的把手,这是我的壶嘴!”
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
const prompt = `
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
`;
const event = {
type: "response.create",
response: {
// An empty input array removes existing context
input: [],
output_modalities: ["text"],
instructions: prompt,
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));
来源示例 52(python):
沿用程序文字译释70。
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
prompt = """
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
"""
event = {
"type": "response.create",
"response": {
"output_modalities": ["text"],
# An empty input array removes all prior context
"input": [],
"instructions": prompt,
},
}
ws.send(json.dumps(event))
来源示例 53(java):
程序文字译释71:严格说出小茶壶示例的原句;中文含义为“我是小茶壶,矮矮又胖胖;这是我的把手,这是我的壶嘴”。
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
Instructions: openai.String("\nSay exactly the following:\nI'm a little teapot, short and stout!\nThis is my handle, this is my spout!\n"),
Input: []realtime.ConversationItemUnionParam{},
OutputModalities: []string{"text"},
},
},
}); err != nil {
return err
}
来源示例 54(csharp):
沿用程序文字译释71。
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
connection.send(
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.input(List.of())
.instructions(
"Say exactly the following: I'm a little teapot, short and stout! This is my handle, this is my spout!")
.build())
.build()));
来源示例 55(ruby):
程序文字译释72:不使用对话上下文,生成简短问候。
// [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
using OpenAI.Realtime;
#pragma warning disable OPENAI002
RealtimeResponseOptions options = new()
{
OutputModalities = { RealtimeOutputModality.Text },
Instructions = "Say exactly the following: I'm a little teapot, short and stout! This is my handle, this is my spout!"
};
options.InputItems.Clear();
// An explicitly empty list excludes conversation context.
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = options
}, timeout.Token);
## [中文] 显式请求创建或继续响应,使用本段实际设置的模态、上下文与工具字段;发送请求不等同于收到完成事件。
connection.response.create(
input: [],
output_modalities: [:text],
instructions: "Generate a concise greeting without conversation context."
)
response_done = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
next unless event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
raise "Response failed: #{event.response.status}" unless event.response.status == :completed
true
end
raise "Connection closed before the response completed" unless response_done
函数调用
Realtime 模型支持函数调用,通过执行自定义代码扩展能力。大致流程:
- 更新会话或创建响应时,指定可调用函数列表。
- 模型处理输入时若判断需要函数,就向对话添加包含调用参数的项目。
- 客户端检测到这类项目后,用参数运行自定义代码。
- 代码完成后,客户端创建含调用结果的新项目,并请求模型回答。
以下以提供当天星座运势的函数为例,展示需要发送的客户端事件与服务器返回结构。
配置可调用函数
先向模型提供可依据用户输入选择的函数。函数可在会话级 session.tools(session.update),或响应级 response.tools(response.create)配置。
以下 session.update 配置运势函数,接受一个星座参数:
来源示例 56(json):
程序文字译释73:提供指定星座今天的运势。
程序文字译释74:要查询运势的星座。
{
"type": "session.update",
"session": {
"tools": [
{
"type": "function",
"name": "generate_horoscope",
"description": "Give today's horoscope for an astrological sign.",
"parameters": {
"type": "object",
"properties": {
"sign": {
"type": "string",
"description": "The sign for the horoscope.",
"enum": [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces"
]
}
},
"required": ["sign"]
}
}
],
"tool_choice": "auto"
}
}
函数和参数的 description 帮助模型决定是否调用,以及每个参数应放什么数据。用户询问运势时,模型会以 sign 调用函数。
检测模型何时需要调用函数
假设应用先用 conversation.item.create 添加以下输入:
来源示例 57(json):
程序文字译释75:我的运势如何?我是水瓶座。
{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "What is my horoscope? I am an aquarius."
}
]
}
}
随后发送 response.create:
来源示例 58(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "response.create"
}
模型不立即返回文字或语音,而生成要传入应用函数的参数。可用 response.function_call_arguments.delta 观察实时参数更新,也可从 response.done 获取调用所需完整数据:
来源示例 59(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "response.done",
"event_id": "event_AeqLA8iR6FK20L4XZs2P6",
"response": {
"object": "realtime.response",
"id": "resp_AeqL8XwMUOri9OhcQJIu9",
"status": "completed",
"status_details": null,
"output": [
{
"object": "realtime.item",
"id": "item_AeqL8gmRWDn9bIsUM2T35",
"type": "function_call",
"status": "completed",
"name": "generate_horoscope",
"call_id": "call_sHlR7iaFwQ2YQOqm",
"arguments": "{\"sign\":\"Aquarius\"}"
}
]
}
}
服务端 JSON 中可据此识别自定义函数调用:
| 属性 | 用途 |
|---|---|
response.output[0].type |
为 function_call 时,表示包含具名函数参数。 |
response.output[0].name |
函数名称,此例为 generate_horoscope。 |
response.output[0].arguments |
JSON 字符串参数,此例为 "{\"sign\":\"Aquarius\"}"。 |
response.output[0].call_id |
系统生成的调用 ID,必须用它向模型返回结果。 |
依据这些信息,应用生成星座运势,再把信息返回模型供其回答。
向模型返回函数结果
收到参数后,应用运行对应代码,例如访问外部 API 或数据库。准备好结果后,用 conversation.item.create 创建包含结果的新项目:
来源示例 60(json):
程序文字译释76:horoscope结果文字:“你很快会认识一位新朋友。”
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_sHlR7iaFwQ2YQOqm",
"output": "{\"horoscope\": \"You will soon meet a new friend.\"}"
}
}
- 项目类型为
function_call_output。 item.call_id与前述response.done的 ID 相同。item.output是包含函数结果的 JSON 字符串。
加入结果项目后,再发送 response.create,触发使用函数数据的回答:
来源示例 61(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "response.create"
}
错误处理
会话期间服务器遇到错误会发出 error,有些错误可以追溯到应用发送的客户端事件。
HTTP 响应天然关联请求,而事件连接需要在客户端事件加 event_id,才能知道哪个事件触发错误。下例尝试发送不支持的事件:
来源示例 62(javascript):
// [中文] 示例 62:错误处理;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:scooby.dooby.doo;不要将构造载荷等同于收到成功确认。
const event = {
event_id: "my_awesome_event",
type: "scooby.dooby.doo",
};
dataChannel.send(JSON.stringify(event));
服务器随后返回类似错误:
来源示例 63(json):
程序文字译释77:无效值scooby.dooby.doo;省略号表示错误详情省略。
{
"type": "error",
"event_id": "event_error_123",
"error": {
"type": "invalid_request_error",
"code": "invalid_value",
"message": "Invalid value: 'scooby.dooby.doo'.",
"param": "type",
"event_id": "my_awesome_event"
}
}
打断与截断
用户可以在模型说话时插话。启用 VAD 后,Realtime 检测用户语音、取消当前响应并启动新的响应。为使模型自然续接,例如用户问“刚才最后说的是什么”,还需让它知道被打断的位置。截断就是从对话中删除上一响应尚未播放的部分。
WebRTC 和 SIP 中,服务器管理输出音频缓冲,知道当前已播放多少,并会在用户打断时自动截断未播放音频。
WebSocket 由客户端管理播放,因此必须停止播放并处理截断:
- 监听
input_audio_buffer.speech_started。启用 VAD 自动打断(interrupt_response: true)时,服务器会取消正在进行的响应;从response.done中的response.status: "cancelled"识别取消结果。客户端主动取消时发送response.cancel。 - 检测到打断后立即停止模型音频播放,丢弃该项目排队中的音频,记录用户实际听到的毫秒数;不要等待response.done才停止播放。
- 发送
conversation.item.truncate,携带被打断项目的ID、音频content_index和已播放时长audio_end_ms,从对话删除未播放部分。下方示例截断到已播放1.5秒的位置。
译注:早期指南快照曾写作
response.cancelled;当前客户端事件参考规定取消以状态为cancelled的response.done终结,不应把旧名称作为正式版服务端事件监听。CH7-P03 的原始差异记录继续保留。
来源示例 64(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "conversation.item.truncate",
"item_id": "item_1234",
"content_index": 0,
"audio_end_ms": 1500
}
模型没有足够信息精确对齐文字与音频,因此 conversation.item.truncate 在指定位置切断音频,并删除未播放部分的文本转录。它解决移除未播放音频的问题,但不提供一份截断后的转录文本。
按键说话
Realtime 默认通过 VAD 根据音频触发响应。也可以关闭 VAD,由应用控制何时送入音频,例如按住空格录音,松开时触发响应。这样用户能控制交互、避开 VAD 失败,也无需等待 VAD 超时,因此某些应用会更灵敏。
WebSocket 和 WebRTC 实现略有不同:WebSocket 所有事件共用有序通道;WebRTC 音频与控制事件分走不同通道。
WebSockets
客户端负责停止播放、处理打断并开启新响应:
- 通过
session.update将session.audio.input.turn_detection设为null,关闭 VAD。 - 按下时开始客户端录音;若模型仍在生成,发送
response.cancel。若模型音频还在播放,立即停止,并发送conversation.item.truncate删除未播放部分。 - 松开时发送带音频的
input_audio_buffer.append,写入输入缓冲。 - 发送
input_audio_buffer.commit,提交输入,并在已启用时启动输入转录。 - 发送
response.create触发回答。
WebRTC 与 SIP
实现类似,但必须明确清除输入音频缓冲:
- 通过
session.update将session.audio.input.turn_detection设为null。 - 按下时发送
input_audio_buffer.clear清空先前输入。若有进行中的响应,发送response.cancel;若输出仍在播放,发送output_audio_buffer.clear,清除未播放音频,同时截断对话。 - 松开时发送
input_audio_buffer.commit,提交已写入的音频,并在启用时开始输入转录。 - 发送
response.create触发回复。

TRAE-AI编程


更多建议: