使用OpenAI API比较语音转文本方法

本教程面向有编程基础的开发者,系统比较了四种OpenAI语音转文本(STT)方法:文件上传阻塞式、文件上传流式、Realtime WebSocket和Agents SDK VoicePipeline。通过对比延迟、适用场景、优势和限制,帮助读者根据实际需求选择合适方案。教程包含完整代码示例、环境配置、核心原理讲解、架构设计分析、常见错误排查及生产环境优化建议,强调从工程实践角度理解各方法的权衡。

技术背景

语音转文本(STT)是构建语音助手、实时字幕、会议记录等应用的基础能力。OpenAI提供了多种STT接口,从简单的文件上传到实时流式处理,每种方法在延迟、复杂度、功能上差异显著。开发者常面临选择困难:是追求低延迟还是简单集成?是处理离线文件还是实时音频流?本教程旨在通过对比分析,帮助开发者理解各方法的适用场景和工程权衡,从而做出合理的技术选型。

核心原理

四种方法的核心原理如下:

  1. 文件上传阻塞式:将完整音频文件通过HTTP POST发送至/audio/transcriptions端点,服务端一次性处理,返回完整转录文本。优点是简单,缺点是必须等待全部处理完成,延迟随音频时长线性增加。
  2. 文件上传流式:同样上传完整文件,但设置stream=True,服务端以SSE(Server-Sent Events)流式返回部分转录结果(delta)和最终结果(text)。客户端可实时显示增量,提升用户体验,但底层仍需完整文件。
  3. Realtime WebSocket:建立WebSocket连接,客户端将音频分块(约128ms)以base64编码的PCM16格式实时发送,服务端通过VAD(语音活动检测)自动识别断句,返回增量转录事件(conversation.item.input_audio_transcription.delta)和完成事件(.completed)。延迟极低(300-800ms),但需处理连接管理、格式转换和会话限制(30分钟)。
  4. Agents SDK VoicePipeline:基于OpenAI Agents SDK,封装了音频输入流、VAD、缓冲、重连等复杂逻辑,开发者只需定义Agent和工作流,即可实现实时转录并集成到智能体对话中。目前仅支持Python测试版。

架构分析

系统架构设计思想:

完整实战教程

概述

本笔记本为初学者提供了一份清晰、实用的指南,帮助他们快速上手使用OpenAI API进行语音转文本(STT)。你将探索多种实用方法、它们的用例和注意事项。

通过本教程,你将能够根据具体需求选择并应用合适的转录方法。

*注意:*

📊 快速概览

| 模式 | 到首个 token 的延迟 | 最适合的场景(实际示例) | 优势 | 主要限制 |

|--------------------------------|---------------------------|--------------------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------|

| 文件上传 + stream=False(阻塞) | 秒级 | 语音信箱、会议录音 | 设置简单 | • 无部分结果,用户需等待文件处理完成才能看到内容 <br>• 每次请求最大 25 MB(长音频需分块) |

| 文件上传 + stream=True | 亚秒级 | 移动应用中的语音备忘录 | 设置简单,通过 token 流提供“实时”体验 | • 仍需完整文件 <br>• 需自行实现进度条 / 分块上传 |

| Realtime WebSocket | 亚秒级 | 网络研讨会中的实时字幕 | 真正的实时;接受连续音频流 | • 音频必须是 pcm16、g711_ulaw 或 g711_alaw <br>• 会话 ≤ 30 分钟,需重连并拼接 <br>• 需自行处理说话人轮次格式以生成完整转录 |

| Agents SDK VoicePipeline | 亚秒级 | 内部帮助台助手 | 实时流式传输,易于构建智能体工作流 | • 仅限 Python 测试版 <br>• API 可能变化 |

安装(一次性)

为了设置你的环境,请在新的 Python 环境中取消注释并运行以下单元格:

!pip install --upgrade -q openai openai-agents websockets sounddevice pyaudio nest_asyncio resampy httpx websocket-client

