---
title: Realtime 兼容协议设计
description: 面向集成方的 OpenAI Realtime 兼容 WebRTC、WebSocket 与扩展事件协议。
icon: radio
---

# Realtime 兼容协议设计

> 当前实现版本为 `Interactive Media Realtime Extensions v1`。OpenAI 兼容接口与现有 `/api/webrtc/offer`、`/ws/voice`、`/ws/stream`、RTVI 消息并行提供；RTVI 不是待删除的迁移接口。

## 设计目标

AI 视频助手以 OpenAI Realtime 协议作为对外接口，同时保留助手、知识库、工具、工作流、动态变量和视频输入等现有能力。

协议适配层只负责连接、事件和 Pipecat Frame 之间的转换，不引入第二套语音运行时：

```text
OpenAI Realtime 客户端
        │
        │ WebRTC / WebSocket
        ▼
Realtime 协议适配层
        │
        │ Pipecat transport / frame
        ▼
现有 run_pipeline()
        │
        ├── Prompt / Workflow / External Agent
        ├── ASR / LLM / TTS / Realtime Model
        ├── Knowledge / System、HTTP、Client、MCP Tools
        └── History / Handoff / End Call
```

协议分为两层：

| 层级 | 约束 | 面向对象 |
| --- | --- | --- |
| OpenAI Realtime Core | 保持标准事件名称、字段含义和生命周期，不改变标准事件语义 | 任意 OpenAI Realtime 兼容客户端 |
| `x.interactive_media.*` Extensions | 所有项目扩展均有命名空间和能力协商 | 了解 Interactive Media 扩展的客户端 |

普通兼容客户端不声明扩展能力时，服务端不会发送 `x.interactive_media.*` 事件。它仍可使用音频、文本、图片、转写、回复流、打断和函数调用，但不会获得工作流节点、动态变量、连续视频等项目专属状态。

## 接口概览

假设服务地址为 `https://api.example.com`：

| 接口 | 用途 | 推荐客户端 |
| --- | --- | --- |
| `POST /v1/realtime/client_secrets` | 创建短期、绑定助手的临时令牌 | 浏览器或移动端 |
| `POST /v1/realtime/calls` | 用 SDP Offer 创建 WebRTC 会话并返回 SDP Answer | 浏览器、移动端 |
| `wss://api.example.com/v1/realtime?model=assistant:<id>` | 创建 WebSocket Realtime 会话 | 服务端、话务网关、自定义客户端 |

`model` 使用 `assistant:<id>`，其中 `id` 是当前数据库中的助手 ID，例如：

```text
assistant:asst_xxx
```

这里的 `model` 代表一个完整助手，而不是直接选择底层模型。助手绑定的系统提示词、模型凭证、知识库、服务端工具、工作流和运行模式仍由服务端控制。

### 鉴权

- 长期 API Key 仅供可信服务端使用，通过 `Authorization: Bearer <api-key>` 发送。
- 浏览器先由自己的业务后端创建临时令牌，再用临时令牌连接 Realtime 接口。
- 临时令牌绑定助手和会话配置，默认有效期为 60 秒；首版不保证单次使用，令牌只用于建连，建连后到期不会中止会话。
- 父长期 Key 被撤销或过期后，尚未使用的临时令牌立即失效。
- 管理后台 Cookie 不属于公开兼容协议，也不应作为第三方客户端的鉴权方式。
- 会话和事件不会返回底层模型密钥、助手系统提示词或工具密钥。

### 长期 API Key 管理

Realtime Key 存在数据库中，每个 Key 默认可访问当前及未来创建的全部助手。管理接口仅允许后台管理员 Cookie 或 Basic Auth 调用：

| 接口 | 行为 |
| --- | --- |
| `POST /api/realtime/api-keys` | 创建 Key；完整 `sk-rt-...` 只在本次响应显示一次 |
| `GET /api/realtime/api-keys` | 查看名称、前缀、有效期、最后使用时间和状态，不返回密钥 |
| `DELETE /api/realtime/api-keys/{id}` | 逻辑撤销并保留审计记录 |

