AI Tools
教程11 分钟2026年5月5日作者:AIGCDev更新于: 2026年5月6日

如何构建低延迟 Voice AI 应用(2026):先用 WebRTC,需要控制权时再加链式音频栈

快速答案

如果你在 2026 年构建浏览器端 Voice AI 应用,默认起点比很多团队想象得简单:

  1. OpenAI Voice agents over WebRTC 做实时对话循环。
  2. 标准 API key 留在服务端,由服务端创建会话或签发临时密钥。
  3. 只有当你需要持久文本记录、说话人标签或下游自动化时,再加入独立的 转录阶段
  4. 只有当音色质量、声音克隆或输出格式控制会改变产品结果时,再加入 ElevenLabs 这类 TTS 层

这是 OpenAI 在 2026-05-04 发布的 low-latency voice AI at scale 工程文章、当前 Voice agents 文档和 Realtime WebRTC 文档共同指向的实用结论。那篇工程文章讲的是 OpenAI 内部的 relay 与 transceiver 设计,但对产品团队来说,更直接的结论是:如果做 1:1 实时语音产品,先走直接实时音频路径。不要一开始就搭 speech-to-text -> text model -> text-to-speech 三段式栈,除非你已经清楚为什么需要中间文本控制权。

适合谁阅读

这篇适合正在构建以下产品的团队:

  • 浏览器端客服或销售语音助手
  • AI tutor、面试教练、口语练习伙伴
  • 需要打断和轮次控制的语音化内部工具
  • 需要快速语音回复的移动端或 Web 应用

它不太适合批量转录、会议归档或异步媒体工作流。那些通常是文本优先系统,只是附带音频输入。

先选实时音频还是链式管线

OpenAI 当前 Voice agents 文档 把问题拆成两种有效架构。

架构 适合 得到什么 放弃什么
Speech-to-speech live session 实时浏览器或 App 对话 更低交互摩擦、原生打断、更少组件 对每个中间文本步骤的显式控制较少
Chained audio pipeline 客服流程、审批、重转录系统、已有文本 agent 持久转录、确定性路由、更容易加策略检查点 更高延迟、更多编排、更多失败点

常见错误是默认把链式栈当成“更专业”的架构。在很多产品里,它只是更慢的架构。

一个可执行的路由规则是:

  • 用户直接和模型说话时,选 Realtime + WebRTC
  • 应用需要在轮次之间检查、存储、审批、转换或分支文本时,选 transcription + text workflow + TTS

短版本:产品承诺是“自然对话”,就先用实时音频。产品承诺是“严格流程”,就先用文本控制。

OpenAI 2026-05-04 WebRTC 更新

这篇文章有真实新闻触发点,但不是新闻改写。

2026-05-04,OpenAI 发布了一篇工程文章,解释它如何重建 WebRTC stack 来支撑大规模低延迟语音。那篇文章描述了三个对外部产品团队也有意义的生产问题:

  • 连接建立速度
  • 低且稳定的 media round-trip time
  • 让第一跳路由尽量靠近用户

这些基础设施细节确认了一个产品层面的事实:低延迟语音首先是架构决策,然后才是 prompt 问题。

加阶段前先测延迟

在加入转录、路由或第二个 TTS provider 前,先做一次测量。否则你无法判断额外阶段到底改善了产品,还是只是让架构图看起来更稳。

至少在用户真实会使用的网络上跑 20 次相同测试脚本:

信号 在哪里测 为什么重要
session setup time session.connect()pc.createOffer() 前开始,到连接就绪后结束 暴露会话创建慢和第一跳路由问题
time to first assistant audio 用户轮次提交 -> 第一个远端音频帧或 playing event 最接近“应用是否响应快”的产品指标
media RTT、jitter、packet loss WebRTC 路径上的 RTCPeerConnection.getStats() 区分模型延迟和网络质量问题
barge-in repair 用户打断 -> assistant 输出取消或修复 验证真实语音下的轮次控制
fallback rate 统计切到文字或异步跟进的会话比例 判断实时语音是否足够可靠

Voice agents 路径先记录 setup time、first assistant audio、interruption repair 和 fallback rate。如果这些数字不足以定位问题,再做一个 raw WebRTC 测试版本,直接采样 WebRTC stats:

const startedAt = performance.now();
await pc.setRemoteDescription(answer);
console.log("webrtc_setup_ms", performance.now() - startedAt);

