Courses
将完成的音频文件上传到转写端点是这个问题的简单版本。您等待整个文件上传完成,然后得到一份转写文本。模型工作时没有人盯着屏幕看。直播字幕是另一回事:当您还在决定如何处理手头内容时,音频仍在不断到来,而文本必须在讲话者还在说话时持续更新。
这正是 gpt-live-transcribe 要填补的空白。OpenAI 于2026 年 7 月 28 日发布,同时上线了其批处理对应模型 gpt-transcribe。本教程将围绕它构建一个 Python 字幕客户端,并进行三个测试:基础流式客户端、可用上下文提示的比较、以及五档延迟设置的基准测试。我用干净的英语、技术词汇,以及埃及阿拉伯语与英语的语码混用进行测试,因为这更接近真实会议,而非单个清晰的旁白。
到最后,您将拥有一个可用的字幕应用,了解哪些上下文设置确实有效,并能有理有据地选择延迟设置,而不是凭感觉。
什么是 GPT Live Transcribe?
gpt-live-transcribe 是为需要在音频仍在到达时获取转写文本的应用而设计的流式语音转文本模型。它只负责将音频转成文本,其他不做,并可通过四个字段进行调优:delay 控制延迟,prompt 提供自由形式的上下文,keywords 指定字面术语,languages 指定预期输入语言。在 OpenAI 的 Context Aware ASR 基准上,自由文本上下文将语义准确率从 38.5% 提升至 44.6%,这也是为什么有测试 2。

该模型运行在 Realtime API 内部,而不是单独的端点,并且与名字可能让人误解的 GPT-Live(OpenAI 的语音系统)并无关联。您需要开启一个转写会话并进行配置,服务器会通过您发送音频的连接将事件流式返回。这就先要回答一个问题:这两个转写模型里,您究竟需要哪一个。
GPT Live Transcribe 与 GPT Transcribe 的区别
OpenAI 提供两款推荐的转写模型,它们不可互换。gpt-live-transcribe 用于持续到达的音频:麦克风、电话、媒体流,当您需要在讲话者尚未结束前得到部分文本时使用。gpt-transcribe 用于已完成的录音,或在 Realtime 会话中您有意等待一个确定的轮次。文档将后者称为一种专门化工作流,而不是获取实时增量文本的方式。
有一个差异很容易踩坑:gpt-transcribe 会返回一个包含检测到的输入语言的 languages 数组,而 gpt-live-transcribe 不会。如果您的逻辑要基于检测到的语言进行分支,那您就选错了模型,不管它的演示字幕看起来多么好。定价也大致沿着这条线分开,批处理模型的价格约为前者的四分之一,这一点我稍后会再提。
gpt-live-transcribe 不会返回什么
我更愿意现在就告诉您这些,免得您围绕它搭了一半应用却发现不满足需求。它没有词级时间戳、没有说话人标签、没有置信度分数、也没有分离说话人。如果您需要字幕时间轴、需要标注谁在说话、或需要置信度阈值,OpenAI 的指南建议使用 gpt-4o-transcribe-diarize 或 whisper-1。
在 Python 中设置 GPT Live Transcribe
本教程中的每个脚本都在 github.com/KhalidAbdelaty/gpt-live-transcribe,先克隆它。您需要 Python 3.10 或更高版本,以及具备 Realtime 访问权限的 API 密钥。脚本依赖四个核心包:websockets 用于连接,sounddevice 用于采集麦克风音频,numpy 用于缓冲区转换,python-dotenv 用于加载密钥。依赖文件还为图表和浏览器演示添加了几个包。
git clone https://github.com/KhalidAbdelaty/gpt-live-transcribe.git
cd gpt-live-transcribe
pip install -r requirements.txt
在 macOS 上,sounddevice 需要在系统层安装 PortAudio(brew install portaudio);Linux 上需要 apt-get install portaudio19-dev。如果您使用 Windows,可跳过这一步。我本人在 macOS 上遇到了这一问题,修复确实只需这一个安装命令。
音频本身必须以 24 kHz、16 位 PCM、单声道、小端序的形式到达,并进行 base64 编码。如果发送 MP3 或立体声 WAV,要么输出乱码,要么连接被关闭,且不会有消息提示格式错误。这一个坑能白白耗掉您一个下午。接下来要决定的是,使用哪种连接承载音频。
选择 WebSocket 还是 WebRTC
OpenAI 的建议很明确:服务器到服务器的应用用 WebSocket,浏览器和移动端客户端用 WebRTC。本教程构建的是读取本地麦克风的 Python 后端,因此选择 WebSocket 更合适,而且标准的 API 密钥也足够,因为它不会离开您的服务器。
理解会话与事件流程
会话以 session.update 事件开始,设置 type: "transcription" 并选择 gpt-live-transcribe 作为模型。负载中的其他内容描述您即将发送的音频。以下为 Realtime 转写指南中的最小配置:
session_config = {
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": "gpt-live-transcribe"},
"turn_detection": None,
}
},
},
}
turn_detection: None 会禁用自动语音活动检测,因此在您明确提交之前,任何内容都不会最终确定。随后有三个客户端事件执行主要工作:input_audio_buffer.append 发送一个 base64 音频块,input_audio_buffer.commit 结束一个轮次,服务器则通过 conversation.item.input_audio_transcription.delta(部分文本)和 conversation.item.input_audio_transcription.completed(最终文本)进行回应。我连接到 wss://api.openai.com/v1/realtime?intent=transcription,这个模式来自 OpenAI 的 cookbook;指南并未记录该查询串,如果有一天失效,请去掉它。