轮换方式为先创建新 Key，再撤销旧 Key。服务端只保存使用部署级 pepper 计算的 HMAC-SHA256，不保存可恢复的 Key 明文。

## 会话配置

首次建连配置以及后续 `session.update` 使用标准 Realtime session 结构：

```json
{
  "type": "realtime",
  "model": "assistant:asst_xxx",
  "output_modalities": ["audio"],
  "audio": {
    "input": {
      "format": {
        "type": "audio/pcm",
        "rate": 24000
      },
      "turn_detection": {
        "type": "server_vad"
      }
    },
    "output": {
      "format": {
        "type": "audio/pcm"
      }
    }
  }
}
```

客户端可修改输出模态，以及输入端 Server VAD / Push-to-Talk 参数。服务端在 `session.updated` 中返回**实际生效**的配置。

以下内容始终以服务端助手配置为准：

- 助手身份和基础系统提示词；
- Pipeline 或 Realtime 运行模式；
- 底层模型资源及凭证；
- 知识库和服务端 System、HTTP、MCP 工具；
- 工作流图、结束条件和接管策略。
- voice、工具定义、系统提示词和每次回复的临时指令。

服务端不能静默接受一个实际未生效的字段。字段不支持、被锁定或没有权限时，应返回标准 `error` 事件。

## WebRTC

WebRTC 是浏览器和移动端实时音视频交互的首选通道。音频和可选视频走媒体轨道，JSON 事件走 DataChannel。

### 建连方式一：统一接口

可信业务后端向 `/v1/realtime/calls` 发送 `multipart/form-data`：

| 表单字段 | 类型 | 说明 |
| --- | --- | --- |
| `sdp` | string | 浏览器生成的 SDP Offer |
| `session` | JSON string | Realtime session 配置，必须选择一个已授权助手 |

请求使用长期 API Key 鉴权。成功响应为 `Content-Type: application/sdp` 的 SDP Answer。

```bash
curl -X POST "https://api.example.com/v1/realtime/calls" \
  -H "Authorization: Bearer $REALTIME_API_KEY" \
  -F 'sdp=<offer.sdp' \
  -F 'session={"type":"realtime","model":"assistant:asst_xxx"}'
```

业务浏览器不应直接持有示例中的长期 API Key。它通常把 Offer 发给自己的业务后端，由后端完成上述调用。

### 建连方式二：临时令牌

1. 可信业务后端使用长期 API Key 调用 `/v1/realtime/client_secrets`。
2. 服务端返回绑定助手的短期令牌。
3. 浏览器创建 `RTCPeerConnection`、音频轨道和 `oai-events` DataChannel。
4. 浏览器以临时令牌调用 `/v1/realtime/calls`，请求体为原始 SDP Offer，类型为 `application/sdp`。
5. 浏览器把响应中的 SDP Answer 设置为远端描述。

创建令牌的请求示例：

```json
{
  "session": {
    "type": "realtime",
    "model": "assistant:asst_xxx",
    "output_modalities": ["audio"]
  }
}
```

浏览器侧的核心连接过程如下：

```js
const pc = new RTCPeerConnection({ iceServers });
const remoteAudio = new Audio();
remoteAudio.autoplay = true;

pc.ontrack = (event) => {
  if (event.track.kind === "audio") {
    remoteAudio.srcObject = event.streams[0];
  }
};

const localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
pc.addTrack(localStream.getAudioTracks()[0], localStream);

const events = pc.createDataChannel("oai-events");
events.onmessage = (event) => {
  const serverEvent = JSON.parse(event.data);
  console.log(serverEvent);
};

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const answerSdp = await fetch("https://api.example.com/v1/realtime/calls", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${ephemeralToken}`,
    "Content-Type": "application/sdp"
  },
  body: offer.sdp
}).then((response) => response.text());