setInterval(async () => {
  const stats = await pc.getStats();

  for (const report of stats.values()) {
    if (report.type === "candidate-pair" && report.state === "succeeded") {
      console.log("current_rtt_s", report.currentRoundTripTime);
    }

    if (report.type === "inbound-rtp" && report.kind === "audio") {
      console.log("audio_jitter_s", report.jitter);
      console.log("packets_lost", report.packetsLost);
    }
  }
}, 5000);

改架构前,先写下自己的通过/失败阈值。公司 Wi-Fi 下的客服 bot 和公共交通上的移动 tutor,不该使用同一套延迟预算。

最快且安全的起点

Step 1:先选一个窄实时场景

不要从通用“voice agent platform”开始。

先选一个对话形态,并定义清楚成功条件,例如:

  • 基于固定知识库回答产品问题
  • 在转人工前收集 intake 信息
  • 做模拟面试问题和语音反馈
  • 引导用户完成 App 内的一个流程

如果你不能用一段话定义什么是好的语音回答,当前问题还不是音频栈。

Step 2:先用 Voice agents,需要控制权时再降到 raw WebRTC

OpenAI 当前文档把浏览器推荐路径分成两层:

  • 对浏览器 speech-to-speech 应用,Voice agents 是最快的受支持起点
  • 当你需要更底层的传输或 session 控制时,再使用 raw Realtime + WebRTC 路径

要把两条路径分清楚。“Start with WebRTC” 是架构选择,不一定等于一开始手写 RTCPeerConnection

最高层的浏览器路径是独立 SDK 路径。一个最小 npm 应用可以先安装 Agents SDK,并在后端暴露一个很小的临时密钥接口:

npm install @openai/agents express
// server
import express from "express";

const app = express();

app.post("/realtime-client-secret", async (_req, res) => {
  const response = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      session: {
        type: "realtime",
        model: "gpt-realtime-1.5",
        audio: { output: { voice: "marin" } },
      },
    }),
  });

  if (!response.ok) {
    res.status(response.status).send(await response.text());
    return;
  }

  res.json(await response.json());
});

app.listen(3000);

然后浏览器用 RealtimeSession 连接:

import { RealtimeAgent, RealtimeSession } from "@openai/agents/realtime";

const agent = new RealtimeAgent({
  name: "Assistant",
  instructions: "You are a helpful voice assistant.",
});

const session = new RealtimeSession(agent, {
  model: "gpt-realtime-1.5",
});

const tokenResponse = await fetch("/realtime-client-secret", {
  method: "POST",
});
const { value: ephemeralKey } = await tokenResponse.json();

await session.connect({
  apiKey: ephemeralKey,
});

预期结果:浏览器请求麦克风权限,session.connect() resolve,远端音频能说回话,而且标准 OpenAI API key 不会出现在浏览器代码里。

如果你需要更底层路径,OpenAI 当前 Realtime WebRTC 文档建议客户端实时连接优先用 WebRTC 而不是 WebSockets。文档同时展示了两种服务端辅助模式:

  • 服务端把 SDP POST 到 https://api.openai.com/v1/realtime/calls 创建 session
  • 或者从 https://api.openai.com/v1/realtime/client_secrets 签发临时 client secret

无论哪种方式,标准 API key 都留在后端。不要把下面的 /session route 和 RealtimeSession 混用;下一段是 raw WebRTC unified-interface 路径,适合你想直接掌控 RTCPeerConnection 的场景。

最小浏览器流程如下:

// browser client
const pc = new RTCPeerConnection();
const remoteAudio = document.createElement("audio");
remoteAudio.autoplay = true;

pc.ontrack = (event) => {
  remoteAudio.srcObject = event.streams[0];
};

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

const events = pc.createDataChannel("oai-events");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const sdpResponse = await fetch("/session", {
  method: "POST",
  body: offer.sdp,
  headers: { "Content-Type": "application/sdp" },
});

await pc.setRemoteDescription({
  type: "answer",
  sdp: await sdpResponse.text(),
});

服务端保持同样窄:

import express from "express";

const app = express();
app.use(express.text({ type: ["application/sdp", "text/plain"] }));

const sessionConfig = JSON.stringify({
  type: "realtime",
  model: "gpt-realtime",
  audio: { output: { voice: "marin" } },
});

app.post("/session", async (req, res) => {
  const form = new FormData();
  form.set("sdp", req.body);
  form.set("session", sessionConfig);

  const response = await fetch("https://api.openai.com/v1/realtime/calls", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: form,
  });

  res.send(await response.text());
});

