English
主导航

旧版 API

实时对话

了解如何管理实时语音对话。

一旦你通过以下任一方式连接到 Realtime API WebRTC or WebSocket, 你可以调用 Realtime 模型(例如 gpt-realtime-2) 来进行语音对话。这样做需要你 发送客户端事件 to initiate actions, and 监听服务端事件 以响应 Realtime API 执行的操作。

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

如果你不需要与模型对话(即不期望获得任何响应),你可以在以下模式下使用 Realtime API 转录模式.

实时语音对话会话

Realtime 会话是模型与已连接客户端之间的有状态交互。该会话的核心组件包括:

  • The 会话 对象,它控制交互的参数,例如使用的模型、用于生成输出的语音以及其他配置。
  • A 对话, 代表当前会话期间产生的用户输入项和模型输出项。
  • 响应, 即在对话中添加的、由模型生成的音频或文本项。

输入音频缓冲区与 WebSockets

如果您使用的是 WebRTC,许多向模型发送和接收音频所需的媒体处理工作都将由 WebRTC API 协助完成。


如果您使用的是 WebSockets 处理音频,您将需要手动与 输入音频缓冲区 进行交互,即将音频发送至服务器,并随 JSON 事件一同发送 base64 编码的音频数据。

所有这些组件共同构成了一次实时会话。您将使用客户端事件来更新会话的状态,并监听服务端事件以响应会话内的状态变更。

diagram realtime state

会话生命周期事件

在通过 WebRTC or WebSockets, 服务器将发送一个 session.created 指示会话就绪的事件。在客户端,您可以使用 session.update 事件来更新当前的会话配置。大多数会话属性可以随时更新,除了在会话期间模型已经使用音频响应过一次后, voice 用于音频输出的模型。实时会话的最长持续时间为 60 分钟.

以下示例展示了如何使用 session.update 客户端事件。有关通过这些通道发送客户端事件的更多信息,请参阅 WebRTC or WebSocket 指南。

更新此会话中模型使用的系统指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const event = {
  type: "session.update",
  session: {
      type: "realtime",
      model: "gpt-realtime-2",
      // 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",
          },
          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));

会话更新后,服务器将发出一个 session.updated 事件,其中包含会话的新状态。

相关客户端事件相关服务器事件

session.update

session.created

session.updated

文本输入和输出

要使用 Realtime 模型生成文本,你可以向当前对话添加文本输入,要求模型生成响应,并监听指示模型响应进度的服务器发送事件。为了生成文本, 必须配置会话 with the text 模态(默认开启)。

使用 conversation.item.create 客户端事件创建一个新的文本对话项。这类似于在 Chat Completions 中发送 用户消息(提示词) in the REST API.

创建带有用户输入的对话项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()
dataChannel.send(JSON.stringify(event));

将用户消息添加到对话后,发送 response.create 事件以发起模型响应。如果当前会话同时启用了音频和文本,模型将同时以音频和文本内容进行响应。如果你只想生成文本,可以在发送 response.create 客户端事件时指定,如下所示。