await pc.setRemoteDescription({ type: "answer", sdp: answerSdp });
```

### WebRTC 数据分工

| 数据 | 通道 | 说明 |
| --- | --- | --- |
| 用户麦克风 | WebRTC audio track | 不再通过 JSON 重复发送音频块 |
| 助手语音 | WebRTC remote audio track | 浏览器直接播放，不依赖 `response.output_audio.delta` 拼接 |
| 标准和扩展事件 | `oai-events` DataChannel | 使用 UTF-8 JSON 文本，保持可靠、有序传输 |
| 用户摄像头 | 可选 WebRTC video track | 通过 `video_track` capability 协商，非 OpenAI 标准能力 |

摄像头轨道只在助手启用视觉、令牌允许且 SDP 协商成功时接收。普通 OpenAI 兼容客户端不添加视频轨道，仍可正常完成语音和文本会话。

切换摄像头、静音、音量控制和 `RTCPeerConnection.getStats()` 属于客户端 WebRTC 能力，不需要新增协议事件。切换设备时可使用 `RTCRtpSender.replaceTrack()`，无需重建会话。

## WebSocket

WebSocket 适合服务到服务、话务网关和希望自行处理音频缓冲的客户端。连接时在 query 中指定助手：

```text
wss://api.example.com/v1/realtime?model=assistant%3Aasst_xxx
```

可信服务端在握手请求中发送：

```http
Authorization: Bearer <api-key-or-ephemeral-token>
```

浏览器无法设置 `Authorization` 握手头时，可同时发送 `realtime` 与 `openai-insecure-api-key.<token>` WebSocket 子协议；服务端选择 `realtime`。这只适用于短期令牌，不应把长期 Key 放入浏览器。

连接成功后，客户端和服务端都只发送 UTF-8 JSON 文本帧。浏览器无法安全保存长期 API Key，浏览器实时通话仍应优先使用 WebRTC。

### 文本输入

先创建用户消息，再请求生成回复：

```json
{
  "event_id": "evt_text_001",
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "请介绍办理流程"
      }
    ]
  }
}
```

```json
{
  "event_id": "evt_response_001",
  "type": "response.create"
}
```

### 音频输入和输出

WebSocket 音频以 Base64 编码放入标准事件：

```json
{
  "event_id": "evt_audio_001",
  "type": "input_audio_buffer.append",
  "audio": "<base64-pcm-bytes>"
}
```

- 开启 Server VAD 时，服务端自动产生 `speech_started`、`speech_stopped` 并触发回复。
- 关闭 VAD 时，客户端发送 `input_audio_buffer.commit`，然后发送 `response.create`。
- 服务端通过 `response.output_audio.delta` 返回 Base64 音频块，并以 `response.output_audio.done` 结束。
- 助手文本或音频转写分别通过 `response.output_text.*` 和 `response.output_audio_transcript.*` 返回。

WebSocket MVP 不传输连续原始视频。视觉输入使用标准 `input_image`；需要连续摄像头的客户端使用 WebRTC 视频轨道。

### 图片输入

图片和文本可放在同一个用户消息中：

```json
{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "画面里有什么？"
      },
      {
        "type": "input_image",
        "image_url": "data:image/jpeg;base64,<base64-image>"
      }
    ]
  }
}
```

## Push-to-Talk

Push-to-Talk（按住说话）属于 OpenAI Realtime Core 兼容能力，不需要新增 `x.interactive_media.push_to_talk.*` 扩展事件。按钮是否按下、快捷键和麦克风 UI 状态由客户端本地管理，服务端只处理标准音频缓冲与回复事件。

### 关闭自动 VAD

客户端建立会话后先通过 `session.update` 关闭自动轮次检测：

```json
{
  "event_id": "evt_ptt_config_001",
  "type": "session.update",
  "session": {
    "type": "realtime",
    "audio": {
      "input": {
        "turn_detection": null
      }
    }
  }
}
```

关闭 VAD 后，服务端不会根据静音自动提交用户语音或创建回复。客户端必须在用户松开按钮时发送 `input_audio_buffer.commit`，然后发送 `response.create`。

### WebSocket 时序

WebSocket 的音频和控制事件在同一条有序连接上传输：

| 阶段 | 客户端行为 |
| --- | --- |
| 按下 | 开始在客户端录音；如果已有活动回复，发送 `response.cancel` |
| 打断播放 | 立即停止本地音频播放，并发送 `conversation.item.truncate`，用 `audio_end_ms` 删除用户未听到的内容 |
| 松开 | 通过一个或多个 `input_audio_buffer.append` 发送本次录音 |
| 提交 | 依次发送 `input_audio_buffer.commit` 和 `response.create` |

完整的松开事件序列如下：

```json
{
  "event_id": "evt_ptt_audio_001",
  "type": "input_audio_buffer.append",
  "audio": "<base64-pcm-bytes>"
}
```

```json
{
  "event_id": "evt_ptt_commit_001",
  "type": "input_audio_buffer.commit"
}
```

```json
{
  "event_id": "evt_ptt_response_001",
  "type": "response.create"
}
```

如果用户在录音完成前取消本次输入，客户端应丢弃本地录音；已经发送到服务端的未提交音频则使用 `input_audio_buffer.clear` 清除。

### WebRTC 时序

WebRTC 的音频媒体轨道和 `oai-events` 控制事件属于不同通道，因此开始新一轮输入前必须显式清理旧缓冲：

| 阶段 | 客户端行为 |
| --- | --- |
| 按下 | 先发送 `input_audio_buffer.clear`，再开始或放开本地麦克风输入门控 |
| 打断生成 | 如果已有活动回复，发送 `response.cancel` |
| 打断播放 | 如果助手音频仍在播放，发送 `output_audio_buffer.clear`；服务端同时截断未播放的对话内容 |
| 松开 | 关闭本地麦克风输入门控，依次发送 `input_audio_buffer.commit` 和 `response.create` |

```js
function sendEvent(event) {
  dataChannel.send(JSON.stringify(event));
}

