OpenAI Realtime 对话

2026-11-06 12:39 更新

通过 WebRTC 或 WebSocket 连接 Realtime API 后,可以调用 gpt-realtime-2.1 等模型进行语音到语音对话。需要发送客户端事件发起操作,并监听服务端事件,响应 API 执行的动作。

本指南介绍音频和文本生成、图像输入、函数调用所需的事件流程,以及如何理解 Realtime 会话状态。

如果不需要与模型对话,也就是不期待模型回复,可使用转录模式。

Realtime 语音到语音会话

Realtime Session 是模型与已连接客户端之间的有状态交互,主要由以下部分组成:

  • Session 对象:控制模型、输出声音等交互参数。
  • Conversation:表示当前会话中的用户输入项与模型输出项。
  • Responses:模型生成并加入 Conversation 的音频或文本项。

输入音频缓冲区与 WebSockets:

WebRTC API 会协助处理大部分模型音频收发。WebSocket 则需要手动操作输入音频缓冲区,通过 JSON 事件发送 Base64 编码音频。

以上组成 Realtime 会话。客户端事件用来更新状态,服务端事件用于响应状态变化。

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 模型支持函数调用,通过执行自定义代码扩展能力。大致流程:

  1. 更新会话或创建响应时,指定可调用函数列表。
  2. 模型处理输入时若判断需要函数,就向对话添加包含调用参数的项目。
  3. 客户端检测到这类项目后,用参数运行自定义代码。
  4. 代码完成后,客户端创建含调用结果的新项目,并请求模型回答。

以下以提供当天星座运势的函数为例,展示需要发送的客户端事件与服务器返回结构。

配置可调用函数

先向模型提供可依据用户输入选择的函数。函数可在会话级 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 由客户端管理播放,因此必须停止播放并处理截断:

  1. 监听 input_audio_buffer.speech_started。启用 VAD 自动打断(interrupt_response: true)时,服务器会取消正在进行的响应;从 response.done 中的 response.status: "cancelled" 识别取消结果。客户端主动取消时发送 response.cancel。
  2. 检测到打断后立即停止模型音频播放,丢弃该项目排队中的音频,记录用户实际听到的毫秒数;不要等待response.done才停止播放。
  3. 发送 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

客户端负责停止播放、处理打断并开启新响应:

  1. 通过 session.update 将 session.audio.input.turn_detection 设为 null,关闭 VAD。
  2. 按下时开始客户端录音;若模型仍在生成,发送 response.cancel。若模型音频还在播放,立即停止,并发送 conversation.item.truncate 删除未播放部分。
  3. 松开时发送带音频的 input_audio_buffer.append,写入输入缓冲。
  4. 发送 input_audio_buffer.commit,提交输入,并在已启用时启动输入转录。
  5. 发送 response.create 触发回答。

WebRTC 与 SIP

实现类似,但必须明确清除输入音频缓冲:

  1. 通过 session.update 将 session.audio.input.turn_detection 设为 null。
  2. 按下时发送 input_audio_buffer.clear 清空先前输入。若有进行中的响应,发送 response.cancel;若输出仍在播放,发送 output_audio_buffer.clear,清除未播放音频,同时截断对话。
  3. 松开时发送 input_audio_buffer.commit,提交已写入的音频,并在启用时开始输入转录。
  4. 发送 response.create 触发回复。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号