生成纯文本响应
1
2
3
4
5
6
7
8
9
const event = {
  type: "response.create",
  response: {
    output_modalities: [ "text" ]
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

响应完全完成后,服务器将发出 response.done 事件。此事件将包含模型生成的完整文本,如下所示。

监听 response.done 以查看最终结果
1
2
3
4
5
6
7
8
9
10
11
12
function handleEvent(e) {
  const serverEvent = JSON.parse(e.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);

在生成模型响应的过程中,服务器会在此期间发出多个生命周期事件。你可以监听这些事件,例如 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 最强大的功能之一是与模型进行端到端的语音交互,无需中间的文本转语音或语音转文本步骤。这不仅降低了语音界面的延迟,还让模型能够获取并处理关于语音输入语调和抑扬顿挫的更多数据。

语音选项

Realtime 会话可配置为在生成音频输出时使用几种内置语音之一。你可以在创建会话时(或在 voice 上)设置 response.create以控制模型的声音。当前的语音选项包括 alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin,且 cedar. 一旦模型在会话中发出了音频, voice 在相应会话中无法修改。为了获得最佳质量,我们建议使用 marin or cedar.

使用 WebRTC 处理音频

如果您使用 WebRTC 连接到 Realtime API,Realtime API 将作为 对等连接 连接到您的客户端。来自模型的音频输出作为 远程媒体流. 模型的音频输入是通过音频设备收集的(getUserMedia)传送到您的客户端,并且媒体流作为轨道添加到对等连接中。

来自 WebRTC 连接指南 中的示例代码展示了使用浏览器 API 配置本地和远程音频的基本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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]);

上面的代码片段实现了与 Realtime API 的简单交互,但这只是冰山一角。如需获取更多不同类型用户界面的示例,请查看 WebRTC 示例 代码库。这些示例的在线演示也可以 在此处找到.

使用 浏览器中的媒体捕获和流 使您能够执行诸如将麦克风静音和取消静音、选择用于收集输入的设备等操作。

WebRTC 中音频的客户端和服务器事件

默认情况下,WebRTC 客户端在发送音频输入之前,不需要向 Realtime API 发送任何客户端事件。一旦将本地音频轨道添加到对等连接中,您的用户就可以直接开始说话!

然而,当音频在客户端和服务器之间通过对等连接来回传输时,WebRTC 客户端仍然会接收许多服务器发送的生命周期事件。例如:

操作用于媒体流的 WebRTC API 可能足以满足您的所有控制需求。但是,有时可能需要使用较低层级的接口来进行音频输入和输出。请参阅下文的 WebSockets 部分,以获取更多信息和处理精细音频输入所需的事件列表。

使用 WebSockets 处理音频

当通过 WebSocket 发送和接收音频时,在从客户端发送媒体以及从服务器接收媒体方面,您需要做更多的工作。下表描述了在 WebSocket 会话期间通过 WebSocket 发送和接收音频所需的事件流程。

以下事件按生命周期顺序给出,尽管某些事件(例如 delta 事件)可能会同时发生。

生命周期阶段客户端事件服务端事件
会话初始化

session.update

session.created

session.updated

用户音频输入

conversation.item.create


  (发送完整音频消息)

input_audio_buffer.append


  (分块流式传输音频)

input_audio_buffer.commit


  (在禁用 VAD 时使用)

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.created

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 编码的音频字节 的块,通过套接字发送至 Realtime API。每个块的大小不能超过 15 MB。

输入块的格式可以针对整个会话进行配置,也可以按响应进行配置。

将音频输入字节追加到对话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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) {
    let s = Math.max(-1, Math.min(1, float32Array[i]));
    view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
  return buffer;
}

// Converts a Float32Array to base64-encoded PCM16 data
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.apply(null, chunk);
  }
  return btoa(binary);
}

// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
  './path/to/sample1.wav',
  './path/to/sample2.wav',
  './path/to/sample3.wav'
];

for (const filename of files) {
  const audioFile = fs.readFileSync(filename);
  const audioBuffer = await decodeAudio(audioFile);
  const channelData = audioBuffer.getChannelData(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'}));

发送完整音频消息

也可以创建包含完整音频录制的对话消息。使用 conversation.item.create 客户端事件来创建包含 input_audio content.

创建完整音频输入对话项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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));

通过 WebSocket 处理音频输出

要在网络浏览器等客户端设备上播放输出音频,我们建议使用 WebRTC 而不是 WebSocket. 在不确定的网络条件下,WebRTC 向客户端设备发送媒体将更加稳健。

但在使用 WebSocket 的服务端到服务端应用程序中处理音频输出时,你需要监听 response.output_audio.delta 事件,其中包含来自模型的 Base64 编码音频数据块。你需要缓冲这些块并将其写入文件,或者立即将其流式传输到另一个源,例如 通过 Twilio 进行的电话呼叫.