这会安装跟随笔记本所需的所有必要软件包。

身份验证

在继续之前,请确保您已将 OpenAI API 密钥设置为名为 OPENAI_API_KEY 的环境变量。您通常可以在终端或笔记本环境中设置它:export OPENAI_API_KEY="your-api-key-here"

通过运行下一个单元格来验证您的 API 密钥是否设置正确。

# ─── Standard Library ──────────────────────────────────────────────────────────
import asyncio
import struct
import base64          # encode raw PCM bytes → base64 before sending JSON
import json            # compose/parse WebSocket messages
import os
import time
from typing import List
from pathlib import Path

# ─── Third-Party ───────────────────────────────────────────────────────────────
import nest_asyncio
import numpy as np
from openai import OpenAI
import resampy         # high-quality sample-rate conversion
import soundfile as sf # reads many audio formats into float32 arrays
import websockets      # asyncio-based WebSocket client
from agents import Agent
from agents.voice import (
    SingleAgentVoiceWorkflow,
    StreamedAudioInput,
    VoicePipeline,
    VoicePipelineConfig,
)
from IPython.display import Audio, display
# ───────────────────────────────────────────────────────────────────────────────
nest_asyncio.apply()

# ✏️  Put your key in an env-var or just replace the call below.
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

client = OpenAI(api_key=OPENAI_API_KEY)
print("✅ OpenAI client ready")

1 · 使用音频文件的语音转文本

*model = gpt-4o-transcribe*

使用场景

* 您有一个完整的音频文件(最大 25 MB)。支持以下输入文件类型:mp3、mp4、mpeg、mpga、m4a、wav 和 webm。

* 适用于批量处理任务,如播客、呼叫中心录音或语音备忘录。

* 不需要实时反馈或部分结果。

工作原理

STT Not Streaming Transcription flow#### 优势

局限性

让我们先预览一下音频文件。我已经从这里下载了音频文件。

AUDIO_PATH = Path('./data/sample_audio_files/lotsoftimes-78085.mp3')  # change me
MODEL_NAME = "gpt-4o-transcribe"

if AUDIO_PATH.exists():
    display(Audio(str(AUDIO_PATH)))
else:
    print('⚠️ Provide a valid audio file')

现在,我们可以调用STT端点来转录音频。

if AUDIO_PATH.exists():
    with AUDIO_PATH.open('rb') as f:
        transcript = client.audio.transcriptions.create(
            file=f,
            model=MODEL_NAME,
            response_format='text',
        )
    print('\n--- TRANSCRIPT ---\n')
    print(transcript)

2 · 带音频文件的语音转文字:流式

*model = gpt-4o-transcribe*

使用场景

局限

if AUDIO_PATH.exists():
    with AUDIO_PATH.open('rb') as f:
        stream = client.audio.transcriptions.create(
            file=f,
            model=MODEL_NAME,
            response_format='text',
              stream=True
)

for event in stream:
    # If this is an incremental update, you can get the delta using `event.delta`
    if getattr(event, "delta", None): 
        print(event.delta, end="", flush=True)
        time.sleep(0.05) # simulate real-time pacing
        
    # When transcription is complete, you can get the final transcript using `event.text`
    elif getattr(event, "text", None):
        print()
        print("\n" + event.text)

3 · 实时转录 API

*model = gpt-4o-transcribe*

使用场景

* 实时场景下的实时字幕(例如会议、演示)。

* 需要内置的语音活动检测、噪声抑制或令牌级对数概率。

* 熟悉处理 WebSockets 和实时事件流。

工作原理

Realtime Transcription flow#### 优势

局限性

TARGET_SR     = 24_000
PCM_SCALE     = 32_767
CHUNK_SAMPLES = 3_072                 # ≈128 ms at 24 kHz
RT_URL        = "wss://api.openai.com/v1/realtime?intent=transcription"