Realtime 转写会话事件流程图。作者供图。
构建基础的实时转写客户端
测试 1 是可工作的最小版本:采集麦克风音频、进行流式传输、并在文本到达时打印部分与最终文本。不设置上下文、不设关键词、不做任何调优,以便事件流更清晰。第一个问题是如何在不阻塞麦克风线程的情况下取出音频。
流式发送麦克风音频
sounddevice 在其自有线程中运行回调,且必须在几毫秒内返回,否则驱动会丢帧,因此它不能等待网络调用。它唯一的工作是将 float32 缓冲区转换为 PCM16,并通过 asyncio.Queue 配合 loop.call_soon_threadsafe 将其放入队列,另一个协程则不断从该队列取出并发送每个音频块。
def callback(indata, frames, time_info, status):
pcm16 = (indata[:, 0] * 32767).astype(np.int16).tobytes()
loop.call_soon_threadsafe(queue.put_nowait, pcm16)
stream = sd.InputStream(samplerate=24000, channels=1, dtype="float32",
blocksize=2400, callback=callback)
100 毫秒一块(24 kHz 时为 2,400 个采样)是个合理的起点。更小会增加每条消息的开销,更大则会让字幕变得明显滞后。没有文档规定的“正确数值”,把它当作一个可以微调的旋钮即可。
处理部分与最终转写
增量文本既便宜又频繁。每个增量都带有一个绑定到 item_id 的文本片段。将其追加到该项已有的部分文本上,字幕就会在屏幕上逐字增长:
if event["type"] == "conversation.item.input_audio_transcription.delta":
item_id = event["item_id"]
partials[item_id] = partials.get(item_id, "") + event["delta"]
print(f"\r[partial] {partials[item_id]}", end="")
completed 事件会用同一项的最终转写替换该部分文本。将 completed 视为真值来源,而将增量视为预览,不要自己拼接。
用 item_id 管理转写状态
有个细节很关键,忽略它会让您的 UI 出问题:OpenAI 的指南指出,不同轮次的完成事件之间不保证顺序。较早轮次的 completed 事件可能在较晚轮次之后到达,因此假设“最新的 completed 就对应最新的轮次”的代码,偶尔会倒退或重复一行。改用 item_id 进行一切关联,我的 TranscriptState 类就是这样做的。
在构建过程中我踩了两个 bug,都是检查了不该检查的字典。只在增量处理器里把 item_id 追加到顺序列表,导致任何只处理 completed 事件的接收方,调用 full_transcript() 时结果为空。更糟的是用 partials 字典判断某项是否为新项: apply_completed() 会清掉该条目,因此迟到的增量看起来像是全新项,又被第二次加进顺序列表,导致已完成的轮次被打印两遍。请在两个处理器中都跟踪 item_id,并改为检查顺序列表。