请注意,由于诸多原因, response.output_audio.done and response.done 事件实际上不会包含音频数据,仅包含音频内容转录。要获取实际的字节,你需要监听 response.output_audio.delta events.

输出块的格式可以针对整个会话进行配置,也可以按响应进行配置。

监听 response.output_audio.delta 事件
1
2
3
4
5
6
7
8
9
10
function handleEvent(e) {
  const serverEvent = JSON.parse(e.data);
  if (serverEvent.type === "response.audio.delta") {
    // Access Base64-encoded audio chunks
    // console.log(serverEvent.delta);
  }
}

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

图像输入

gpt-realtime-2 and gpt-realtime 还支持图像输入。你可以将图像作为用户消息中的内容部分进行附加,模型在响应时可以结合图像中的内容。

将图像添加到对话中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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));

语音活动检测

默认情况下,实时会话具有 语音活动检测 (VAD) 已启用,这意味着 API 将判断用户何时开始或停止说话,并自动进行响应。

有关如何配置 VAD 的更多信息,请阅读我们的 语音活动检测 guide.

禁用 VAD

可以通过设置 turn_detection to null with the session.update 客户端事件来禁用 VAD。这对于你希望对音频输入进行精细控制的场景非常有用,例如 按键通话 interfaces.

当 VAD 被禁用时,客户端必须手动发送一些额外的客户端事件来触发音频响应:

保留 VAD,但禁用自动响应

如果您希望保持 VAD 模式启用,但只想保留手动决定何时生成响应的能力,您可以设置 turn_detection.interrupt_response and turn_detection.create_response to false with the session.update 客户端事件。这将保留 VAD 的所有行为,但不会自动创建新响应。客户端可以通过 response.create event.

这对于内容审核、输入验证或 RAG 模式非常有用,在这些场景下,您可以接受以增加一点交互延迟为代价来换取对输入的控制权。

在默认对话之外创建响应

默认情况下,在会话期间生成的所有响应都会添加到该会话的对话状态(即“默认对话”)中。但是,您可能希望在会话默认对话的上下文之外生成模型响应,或者同时生成多个响应。您可能还希望对模型生成响应时所考虑的对话项进行更细粒度的控制(例如,仅考虑最近 N 轮对话)。

通过将 response.conversation 字段设置为字符串 none ,即可生成不添加到默认对话状态的“带外”响应,该设置是在使用 response.create 客户端事件创建响应时完成的。

在创建带外响应时,您可能还需要某种方法来识别哪些服务器发送事件属于该响应。您可以提供 metadata 用于您的模型响应,以帮助您识别是哪个响应正在为该客户端发送事件生成。

创建带外模型响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()
dataChannel.send(JSON.stringify(event));

现在,当您监听 response.done 服务器事件时,您可以识别带外响应的结果。