这已经足够验证你的产品是否真的需要实时语音。

Step 3:只有当业务逻辑需要文本时才加转录

当你需要以下内容时,转录阶段才值得加入:

  • 可搜索的通话记录
  • 可供复核的说话人感知记录
  • 通话后的 CRM 或 ticket 更新
  • 实时 session 外的 moderation 或审批逻辑
  • 分析用户问了什么、流程在哪里失败

如果你需要的是 live captions 或转录 sidecar,OpenAI 现在也支持通过 WebRTC 或 WebSockets 使用 Realtime transcription sessions。低延迟纯转录流程用这条路径。/v1/audio/transcriptions 保留给完成后的录音、通话后处理,或你明确需要 gpt-4o-transcribe-diarize 的情况;它仍不支持 Realtime API。

OpenAI 当前 speech-to-text 文档给出几个容易围绕设计的具体约束:

  • 上传音频文件限制为 25 MB
  • 接受格式包括 mp3mp4mpegmpgam4awavwebm
  • gpt-4o-transcribe-diarize 可用于 /v1/audio/transcriptions,但 尚不支持 Realtime API
  • whisper-1 仍支持 verbose_json 和 word timestamps

如果你的应用会录制通话再做后处理,让这条路径保持文本优先且简单。

from openai import OpenAI

client = OpenAI()

with open("call.wav", "rb") as audio_file:
    transcript = client.audio.transcriptions.create(
        model="gpt-4o-transcribe-diarize",
        file=audio_file,
        response_format="diarized_json",
        chunking_strategy="auto",
    )

for segment in transcript.segments:
    print(segment.speaker, segment.text, segment.start, segment.end)

这个模式适合实时 session 结束之后。它通常不是用户说话时最先需要的东西。

Step 4:只有当音质改变产品结果时才加独立 TTS 层

很多应用不需要第二个语音 provider。如果 OpenAI 内置 voice 已经足够,就停在这里。

只有当你需要以下能力时,再加入 ElevenLabs:

  • 品牌化或克隆声音
  • 下游系统需要特定输出格式
  • 内容或产品团队需要直接控制 voice library
  • 不改变推理模型的情况下独立调语音输出

ElevenLabs 当前 API 仍然让集成保持很小。对于低延迟输出层,从服务端使用 streaming TTS,不要等完整文件生成完:

const voiceId = "JBFqnCBsd6RMkjVDRZzb";

const response = await fetch(
  `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream?output_format=mp3_44100_128`,
  {
    method: "POST",
    headers: {
      "xi-api-key": process.env.ELEVENLABS_API_KEY ?? "",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model_id: "eleven_flash_v2_5",
      text: "Your return has been approved. I just sent the label to your email.",
    }),
  }
);

if (!response.ok || !response.body) {
  throw new Error(await response.text());
}

const audioBody = response.body;
// Pipe audioBody to your web client, telephony layer, or audio playback path.

两个 ElevenLabs 细节会影响实现:

  • 延迟比离线渲染更重要时,使用 streaming endpointFlash model;截至 2026-05-06,ElevenLabs 将 eleven_flash_v2_5 描述为面向实时场景优化,模型延迟约 75 ms,不含应用和网络延迟
  • optimize_streaming_latency 仍出现在 TTS API reference,但已标记为 Deprecated,不要围绕它写新的推荐方案
  • mp3_44100_192 至少需要 Creator tier,pcm_44100wav_44100 至少需要 Pro tier

把 TTS 当成后置阶段。一旦加入第二个音频 provider,你就继承了另一套价格模型、另一层失败面,以及另一组格式约束。

简单决策表

加更多基础设施前,先用这张表检查。

产品形态 从这里开始 需要时再加
浏览器 voice tutor OpenAI Realtime over WebRTC 每次 session 后导出 transcript
带 QA 复核的客服语音 bot 带 transcript storage 的 chained pipeline 品牌 voice 重要时加入 ElevenLabs
内部语音填表工具 用 Realtime 做实时交换 用 diarized transcript 做 audit trail
AI meeting recap tool transcription pipeline 优先 可选播放才需要 TTS
有标志性声音的 consumer companion app Realtime 负责 turn-taking ElevenLabs 做品牌输出层

规则很简单:用能满足需求的最少阶段。

最有用的防护栏

低延迟语音应用通常先因为运营原因失败,而不是先因为模型原因失败。

尽早设置这些防护栏:

保持 voice instructions 短