实时部分字幕最终转为转写文本。作者供图。
面对干净的英语,文本会在一两秒内出现,并且与我所说基本一致,标点也包含在内。不过通过笔记本麦克风、在未设置上下文的情况下,模型对个别不确定的词偶尔会用完全不同的文字体系返回。此时就需要用 languages 字段,测试 2 正是要解决它。
用上下文与关键词提升准确率
模型接受三种上下文提示,测试前先说清楚它们分别是什么。prompt 是描述场景的自由文本,keywords 是音频中可能包含的字面术语,languages 列出预期的输入语言,格式为 ISO 639-1 代码,如 en 或 ar。它们都不会强制产出某个结果。未被说出的关键词不会仅因被列出而出现在结果中,而了解这些字段到底在做什么的唯一方法,就是一次只改变一个变量。
测试 prompt、keywords 与语言提示
我用五种配置对同一段音频进行了三次各自的跑分。两条规则能让这样的对比更可靠:每次只改变一个上下文字段;每种配置至少跑不止一遍,因为在相同音频上模型并非完全确定性。
RUNS = {
"no_context": TranscriptionConfig(delay="low"),
"prompt_only": TranscriptionConfig(delay="low", prompt=PROMPT),
"keywords_only": TranscriptionConfig(delay="low", keywords=KEYWORDS),
"languages_only": TranscriptionConfig(delay="low", languages=["en"]),
"prompt_and_keywords": TranscriptionConfig(
delay="low", prompt=PROMPT, keywords=KEYWORDS,
),
}
我最初的版本只在“组合运行”里设置了 languages,这违反了第一条规则:一次改了两个字段,因此那次差异可能来自任意一个。还有一个格式规则也让我吃了“会话更新被拒”的亏:关键词中如果包含 <、>、回车或换行,会导致整个更新被拒,而不仅仅是该关键词。TranscriptionConfig.validate_keywords() 会在构建负载前拦住这种情况。
上下文能修正什么,无法修正什么
关键词在意料之中的场景下确实有帮助。每次运行都会按说话内容将账号编号转写为单词,因为音频里本来就是这么说的。问题在于,模型会不会把那些词聚合成一个标识符,还是会拼成“ A C forty-two(A C 四十二)”。
在 15 次运行中,分野非常明显。没有 keywords 的配置从未做过聚合:no_context、prompt_only 和 languages_only 在它们各自的 9 次运行里全部返回“ A C forty-two”。包含 keywords 的运行有 6 次中的 5 次将其聚合,多数情况下是完整格式的“AC-42”。因此,keywords 能改变结果,而单独的 prompt 并不能,这与 OpenAI 对 keywords 的定位一致:它是针对模型可能误解析的字面术语的字段。仅用关键词仍有一次没命中,因此应将其视为大幅提升概率的提示,而非模型必然遵循的规则。
这与我开头引用的基准数据略显不适:自由文本上下文让语义准确率提升了 6 个点。两组测试衡量的东西不同:OpenAI 的评分关注的是广泛音频上的“语义”,而我观察的是一段音频里的一个标识符。一个 prompt 可能确实在句子层面发挥了作用,却没有触及到您恰好在检查的那个细节。本文唯一的迹象是:当 prompt 和 keywords 同时存在时,每次都能聚合;而仅 keywords 的情况下错了一次,差别只有一次运行。

