将完成的音频文件上传到转写端点是这个问题的简单版本。您等待整个文件上传完成,然后得到一份转写文本。模型工作时没有人盯着屏幕看。直播字幕是另一回事:当您还在决定如何处理手头内容时,音频仍在不断到来,而文本必须在讲话者还在说话时持续更新。
这正是 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)
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技术支持
将完成的音频文件上传到转写端点是这个问题的简单版本。您等待整个文件上传完成,然后得到一份转写文本。模型工作时没有人盯着屏幕看。直播字幕是另一回事:当您还在决定如何处理手头内容时,音频仍在不断到来,而文本必须在讲话者还在说话时持续更新。
这正是
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用于加载密钥。依赖文件还为图表和浏览器演示添加了几个包。在 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 转写指南中的最小配置: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将其放入队列,另一个协程则不断从该队列取出并发送每个音频块。100 毫秒一块(24 kHz 时为 2,400 个采样)是个合理的起点。更小会增加每条消息的开销,更大则会让字幕变得明显滞后。没有文档规定的“正确数值”,把它当作一个可以微调的旋钮即可。
处理部分与最终转写
增量文本既便宜又频繁。每个增量都带有一个绑定到
item_id的文本片段。将其追加到该项已有的部分文本上,字幕就会在屏幕上逐字增长: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 与语言提示
我用五种配置对同一段音频进行了三次各自的跑分。两条规则能让这样的对比更可靠:每次只改变一个上下文字段;每种配置至少跑不止一遍,因为在相同音频上模型并非完全确定性。
我最初的版本只在“组合运行”里设置了
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 音频流式发送到五个延迟层级,每个层级多次运行,记录从流开始到首个增量文本、以及到最终转写的用时。保持音频、上下文字段和提交策略完全一致,才能让比较有意义。结果显示了什么
这些数字并非通用结论。它们来自同一片段、同一网络、同一下午里,每个层级 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控制其决策速度:这是两种模式在通用 Realtime API 中的文档说明。若将完全相同的负载发送到
gpt-live-transcribe会话,会被拒绝:server_vad产生同样的错误。截至 2026 年 8 月 4 日,gpt-live-transcribe仅接受手动提交这一种轮次检测模式,尽管转写指南仍然建议配置语音活动检测以便服务器替您提交轮次。在以此为基础构建前请再次测试,因为 OpenAI 可能在未通知的情况下后来为该模型启用 VAD。选择轮次策略
按键说话是最简单的情形,因为按下与松开本身就标记了边界。其他情况都要由客户端决定何时一轮结束,而我前两次尝试都搞错了。
第一次尝试用
if not mic_queue.empty()作为提交条件,看起来合理但从未触发:负责清空该队列的协程与麦克风写入一样快。部分字幕仍在流式更新,这就是它“显得合理”的原因,但最终文本从未出现。第二次尝试则只要检测到有音频被追加,每 4 秒提交一次。对着真实麦克风时,它会产出:两种失败同时发生。定时器把句子切在单词中间,模型把一个从空白开头的片段读成了“Abort”。然后它又在我没说话时提交了一次,返回了空转写,因为麦克风不管有没有人在说话都会持续输出音频块。
两者都源于同样的缺失信息:音频能量。
mic_stream.py中的SpeechGate会跟踪每个音频块的 RMS 幅度,并在说话者已说出内容且随后安静时进行提交,同时设置了上限,以确保连续说话也能在某处结束。我的第一个版本将该幅度与一个固定数值对比,在一台机器上能用,换到下一台就相差五倍,因此现在它会先估计环境噪声,再把“明显高出数倍”的当作语音。几乎没有音频的轮次结束,比如咳嗽或关门声,会走input_audio_buffer.clear而非提交,因为问模型“门刚才说了什么”很容易得到一个凭空的词。构建完整的实时字幕应用
app.py把本教程的各个部分整合进一个终端应用:麦克风采集、实时部分字幕、基于item_id的转写历史,以及涵盖会话可接收的每个字段的 CLI 参数。只列出您实际在说的语言。对纯英语语音使用
en,ar的同一命令,会把“delta”这个词按发音转成阿拉伯文,这就是测试 2 反向验证的结果。--turn-detection默认为manual,--silence-hold与--max-turn用于调节上一节的“闸门”。VAD 模式仍保留为参数,以防 API 以后开始接受;传入任一选项时,应用会打印服务器的拒绝信息,而不是静默卡住。带设置运行的完整字幕应用。作者供图。
退出时,它会写出一份纯文本转写以及一份 JSON 文件,其中包含所用配置、本地测得的“首个增量用时”、以及每个已完成轮次及其
item_id。我是在因为终端窗口关闭而丢失了一次很好的测试运行后才加上的导出。里面的时间戳是客户端侧的,所以不要把您的自测当作 OpenAI 的官方数据。三个测试也都能在浏览器中运行。
demo_app.py是 Streamlit 版本,每个实验一个标签页。它更像演示,而非主教学路径,因为终端脚本能更直观地展示原始事件。