创建带外模型响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function handleEvent(e) {
  const serverEvent = JSON.parse(e.data);
  if (
    serverEvent.type === "response.done" &&
    serverEvent.response.metadata?.topic === "classification"
  ) {
    // this server event pertained to our OOB model response
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
// ws.on("message", handleEvent);

为响应创建自定义上下文

你还可以构建一个自定义上下文,供模型在默认/当前对话之外用于生成响应。这可以通过在 input 客户端事件的 response.create 数组上实现。你可以使用新的输入项,或通过 ID 引用对话中现有的输入项。

监听带有自定义上下文的带外模型响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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: "some_conversation_item_id"
      },
      {
        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()
dataChannel.send(JSON.stringify(event));

创建无上下文的响应

你还可以将响应插入到默认对话中,忽略所有其他指令和上下文。可以通过设置 input to an empty array.

将无上下文的模型响应插入到默认对话中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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: [],
    instructions: prompt,
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

函数调用

Realtime 模型还支持 函数调用, 允许你执行自定义代码以扩展模型的功能。其高层工作原理如下:

  1. 更新会话 or 创建响应, 你可以指定一个供模型调用的可用函数列表。
  2. 如果在处理输入时,模型决定应进行函数调用,它将在对话中添加表示函数调用参数的项目。
  3. 当客户端检测到包含函数调用参数的对话项目时,它将使用这些参数执行自定义代码
  4. 当自定义代码执行完毕后,客户端将创建包含函数调用输出的新对话项目,并要求模型进行响应。

下面我们通过添加一个可调用函数来看看这在实际中是如何运作的,该函数将为模型用户提供今日星座运势。我们将展示需要发送的客户端事件对象的结构,以及服务器随后将发出的事件。

配置可调用函数

首先,我们必须为模型提供一系列可基于用户输入调用的函数。可用函数既可以在会话级别进行配置,也可以在单个响应级别进行配置。

以下是一个 session.update 的客户端事件载荷示例,它配置了一个星座运势生成函数,该函数接受一个参数(即应生成运势的星座):

session.update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
  "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"
  }
}

The description 函数及其参数的字段有助于模型选择是否调用该函数,以及在各个参数中包含哪些数据。如果模型接收到表明用户想要查看星座运势的输入,它将使用以下参数调用此函数: sign parameter.

检测模型何时想要调用函数

根据模型的输入,模型可能会决定调用函数以生成最佳响应。假设我们的应用程序添加了以下带有 conversation.item.create 事件的对话项,然后创建响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "What is my horoscope? I am an aquarius."
      }
    ]
  }
}

随后通过一个 response.create 客户端事件来生成响应:

1
2
3
{
  "type": "response.create"
}

模型不会立即返回文本或音频响应,而是会生成一个响应,其中包含应传递给开发者应用程序中函数的参数。你可以使用 response.function_call_arguments.delta 服务器事件来监听函数调用参数的实时更新,但是 response.done 也会包含我们调用函数所需的完整数据。

response.done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "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—— 你需要使用此 ID 将函数调用的结果传回给模型.

基于这些信息,我们可以在应用程序中执行代码来生成星座运势,然后将该信息返回给模型,以便其生成响应。

将函数调用的结果提供给模型

在接收到包含函数调用参数的模型响应后,你的应用程序可以执行相应的代码来满足该函数调用。这可以是你需要的任何操作,例如调用外部 API 或访问数据库。

当你准备好将自定义代码的结果提供给模型时,可以通过 conversation.item.create 客户端事件创建响应时完成的。