只有关键词将口述标识符聚合。作者供图。
语言提示的帮助更为明显,并且发生在一个我未曾预料的问题上。没有提示时,针对语码混用的片段,模型总把开头的阿拉伯语填充词(大致相当于“tayyib”)听成英语的“But”,并把两种书写体系糊成一个破碎的词。在一些运行中,它还会把“billing statement”按发音用阿拉伯文拼写出来,而另一些运行则保留拉丁字母。加入 languages: ["ar", "en"] 后,每次都消除了那个破碎词。当然这只是一个片段,而且句子进行到一半换语言,是提示最容易起作用的场景。
为五档延迟做基准测试
delay 有五个取值: minimal、low、medium、high 和 xhigh。较低的设置能更快产出部分文本;较高的设置会在提交文本前为模型提供更多音频上下文,从而可能在较难的音频上提高准确率。OpenAI 明确表示,精确时序会因配置而异,应使用具有代表性的音频进行基准测试,这正是测试 3 的目的。
运行基准测试
test3_delay_benchmark.py 将同一段 WAV 音频流式发送到五个延迟层级,每个层级多次运行,记录从流开始到首个增量文本、以及到最终转写的用时。保持音频、上下文字段和提交策略完全一致,才能让比较有意义。
async def benchmark_once(delay: str, wav_path: str) -> dict:
config = TranscriptionConfig(delay=delay)
# ...connect, send session_config, stream the file, time the events...
return {
"delay": delay,
"time_to_first_delta_s": first_delta_at - start,
"time_to_final_s": final_at - start,
"delta_event_count": delta_count,
}
结果显示了什么
这些数字并非通用结论。它们来自同一片段、同一网络、同一下午里,每个层级 3 次运行。首个部分文本的中位用时从 minimal 的 0.70 秒,到 xhigh 的 2.91 秒,且通过 low(1.19s)、medium(1.39s)和 high(2.09s)呈现均匀递增。每个层级的三次运行彼此相差约 0.2 秒,因此排序是稳定的,尽管具体数值是我的,不一定是您的。

延迟档位以速度换取准确率。作者供图。
我没能证实一个常见假设:更高延迟意味着更少的修订。所有层级的增量事件计数都在 84 至 86 之间,几乎一致,看不出趋势。最终用时也都在 30.6 秒左右的半秒范围内,但那反映的是我的提交时机,而非模型本身。这也是图表将两项指标分成两个面板的原因:在一个坐标轴上,2 秒的差距会淹没在高出 10 倍的柱形之下。
为您的场景选择延迟
对于需要在说话同时阅读的实时字幕,从 low 起步。与其让人感觉等了两秒才出字,不如让字幕稍后被修正一下。对于不会立刻阅读的会议笔记,high 或 xhigh 几乎没有成本。对语音指令,更倾向 medium,因为两个词的指令里一个词错了,影响会更大。
处理轮次检测与音频提交
目前为止的每个测试都用的是 turn_detection: null 与手动提交。Realtime API 还提供语音活动检测作为替代方案,于是我把它接到了 gpt-live-transcribe 上进行验证,而不是想当然地认为可用。最初测试失败时我差点删掉本节,后来发现失败本身就是发现。
手动提交 vs. 语音活动检测
server_vad 会按静音段切分音频,并可通过 threshold、prefix_padding_ms 和 silence_duration_ms 进行配置;semantic_vad 使用分类器来估计说话者是否听起来像已说完,并可通过 eagerness 控制其决策速度:
"turn_detection": {"type": "semantic_vad", "eagerness": "auto"}
这是两种模式在通用 Realtime API 中的文档说明。若将完全相同的负载发送到 gpt-live-transcribe 会话,会被拒绝:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"code": "invalid_value",
"message": "Turn detection is not supported for this transcription model.",
"param": "session.audio.input.turn_detection"
}
}
server_vad 产生同样的错误。截至 2026 年 8 月 4 日,gpt-live-transcribe 仅接受手动提交这一种轮次检测模式,尽管转写指南仍然建议配置语音活动检测以便服务器替您提交轮次。在以此为基础构建前请再次测试,因为 OpenAI 可能在未通知的情况下后来为该模型启用 VAD。
选择轮次策略
按键说话是最简单的情形,因为按下与松开本身就标记了边界。其他情况都要由客户端决定何时一轮结束,而我前两次尝试都搞错了。
第一次尝试用 if not mic_queue.empty() 作为提交条件,看起来合理但从未触发:负责清空该队列的协程与麦克风写入一样快。部分字幕仍在流式更新,这就是它“显得合理”的原因,但最终文本从未出现。第二次尝试则只要检测到有音频被追加,每 4 秒提交一次。对着真实麦克风时,它会产出:
[final] This is a customer support (item_id=item_E8bCJcvjO9L1KU2zckqOr)
[final] Abort call about the premium plan on account A (item_id=item_E8bCNSu1dmYK380JOr1ro)
[final] (item_id=item_E8bCVgxGSjXTasbrTWE3U)
两种失败同时发生。定时器把句子切在单词中间,模型把一个从空白开头的片段读成了“Abort”。然后它又在我没说话时提交了一次,返回了空转写,因为麦克风不管有没有人在说话都会持续输出音频块。
两者都源于同样的缺失信息:音频能量。mic_stream.py 中的 SpeechGate 会跟踪每个音频块的 RMS 幅度,并在说话者已说出内容且随后安静时进行提交,同时设置了上限,以确保连续说话也能在某处结束。我的第一个版本将该幅度与一个固定数值对比,在一台机器上能用,换到下一台就相差五倍,因此现在它会先估计环境噪声,再把“明显高出数倍”的当作语音。几乎没有音频的轮次结束,比如咳嗽或关门声,会走 input_audio_buffer.clear 而非提交,因为问模型“门刚才说了什么”很容易得到一个凭空的词。
构建完整的实时字幕应用
app.py 把本教程的各个部分整合进一个终端应用:麦克风采集、实时部分字幕、基于 item_id 的转写历史,以及涵盖会话可接收的每个字段的 CLI 参数。
python app.py --delay low --keywords "AC-42,premium plan" --languages en
只列出您实际在说的语言。对纯英语语音使用 en,ar 的同一命令,会把“delta”这个词按发音转成阿拉伯文,这就是测试 2 反向验证的结果。
--turn-detection 默认为 manual,--silence-hold 与 --max-turn 用于调节上一节的“闸门”。VAD 模式仍保留为参数,以防 API 以后开始接受;传入任一选项时,应用会打印服务器的拒绝信息,而不是静默卡住。