长 system prompt 会增加时间成本,也让打断更难推理。实时助手可以先用这样紧凑的 instruction block:

You are a product support voice assistant.
Answer in short spoken sentences.
If the user asks for account actions, collect the minimum details first.
If you are uncertain, say what you need next instead of guessing.
Do not invent policies or order status.

控制 session 增长和转录成本

OpenAI 当前 Realtime cost 文档明确了三个生产约束:

  • 每次新 response 都会复用累积会话,所以成本会逐轮增长
  • 启用 input transcription 时会单独计费
  • session history 足够稳定时,prompt caching 效果最好

实际做法是:给长 session 设 token window,总结或删除陈旧轮次,除非必要不要在 session 中途修改 instructions。低延迟不只是传输速度,也包括让 session 小到每轮都便宜且可预测。

决定 transcript 何时持久化

如果什么都不存,debug 会很痛苦。

如果默认全存,隐私审查会很痛苦。

提前定义一个规则:

  • 只存通话后的 transcript
  • 或者只存 error cases
  • 或者只存用户批准的 transcript

明确处理 barge-in 和修复

用户会打断语音系统。他们会重说句子,也会半路改变主意。

你的应用至少要支持这些 repair moves:

  • 取消上一条回答
  • 让用户干净地重述问题
  • 音频质量差时回退到文本输入
  • 语音轮次结束后用文本重放最终答案

给坏网络准备 fallback

OpenAI 的 2026-05-04 文章提醒了一点:网络质量就是产品质量的一部分。你仍然需要应用层备用路径。

一个实用 fallback ladder 是:

  1. 正常路径用 Realtime over WebRTC
  2. 实时音频变差时,切到 text transcript + typed reply
  3. 如果任务无法实时完成,转成异步 follow-up

不要做什么

避免这三个常见错误:

不要因为链式栈看起来更安全就从全链式开始

你会把更多时间花在协调阶段上,而不是验证用户是否真的需要语音界面。

不要把标准 API key 放进浏览器

OpenAI 当前 WebRTC 设置仍然通过你的后端创建 session 或签发 ephemeral secret。保持这个边界。

不要在产品需要高级 TTS 前就付费

更好的声音只有在影响 conversion、retention、task completion 或品牌感知时才有价值。如果没有,额外阶段只是延迟加成本。

推荐上线顺序

如果你需要一个实用实现顺序,用这个:

  1. 先上线一个基于 WebRTC 的实时流程
  2. 测量用户是否完成语音任务
  3. 为复核或自动化加入 transcript storage
  4. 只有当音质或格式控制成为 blocker 时,再加入第二个 TTS 层
  5. 核心对话可用后,再加更深的 analytics 和 routing

这个顺序让系统保持可 debug。

FAQ

应该从 speech-to-speech 还是 chained audio pipeline 开始?

如果产品是实时浏览器对话,从 speech-to-speech over WebRTC 开始。如果你需要持久 transcript、审批步骤,或在语音输入和语音输出之间加入确定性逻辑,从 chained pipeline 开始。

已经使用 realtime voice model 还需要 Whisper 吗?

不一定。Realtime model 可以直接处理实时音频。当你需要可搜索 transcript、说话人感知记录,或 live session 外的通话后处理时,再加转录阶段。

什么时候值得把 ElevenLabs 加进栈?

当 voice 本身是产品需求时,例如品牌 narration、cloned voices 或下游系统需要特定输出格式,再加入 ElevenLabs。如果内置 model voice 对任务已经够用,就跳过它。

可以为了更快接入把 API key 放在浏览器里吗?

不可以。OpenAI 当前 WebRTC 文档仍然要求标准 API key 先经过你的服务端,用来创建 realtime call 或签发 ephemeral client secret。

核验说明

截至 2026-05-06,已用官方来源核验:

核验项:

  • OpenAI 当前推荐浏览器 speech-to-speech 从 Voice agents 开始,并且客户端 realtime voice 优先使用 WebRTC 而不是 WebSockets
  • OpenAI Realtime 的服务端 session 创建 endpoint
  • Realtime transcription-only 与 /v1/audio/transcriptions 的支持边界
  • speech-to-text 上传限制、接受格式和 diarization 可用性
  • Realtime session 成本增长、独立转录计费,以及当前 truncation/caching 指引
  • ElevenLabs streaming TTS endpoint、Flash model 定位和 optimize_streaming_latency 的 deprecated 状态
  • ElevenLabs 高质量输出格式的套餐要求
voice-aiai-agentsopenai