EV_DELTA      = "conversation.item.input_audio_transcription.delta"
EV_DONE       = "conversation.item.input_audio_transcription.completed"
# ── helpers ────────────────────────────────────────────────────────────────
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', int(x * 32767)) 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

def load_and_resample(path: str, sr: int = TARGET_SR) -> np.ndarray:
    """Return mono PCM-16 as a NumPy array."""
    data, file_sr = sf.read(path, dtype="float32")
    if data.ndim > 1:
        data = data.mean(axis=1)
    if file_sr != sr:
        data = resampy.resample(data, file_sr, sr)
    return data

async def _send_audio(ws, pcm: np.ndarray, chunk: int, sr: int) -> None:
    """Producer: stream base-64 chunks at real-time pace, then signal EOF."""
    dur = 0.025 # Add pacing to ensure real-time transcription
    t_next = time.monotonic()

    for i in range(0, len(pcm), chunk):
        float_chunk = pcm[i:i + chunk]
        payload = {
            "type":  "input_audio_buffer.append",
            "audio": base64_encode_audio(float_chunk),
        }
        await ws.send(json.dumps(payload))
        t_next += dur
        await asyncio.sleep(max(0, t_next - time.monotonic()))

    await ws.send(json.dumps({"type": "input_audio_buffer.end"}))

async def _recv_transcripts(ws, collected: List[str]) -> None:
    """
    Consumer: build `current` from streaming deltas, promote it to `collected`
    whenever a …completed event arrives, and flush the remainder on socket
    close so no words are lost.
    """
    current: List[str] = []

    try:
        async for msg in ws:
            ev = json.loads(msg)

            typ = ev.get("type")
            if typ == EV_DELTA:
                delta = ev.get("delta")
                if delta:
                    current.append(delta)
                    print(delta, end="", flush=True)
            elif typ == EV_DONE:
                # sentence finished → move to permanent list
                collected.append("".join(current))
                current.clear()
    except websockets.ConnectionClosedOK:
        pass

    # socket closed → flush any remaining partial sentence
    if current:
        collected.append("".join(current))

def _session(model: str, vad: float = 0.5) -> dict:
    return {
        "type": "transcription_session.update",
        "session": {
            "input_audio_format": "pcm16",
            "turn_detection": {"type": "server_vad", "threshold": vad},
            "input_audio_transcription": {"model": model},
        },
    }

async def transcribe_audio_async(
    wav_path,
    api_key,
    *,
    model: str = MODEL_NAME,
    chunk: int = CHUNK_SAMPLES,
) -> str:
    pcm = load_and_resample(wav_path)
    headers = {"Authorization": f"Bearer {api_key}", "OpenAI-Beta": "realtime=v1"}

    async with websockets.connect(RT_URL, additional_headers=headers, max_size=None) as ws:
        await ws.send(json.dumps(_session(model)))

        transcripts: List[str] = []
        await asyncio.gather(
            _send_audio(ws, pcm, chunk, TARGET_SR),
            _recv_transcripts(ws, transcripts),
        )  # returns when server closes

    return " ".join(transcripts)
transcript = await transcribe_audio_async(AUDIO_PATH, OPENAI_API_KEY)
transcript

4 · Agents SDK 实时转录

*模型 = gpt-4o-transcribe, gpt-4o-mini*

使用场景

* 利用 OpenAI Agents SDK 以最少设置实现实时转录和合成。

* 希望将转录直接集成到代理驱动的工作流中。

* 倾向于对音频输入/输出、WebSocket 和缓冲进行高级管理。

工作原理

Agents Transcription flow优势

局限性

# ── 1 · agent that replies in French ---------------------------------------
fr_agent = Agent(
    name="Assistant-FR",
    instructions=
        "Translate the user's words into French.",
    model="gpt-4o-mini",
)