带设置运行的完整字幕应用。作者供图。
退出时,它会写出一份纯文本转写以及一份 JSON 文件,其中包含所用配置、本地测得的“首个增量用时”、以及每个已完成轮次及其 item_id。我是在因为终端窗口关闭而丢失了一次很好的测试运行后才加上的导出。里面的时间戳是客户端侧的,所以不要把您的自测当作 OpenAI 的官方数据。
三个测试也都能在浏览器中运行。 demo_app.py 是 Streamlit 版本,每个实验一个标签页。它更像演示,而非主教学路径,因为终端脚本能更直观地展示原始事件。
streamlit run demo_app.py请关注字幕面板而非标签页。青绿色文本是暂时性的,作为 delta 事件到达;一旦 completed 事件将轮次最终确定,它就会变成白色。这种区别就是该模型存在的全部意义,静态图片难以呈现,动态观看一目了然。
GPT Live Transcribe 的定价与时延
gpt-live-transcribe 按实时音频时长计费,每分钟 $0.017,连续流一小时约 $1.02。 gpt-transcribe 为每分钟 $0.0045,约为前者的四分之一。这其实是持续追问“工作流是否真的需要实时增量,还是最终有文本即可”的真正原因。两项价格来自 官方定价页,2026 年 8 月 4 日复核过,且实时定价之前有过变更。
还要区分您花钱买的是什么,与让字幕显得慢的是什么。delay 只是链条中的一环,其他还包括麦克风缓冲、base64 编码、网络往返时延,以及您的 UI 重绘速度。在我的测试中,终端的慢速重绘带来的可见延迟,比编码带来的更明显。
局限与生产环境考量
从演示走向实战,有两点很重要,除了前面提到的缺少时间戳与说话人标签之外:会话时长,以及连接断开时会发生什么。
可靠性与重连
由于 gpt-live-transcribe 仅在 Realtime 转写会话中运行,它继承了该会话硬性的 60 分钟上限。一小时的会议往往在您最不希望的时候刚好触顶,因此要规划轮换:提前几分钟打开新会话,沿用您的上下文配置,并自行拼接转写历史。我没有真坐满一小时去观察会话关闭,所以这点以文档描述为准,而非我亲自做的压力测试。
也要考虑普通的 WebSocket 断连:保留一个有界的本地未发送音频队列,带退避地重连,并重新发送 session.update,因为新连接不会继承先前的任何配置。
隐私与录音同意
这些并非 OpenAI 特有,但字幕工具很容易让人忽视。请告知参与者正在录音,先决定转写文本要保留多久,再去构建存储功能,并且除非确有必要,不要把客户姓名与账号放进 prompt 与 keywords。
常见错误与排障
我遇到的大多数失败都源于音频格式问题,而非模型本身。正式开始怪模型前,做一次简短的诊断能节省大量时间。
-
乱码转写几乎总能追溯到设置部分讲过的音频格式问题,通常是采样率不对、立体声而非单声道、或字节序错误。
-
在空缓冲区上执行
input_audio_buffer.commit会返回错误,而不是转写文本。 -
前面提到的轮次检测被拒,是我在这里最耗时的问题,通用的 VAD 文档没有任何提醒。
-
如果
prompt超过模型的长度限制(OpenAI 未公布具体数值),会话更新也会失败,因此请先缩短 prompt,再去怀疑关键词规则。 -
不支持同时发送旧的单数
language字段与新的languages数组。请只使用languages。 -
字幕重复或错序,说明您信任了到达顺序,而非基于
item_id做对账,正如我之前所说。 -
最终文本迟迟不来、空的
completed事件、以及轮次边界处的无意义词,往往都与您的提交策略有关,如前文所述,而非模型问题。 -
session.updated会回显prompt与languages,但不会回显delay或keywords,因此可发送一个故意非法的值来确认它们是否生效。 -
非拉丁文字的转写可能会在 Windows 终端触发
UnicodeEncodeError。请设置PYTHONIOENCODING=utf-8。 -
input_audio_buffer.append每次事件上限为 15 MiB,正常的音频块大小不会触及该限制。
如果以上都无法解释您遇到的问题,请将麦克风与 API 脱钩:先录制一个短片段,检查其采样率与声道数,确认音频无误后,再去怀疑模型。
最终结论
在三项测试中,gpt-live-transcribe 基本都做到了文档所述:部分文本快速流出、上下文提示按文档描述的方式影响结果、改变 delay 会带来实实在在的时序差异。除了轮次检测的缺口外,还值得强调的一点是:上下文提示只会让某个结果“更可能”,而非“必然”,这是我不再用“每种配置只跑一遍”得出结论之后才变得明显的。
如果今天启动一个项目,我的默认设置会是 delay: "low"(任何有现场受众的场景), keywords 填入我知道会出现的领域术语, languages 只命名我实际在说的语言,并用“停顿”而非“时钟”驱动提交。上文的三条习惯是我在基于该模型构建任何项目时都会坚持的:基于 item_id 做对账、在一小时到期前轮换会话、并用您的真实音频和口音进行测试,而不是只用一个干净片段。
对于类似应用的浏览器端,我们的 gpt-realtime-2 API 教程 更详细地讨论了 WebRTC 与 WebSocket 的取舍。对于基于文件的转写,Audio API 指南 与 Whisper API 教程有更完整的覆盖。
FAQs
gpt-live-transcribe 是否支持英语以外的语言?
可以,通过 languages 提示字段,而且指南还接受 ISO 639-3 代码与区域性的 zh 变体,此外还支持我文中使用的两字母代码。不过它不会告诉您检测到的语言是哪种。该输出只存在于 gpt-transcribe。
我可以用它处理电话音频而不是麦克风吗?
可以。会话除 PCM 外还接受 G.711 μ-law 与 A-law,覆盖标准电话音频,无需转换。只需更改 format 配置块。
如果 WebSocket 在会议中途断开,我的转写会怎样?
已接收的内容不会丢失,因为增量与完成事件都在您的本地转写状态里。您会丢失的是从断连到重连这段期间所说的内容,这也是建议保留最近几秒音频在缓冲中,而不是刚发送就立即丢弃的原因。
gpt-live-transcribe 是 GPT-Live 的一部分吗?
不属于,名字相似很容易误解。GPT-Live 是 OpenAI 的第三代语音系统,是一个全双工模型,能同时听与说,为 ChatGPT Voice 提供支持,其 GPT-Live API 被描述为“即将推出”,尚未发布。gpt-live-transcribe 是一个您今天就能调用的转写模型,无语音回复、无对话。名称相似,分工不同。
这类项目我还该用 Whisper 吗?
对直播流而言,不需要。gpt-live-transcribe 是当前推荐模型,OpenAI 已开始淘汰旧的音频与实时快照,其中数款标注了 2027 年 1 月 20 日的下线日期。若您需要词级时间戳或生成字幕,Whisper 仍然合适。