function onPushDown() {
  sendEvent({ type: "input_audio_buffer.clear" });

  if (hasActiveResponse) {
    sendEvent({ type: "response.cancel" });
  }

  if (isAssistantAudioPlaying) {
    sendEvent({ type: "output_audio_buffer.clear" });
  }

  microphoneTrack.enabled = true;
}

function onPushUp() {
  microphoneTrack.enabled = false;
  sendEvent({ type: "input_audio_buffer.commit" });
  sendEvent({ type: "response.create" });
}
```

客户端应处理指针移出、窗口失焦和权限撤销等情况，确保一次按下只产生一次提交。没有有效音频时不要发送 `input_audio_buffer.commit`。

### 保留 VAD、手动触发回复

如果只想由客户端决定何时生成回复，但仍希望服务端判断说话开始和结束，可以保留 VAD，并关闭自动回复及自动打断：

```json
{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "audio": {
      "input": {
        "turn_detection": {
          "type": "server_vad",
          "interrupt_response": false,
          "create_response": false
        }
      }
    }
  }
}
```

这种模式适合在提交前进行审核、输入校验或知识检索，不等同于严格的按住说话。

## 标准事件范围

首版兼容层至少支持以下 OpenAI Realtime 事件。

### 客户端发送

| 事件 | 用途 |
| --- | --- |
| `session.update` | 更新允许覆盖的会话配置 |
| `conversation.item.create` | 发送文本、整段音频、图片或函数结果 |
| `conversation.item.truncate` | 打断后删除未播放的助手内容 |
| `input_audio_buffer.append` | WebSocket 追加音频 |
| `input_audio_buffer.commit` | 无 VAD 时提交音频 |
| `input_audio_buffer.clear` | 清除未提交音频 |
| `output_audio_buffer.clear` | WebRTC/SIP 清除未播放音频并截断上下文 |
| `response.create` | 请求助手生成回复 |
| `response.cancel` | 取消正在生成的回复 |

### 服务端发送

| 事件组 | 事件 |
| --- | --- |
| 会话 | `session.created`、`session.updated`、`error` |
| 用户语音 | `input_audio_buffer.speech_started`、`input_audio_buffer.speech_stopped`、`input_audio_buffer.committed` |
| 用户转写 | `conversation.item.input_audio_transcription.completed` |
| 对话项 | `conversation.item.added`、`conversation.item.done` |
| 回复生命周期 | `response.created`、`response.output_item.added`、`response.output_item.done`、`response.done` |
| 文本 | `response.output_text.delta`、`response.output_text.done` |
| 音频 | `response.output_audio.delta`、`response.output_audio.done` |
| 音频转写 | `response.output_audio_transcript.delta`、`response.output_audio_transcript.done` |
| 函数调用 | `response.function_call_arguments.delta`、`response.function_call_arguments.done` |

WebRTC 中的助手音频本体走媒体轨道，因此客户端不应依赖 `response.output_audio.delta` 播放声音；音频生命周期和转写事件仍通过 `oai-events` 发送。

## 首版兼容矩阵

| 分类 | 状态 | 说明 |
| --- | --- | --- |
| WebRTC 音频、`oai-events` DataChannel | 支持 | 复用 SmallWebRTC 和现有 Pipeline |
| WebSocket 文本与 24 kHz PCM16 mono Base64 音频 | 支持 | 只接受 UTF-8 JSON 文本帧 |
| 文本轮次、Server VAD、PTT、取消和清空输出 | 支持 | `turn_detection: null` 时由 `commit` + `response.create` 结束轮次 |
| `input_image` Data URL | 条件支持 | 仅 Pipeline 模式且助手已授权视觉输入；Realtime 模式明确报错 |
| Client Tool 标准 function call | 支持 | System、HTTP、MCP 工具仍在服务端内部执行 |
| `x.interactive_media.*` | 协商后支持 | 未协商的客户端不会收到扩展事件 |
| `model`、instructions、voice、工具和系统提示词 | 锁定 | 由助手配置决定，覆盖请求返回标准 `error` |
| WebSocket 其他采样率、压缩音频、连续视频 | 暂不支持 | WebRTC 音频由 SDP 协商；连续视频仅走 WebRTC |
| 公开助手别名、断线续传、并发回复、SIP | 暂不支持 | `model` 必须使用实际的 `assistant:asst_xxx` |

Qwen Realtime provider 只允许在 provider 会话首次配置前选择 PTT 或自动轮次检测；建立连接后再次切换会返回明确错误。Pipeline 与支持动态更新的 Realtime provider 可在会话内切换。

## 工具调用

服务端 System、HTTP 和 MCP 工具在现有运行时内部执行。Client 工具使用标准函数调用协议，不再建立一套平行的 `client-tool-call` 公开协议。

服务端完成函数参数后发送标准函数调用事件；客户端执行函数并回传：

```json
{
  "type": "conversation.item.create",
  "item": {
    "type": "function_call_output",
    "call_id": "call_abc123",
    "output": "{\"ok\":true,\"result\":{\"ticket_id\":\"T-1001\"}}"
  }
}
```

随后发送 `response.create` 让助手基于工具结果继续当前轮次。`call_id` 在整个会话内唯一，客户端应原样返回。

## Interactive Media 扩展

### 能力协商

客户端收到 `session.created` 后主动声明所需扩展：

```json
{
  "event_id": "evt_capabilities_001",
  "type": "x.interactive_media.capabilities.update",
  "capabilities": [
    "workflow_events",
    "dynamic_variables",
    "handoff",
    "video_track"
  ]
}
```

服务端返回实际接受的能力：

```json
{
  "event_id": "evt_server_001",
  "type": "x.interactive_media.capabilities.updated",
  "capabilities": [
    "workflow_events",
    "dynamic_variables",
    "handoff",
    "video_track"
  ],
  "rejected": []
}
```

服务端只发送已接受能力对应的扩展事件。扩展版本与 OpenAI Realtime Core 独立演进。

### 扩展事件目录

| 方向 | 事件 | 用途 |
| --- | --- | --- |
| Client → Server | `x.interactive_media.capabilities.update` | 请求扩展能力 |
| Server → Client | `x.interactive_media.capabilities.updated` | 返回允许的能力 |
| Client → Server | `x.interactive_media.session.variables.update` | 静默更新动态变量，不创建用户消息 |
| Server → Client | `x.interactive_media.session.variables.updated` | 确认变量更新状态 |
| Server → Client | `x.interactive_media.workflow.node_active` | 当前工作流节点变化 |
| Server → Client | `x.interactive_media.workflow.variables.updated` | 工作流变量变化 |
| Server → Client | `x.interactive_media.workflow.event` | 工作流业务事件 |
| Server → Client | `x.interactive_media.workflow.error` | 工作流执行失败 |
| Server → Client | `x.interactive_media.call.handoff_requested` | 助手请求人工接管 |
| Server → Client | `x.interactive_media.call.ended` | 会话结束及原因 |

动态变量更新示例：

```json
{
  "event_id": "evt_variables_001",
  "type": "x.interactive_media.session.variables.update",
  "update_id": "update_001",
  "variables": {
    "user_name": "王先生",
    "region": "上海"
  }
}
```

```json
{
  "event_id": "evt_server_002",
  "type": "x.interactive_media.session.variables.updated",
  "update_id": "update_001",
  "status": "accepted"
}
```

工作流节点事件示例：

```json
{
  "event_id": "evt_server_003",
  "type": "x.interactive_media.workflow.node_active",
  "nodeId": "collect_materials"
}
```

会话结束示例：

```json
{
  "event_id": "evt_server_004",
  "type": "x.interactive_media.call.ended",
  "reason": "assistant_completed"
}
```

`x.interactive_media.call.ended` 发出后，服务端停止产生新回复并正常关闭媒体与信令通道。未启用扩展的客户端只观察到标准回复结束和连接关闭。

## 打断和回复并发

- 默认同一会话只允许一个活动回复。
- 用户开始说话且助手允许打断时，服务端取消当前生成并停止后续音频输出。
- 客户端可显式发送 `response.cancel`。
- WebSocket 客户端自行管理音频播放，因此停止播放后应发送 `conversation.item.truncate`，用 `audio_end_ms` 告诉服务端用户实际听到的位置。
- WebRTC/SIP 的输出音频由服务端缓冲；VAD 打断时由服务端自动截断，显式 Push-to-Talk 打断使用 `output_audio_buffer.clear`。
- 助手配置禁止打断时，服务端继续当前回复，并对不允许的显式取消返回 `error`。

## 错误格式

协议错误使用标准 `error` 事件，不使用只存在于某个传输的自定义错误结构：

```json
{
  "event_id": "evt_server_error_001",
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "code": "session_field_locked",
    "message": "The assistant does not allow overriding instructions.",
    "param": "session.instructions",
    "event_id": "evt_update_001"
  }
}
```

常见错误码包括：

| code | 含义 |
| --- | --- |
| `authentication_failed` | API Key 或临时令牌无效 |
| `assistant_not_found` | 助手不存在、未发布或不可访问 |
| `session_field_locked` | 客户端尝试覆盖服务端锁定配置 |
| `unsupported_event` | 当前协议版本不支持该事件 |
| `capability_not_enabled` | 未协商或无权使用某项扩展 |
| `response_in_progress` | 已有活动回复且当前请求不能并发执行 |
| `tool_result_timeout` | Client 工具未在规定时间内返回 |
| `internal_error` | 运行时出现不可恢复错误 |

错误只终止相关事件；只有鉴权失败、协议严重错误或运行时不可恢复时才关闭整个连接。

## 事件顺序和重连

- WebSocket 文本帧和 `oai-events` DataChannel 都按连接内的发送顺序处理。
- 客户端事件建议携带唯一 `event_id`；服务端错误通过 `error.event_id` 指回原事件。
- `session.created` 是连接建立后的第一个业务事件。
- 每个回复由 `response.created` 开始，以 `response.done` 结束；Delta 必须携带所属的 response、item 和 content 索引。
- `Interactive Media Realtime Extensions v1` 不支持会话断线续传。重连会创建新会话；服务端历史记录仍按平台策略持久化。
- 客户端不得把收到的旧连接事件写入新连接。

## 现有能力映射

| 现有能力或旧事件 | 新协议 |
| --- | --- |
| WebRTC 麦克风与助手音频 | WebRTC audio track |
| 摄像头连续输入 | 可选 WebRTC video track + `video_track` capability |
| `user-input` 文本或图片 | `conversation.item.create` + `response.create` |
| `session-update` | `x.interactive_media.session.variables.update` |
| `transcript` 用户转写 | `conversation.item.input_audio_transcription.*` |
| `assistant-text-start/delta/end` | `response.output_text.*` 或 `response.output_audio_transcript.*` |
| `client-tool-call/result` | 标准 function call + `function_call_output` |
| `node-active` | `x.interactive_media.workflow.node_active` |
| `workflow-variables` | `x.interactive_media.workflow.variables.updated` |
| `workflow-event` | `x.interactive_media.workflow.event` |
| `call-ended` | `x.interactive_media.call.ended` |
| 知识库、服务端工具、工作流执行 | 继续在现有运行时内部完成 |

Pipeline 与 Realtime 两种助手运行模式对客户端使用同一套北向事件。协议适配层负责把不同内部模型事件归一化，客户端不需要根据运行模式切换协议。

原有 RTVI 入口和消息格式继续并行保留，当前前端不需要迁移；新兼容接口面向新的第三方集成。

## 传输选择

| 场景 | 推荐传输 | 原因 |
| --- | --- | --- |
| 浏览器实时语音、打断和摄像头 | WebRTC | 媒体自适应、播放简单、支持视频轨道 |
| 移动端实时语音 | WebRTC | 更适合不稳定网络和双向媒体 |
| 服务端 Agent 或批处理式实时流 | WebSocket | JSON 与音频缓冲可完全由调用方控制 |
| 电话网关 | WebSocket | 易于接入现有 PCM/PCMU 音频桥接 |
| 纯文本 Realtime | WebSocket | 无需建立媒体轨道 |

## 兼容性原则

1. 不修改标准事件的名称和含义。
2. 所有项目扩展使用 `x.interactive_media.*` 命名空间。
3. 扩展必须先协商再发送，普通客户端只看到标准事件。
4. 未支持字段明确报错，不静默伪装为成功。
5. OpenAI Realtime Core 和 Interactive Media 扩展分别进行版本管理。
6. 新协议只替换北向连接层，不改变现有 Pipecat 运行时和助手配置模型。

## 参考

- [OpenAI Realtime API with WebRTC](https://developers.openai.com/api/docs/guides/realtime-webrtc)
- [OpenAI Realtime API with WebSocket](https://developers.openai.com/api/docs/guides/realtime-websocket)
- [OpenAI Realtime conversations](https://developers.openai.com/api/docs/guides/realtime-conversations)
- [OpenAI Realtime Push-to-Talk](https://developers.openai.com/api/docs/guides/realtime-conversations#push-to-talk)