# ── 2 · workflow that PRINTS what it yields --------------------------------
class PrintingWorkflow(SingleAgentVoiceWorkflow):
    """Subclass that prints every chunk it yields (the agent's reply)."""

    async def run(self, transcription: str):
        # Optionally: also print the user transcription
        print()
        print("[User]:", transcription)
        print("[Assistant]: ", end="", flush=True)
        async for chunk in super().run(transcription):
            print(chunk, end="", flush=True)   # <-- agent (French) text
            yield chunk                        # still forward to TTS


pipeline = VoicePipeline(
    workflow=PrintingWorkflow(fr_agent),
    stt_model=MODEL_NAME,
    config=VoicePipelineConfig(tracing_disabled=True),
)

# ── 3 · helper to stream ~40 ms chunks at 24 kHz ---------------------------
def load_and_resample(path: str, sr: int = 24_000) -> np.ndarray:
    """Return mono PCM-16 as a NumPy array."""
    data, file_sr = sf.read(path, dtype="float32")
    if data.ndim > 1:
        data = data.mean(axis=1)
    if file_sr != sr:
        data = resampy.resample(data, file_sr, sr)
    return data
        
def audio_chunks(path: str, target_sr: int = 24_000, chunk_ms: int = 40):
    # 1️⃣ reuse the helper
    audio = load_and_resample(path, target_sr)

    # 2️⃣ float-32 → int16 NumPy array
    pcm = (np.clip(audio, -1, 1) * 32_767).astype(np.int16)

    # 3️⃣ yield real-time sized hops
    hop = int(target_sr * chunk_ms / 1_000)
    for off in range(0, len(pcm), hop):
        yield pcm[off : off + hop]

# ── 4 · stream the file ----------------------------------------------------
async def stream_audio(path: str):
    sai = StreamedAudioInput()
    run_task = asyncio.create_task(pipeline.run(sai))

    for chunk in audio_chunks(path):
        await sai.add_audio(chunk)
        await asyncio.sleep(len(chunk) / 24_000)   # real-time pacing

    # just stop pushing; session ends automatically
    await run_task        # wait for pipeline to finish
await stream_audio(AUDIO_PATH)

结论

在本笔记本中,你探索了多种使用 OpenAI API 和 Agents SDK 将语音转换为文本的方法,从简单的文件上传到完全交互式的实时流式传输。每种工作流在不同场景下都有其优势,因此请选择最符合你产品需求的那一种。

关键要点

后续步骤

  1. 尝试运行笔记本!
  2. 将你选择的工作流集成到你的应用程序中。
  3. 向我们发送反馈!社区见解有助于推动下一轮模型升级。

参考资料

* 探索 Transcriptions API 文档。

* 阅读 Realtime 指南。

* 探索 Agents SDK 参考。

* 探索 Agents SDK 语音管道参考。

常见问题

常见错误及排查:

  1. API密钥未设置:确保环境变量OPENAI_API_KEY已设置,否则客户端初始化失败。
  2. 音频格式不支持:文件上传支持mp3、wav等,但Realtime仅接受PCM16,需用resampy重采样至24kHz。
  3. 文件大小超限:文件上传最大25MB,长音频需分块或使用Realtime。
  4. WebSocket连接失败:检查网络,确保使用正确的URL和认证头(Authorization: Bearer)。
  5. 事件循环冲突:在Jupyter中需调用nest_asyncio.apply()。
  6. 流式处理中未收到增量:确认stream=True,并正确处理事件类型(delta和text)。
  7. Agents SDK版本兼容:API可能变化,确保安装最新版openai-agents。

生产环境优化:

企业应用扩展

进一步学习方向:

总结

本教程面向有编程基础的开发者,系统比较了四种OpenAI语音转文本(STT)方法:文件上传阻塞式、文件上传流式、Realtime WebSocket和Agents SDK VoicePipeline。通过对比延迟、适用场景、优势和限制,帮助读者根据实际需求选择合适方案。教程包含完整代码示例、环境配置、核心原理讲解、架构设计分析、常见错误排查及生产环境优化建议,强调从工程实践角度理解各方法的权衡。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部语音与实时 API教程