1
2
3
4
5
6
7
8
{
  "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 事件。这将触发模型使用函数调用中的数据生成响应。

1
2
3
{
  "type": "response.create"
}

错误处理

The error 每当服务器在会话期间遇到错误情况时,就会发出该事件。有时,这些错误可以追溯到你的应用程序发出的客户端事件。

与请求和响应隐式关联的 HTTP 请求不同,我们需要使用客户端事件上的 event_id 属性来了解它们中的哪一个在服务器上触发了错误情况。此技术如下方代码所示,其中客户端尝试发出不支持的事件类型。

1
2
3
4
5
6
const event = {
  event_id: "my_awesome_event",
  type: "scooby.dooby.doo",
};

dataChannel.send(JSON.stringify(event));

这个从客户端发送的失败事件将发出类似以下的错误事件:

1
2
3
4
5
6
7
{
  "type": "invalid_request_error",
  "code": "invalid_value",
  "message": "Invalid value: 'scooby.dooby.doo' ...",
  "param": "type",
  "event_id": "my_awesome_event"
}

中断与截断

在许多语音应用中,用户可以在模型说话时打断它。Realtime API 在启用 VAD 的情况下会处理中断,即检测到用户语音、取消正在进行的响应并启动新响应。然而,在这种情况下,你会希望模型知道它在何处被打断,以便自然地继续对话(例如,如果用户问“你刚才说了什么?”)。我们称之为 截断 模型的最后一次响应,即从对话中移除模型最后一次响应中未播放的部分。

在 WebRTC 和 SIP 连接中,服务器管理输出音频缓冲区,因此知道在给定时刻已播放了多少音频。当发生用户中断时,服务器将自动截断未播放的音频。

在 WebSocket 连接中,由客户端管理音频播放,因此必须停止播放并处理截断。以下是此过程的工作原理:

  1. 客户端监控来自服务器的新 input_audio_buffer.speech_started 事件,该事件表明用户已开始说话。服务器将自动取消任何进行中的模型响应,并触发一个 response.cancelled 事件。
  2. 当客户端检测到此事件时,应立即停止播放当前正在播放的任何来自模型的音频。应记录在上次中断前,上一个音频响应播放了多少。
  3. 客户端应发送一个 conversation.item.truncate 事件,以便从对话中移除模型上次响应中未播放的部分。

以下是一个示例:

1
2
3
4
5
6
{
    "type": "conversation.item.truncate",
    "item_id": "item_1234", # this is the item ID of the model's last response
    "content_index": 0,
    "audio_end_ms": 1500 # truncate audio after 1.5 seconds
}

那么也要截断文本记录吗?实时模型没有足够的信息来精确对齐文本和音频,因此 conversation.item.truncate 会在给定位置截断音频,并移除未播放部分的文本记录。这解决了移除未播放音频的问题,但无法提供截断后的文本记录。

按键通话

Realtime API 默认使用语音活动检测 (VAD),这意味着模型将根据音频输入触发响应。你也可以通过禁用 VAD 来实现按键通话交互,并使用应用层的控制机制来控制何时将音频输入发送给模型,例如按住空格键捕获音频,松开时触发响应。对于某些应用来说,这种方式效果出奇地好——它让用户能够控制交互,避免了 VAD 失败,而且响应迅速,因为无需等待 VAD 超时。

在 WebSocket 和 WebRTC 上实现按键通话略有不同。在 Realtime API WebSocket 连接中,所有事件都在同一通道中按相同顺序发送,而 WebRTC 连接则为音频和控制事件提供独立的通道。

WebSockets

要通过 WebSocket 连接实现按键通话,你需要让客户端停止音频播放、处理中断并启动新的响应。以下是更详细的步骤:

  1. 通过设置关闭 VAD "turn_detection": null in a session.update event.
  2. 按下时,开始在客户端录制音频。
    1. 如果模型有一个正在进行的响应,请通过发送一个取消它 response.cancel event.
    2. 如果模型有正在进行的输出播放,请立即停止播放并发送一个 conversation.item.truncate 事件,以从对话中移除任何未播放的音频。
  3. 松开时,发送一个 input_audio_buffer.append 消息连同音频一起,以将新音频放入输入缓冲区。
  4. 发送一个 input_audio_buffer.commit 事件,这将提交写入输入缓冲区的音频并启动输入转录(如果已启用)。
  5. 然后使用一个触发响应 response.create event.

WebRTC 和 SIP

使用 WebRTC 实现按键通话类似,但必须显式清除输入音频缓冲区。以下是操作步骤:

  1. 通过设置关闭 VAD "turn_detection": null in a session.update event.
  2. 按下时,发送一个 input_audio_buffer.clear 事件以清除任何之前的音频输入。
    1. 如果模型有一个正在进行的响应,请通过发送一个取消它 response.cancel event.
    2. 如果模型有正在进行的输出播放,请发送一个 output_audio_buffer.clear 事件以清除未播放的音频,这也会同时截断对话。
  3. 松开时,发送一个 input_audio_buffer.commit 事件,这将提交写入输入缓冲区的音频并启动输入转录(如果已启用)。
  4. 然后使用一个触发响应 response.create event.