跳至内容

Grok Voice Transcribe 2.0 API 教程:构建实时客服通话转写器

学习如何使用 Grok Voice Transcribe 2.0 API 在 Python 中构建实时客服通话转写器,并加入说话人分离、Smart Turn 与 8 kHz 电话音频支持。
已更新 2026年10月5日  · 12分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

SpaceXAI 的 Grok Voice Transcribe 2.0 是一款语音转文字模型。在本教程中,您将通过 REST 发送录音,通过 WebSocket 发送实时音频。API 返回文本、逐词时间戳、可选的说话人 ID,以及轮次结束事件;它不会替来电者作答。

客服通话比单一清晰旁白更复杂:有短停顿、不熟悉的姓名、多位说话人,以及在 8 kHz 线路上报读的联系方式。我们以名为 Qivora Sync 的项目为主线:客户反馈文件同步失败,坐席收集联系信息,随后有升级工程师加入。同一个 Python 客户端先处理录音,再处理实时音频。

如需语音对语音的场景(模型直接回答来电者),请参见我们的 Grok Voice Think Fast 2.0 教程。本教程相关代码见GitHub 仓库。

TL;DR

时间紧?下面是通话演示要点。

  • POST /v1/stt 处理录音音频,wss://api.x.ai/v1/stt 处理实时音频,且共享说话人分离、关键词、语气填充词与音频处理等控制项。

  • 关键词矫正了被“编造”的产品名,但在实时说话人检测中,强词汇偏置会将微弱回声拉向该关键词。

  • 在干净混音中,说话人标签稳定;在 8 kHz 下则不可靠。

  • 阿拉伯语切换保持阿拉伯文脚本,format=true 修正了电话号码,但对邮箱只修正了一半。

  • 在号码中段的长停顿上,Smart Turn 跨过了所有测试阈值,仅靠阈值调整并不足够。

什么是 Grok Voice Transcribe 2.0?

Grok Voice Transcribe 2.0(grok-voice-transcribe-2.0)是 SpaceXAI 的语音转文字模型。REST 路径用于转写完整文件,WebSocket 路径用于处理实时音频。

SpaceXAI 的 Grok Voice Transcribe 2.0 公告重点介绍了电话通话、多说话人、凭据以及多语言语音。基准对比请参见我们的 Grok Voice Transcribe 2.0 概览。

构建实时客服通话转写器

我们使用受控的 Qivora Sync 固定场景,让音频与 API 设置变化时场景不变。通话包含一个虚构产品名、语气填充词、语言切换、口述联系方式、口述过程中的停顿,以及第三位说话人。

Qivora Sync 客服通话流程:三位说话人输入 Grok Voice Transcribe 2.0,输出为带说话人分离的实时转写

三位说话人汇成一份实时转写。图片来源:作者。

创建三人通话

该受控场景使用来自 Grok 文本转语音 API 的三种不同声音。每个语言片段分别合成,并用 ffmpeg 拼接,以保证切换点固定。API 也接受 language=auto;分开请求只是实验设计选择,并非 API 要求。

定义预期转写文本

在首次请求前,先定义预期文本、说话人、产品拼写、客户信息、填充词与停顿。之后每次设置都以同一目标为准。

在 Python 中设置 Grok Voice Transcribe 2.0

发送音频前先安装依赖。

先决条件

您需要 Python 3.10 或更高版本、一个 xAI API 密钥,以及用于构建音频的 ffmpeg。Python 客户端使用 requests、websockets 和 python-dotenv。

语音转文字文档指出:当省略 model 时,默认即为 2.0;而 grok-voice-transcribe-1.0 已于 2026 年 10 月 2 日停止支持。我仍建议固定使用带版本的 ID。

安装依赖并构建音频

克隆仓库,将密钥添加到 .env,并构建示例音频:

git clone https://github.com/KhalidAbdelaty/grok-voice-transcribe-2.0.git
cd grok-voice-transcribe-2.0
pip install -r requirements.txt
cp .env.example .env    # then paste your key into .env
python project/scripts/make_fixtures.py

该设置命令会创建后文使用的对话与音频文件。若您已有录音,可跳过此命令。

在 Windows 上写入的 .env 可能在密钥后留下 \r,requests 会在到达 SpaceXAI 之前就因该请求头而拒绝。将密钥去除多余字符后再加入授权头。

建立批处理转写基线

基线是指在未启用任何选项的模型输出,以便之后的每项更改都有对照。首次请求发送文件与固定模型:

import os
import requests
from dotenv import load_dotenv

load_dotenv()
api_key = os.environ["XAI_API_KEY"].strip()

with open("support_call.wav", "rb") as audio_file:
    response = requests.post(
        "https://api.x.ai/v1/stt",
        headers={"Authorization": f"Bearer {api_key}"},
        data=[("model", "grok-voice-transcribe-2.0")],
        files={"file": ("support_call.wav", audio_file, "audio/wav")},
    )

response.raise_for_status()
result = response.json()

响应包含 text、检测到的 language、duration,以及带时间戳的 words 数组。参见 REST 参考,其中展示了逐词 confidence,但在本场景的批处理响应中未出现。我会将该字段视为可选,并在使用前检查每次 API 响应。将可选字段置于 file 之前;之后的字段可能被忽略。

基线移除了填充词,阿拉伯语保持阿拉伯文脚本,口述数字保持分开。虚构产品名的拼写始终有误。

添加说话人分离、关键词与文本格式化

客服转写需要说话人标签、正确的产品拼写,以及可用的客户信息。每个设置都对应一个额外的表单字段:

data = [
    ("model", "grok-voice-transcribe-2.0"),
    ("diarize", "true"),         # a speaker id on every word
    ("keyterm", "Qivora Sync"),  # repeat the field for more terms
    ("language", "en"),          # required by format
    ("format", "true"),          # inverse text normalization
    ("filler_words", "false"),   # the default; true keeps "uh" and "um"
]

对同一段音频逐项添加选项。先从说话人标签开始。

将单词归组为说话轮次

说话人分离为单词赋予数字型说话人 ID,而非姓名。将相邻且同一 ID 的单词分组以构建轮次:

def group_turns(words):
    turns = []
    for word in words:
        if turns and turns[-1]["speaker"] == word.get("speaker"):
            turns[-1]["words"].append(word["text"])
            turns[-1]["end"] = word["end"]
        else:
            turns.append({"speaker": word.get("speaker"), "start": word["start"],
                          "end": word["end"], "words": [word["text"]]})
    for turn in turns:
        turn["text"] = " ".join(turn.pop("words"))
    return turns

在干净音频中,每个已知轮次都保持一致的说话人 ID。按首次出现顺序映射姓名只在通话顺序已知时有效;生产系统应有自己的说话人映射。

Qivora Sync 通话的分离转写,显示三个分开的说话轮次与时间戳

干净音频有助于保持说话人标签一致。图片来源:作者。

使用关键词偏置修正产品名

关键词偏置是逐次请求的提示,而非训练。传入 keyterm=Qivora Sync(最多 100 个词条,每个不超过 50 个字符),模型会在音频支持的情况下朝该拼写倾斜。

该关键词纠正了基线中的产品名错误,而未改变周围转写。

在另一次实时说话人检查中,强词汇偏置将微弱回声拉向该关键词。这并不意味着关键词会凭空生成错误文本;而是说明含糊音频仍需回声检查。

转写英—阿语切换

如基线所示,Khalid 的阿拉伯语保持阿拉伯文脚本。无论使用自动检测还是 language=en,结果都相同,因为 language 用于选择格式化规则,而非强制输出语言。

格式化口述电话号码与邮箱

基线保持口述数字分开。逆文本规范化(ITN)将口述形式转换为书面形式。 format=true 可开启该功能,并需要 language,否则请求将以 400 失败。

电话号码变为连续数字串。邮箱仅部分被规范化:标点改善了,但口述的“at”和拼读的域名仍需清理。

该不均衡结果令人沮丧。ITN 负责格式化文本;并不校验联系数据。我会在入库前同时校验这两项。

ITN 还可将普通的时长短语重写为缩写数量。在本场景的批处理格式化响应中,仅顶层 text 被规范化;words 数组仍保留口述形式。

保留或移除语气填充词

如基线所示,默认会从 text 与 words 中移除填充词。设置 filler_words=true 会在预期位置恢复 Khalid 的 “uh” 与 “um”。撰写支持记录时建议关闭,做逐字 QA 记录时建议开启。

批处理输出涵盖了说话人、词汇、格式化和填充词控制。接下来,将同一音频以实时流方式发送。

通过 WebSocket 流式使用 Grok Voice Transcribe 2.0

流式路径使用查询参数而非初始化消息。等待 transcript.created,发送原始二进制音频(非 base64),并以 {"type": "audio.done"} 结束。我们的 GPT 实时转写教程也以另一模型采用了同样的模式。

先了解事件,再连接客户端。

批处理使用文档中的 format=true 加 language=en。流式文档称 language 会开启 ITN,但在一次实时探测中,仅 language=en 并未改变转写。WebSocket 查询参数不包含 format,因此本教程将流式 ITN 视为需验证而非依赖的行为。

读取部分与最终事件

每次转写更新都是 transcript.partial 事件,并带有两个布尔值。中间文本仍可能变化。块级最终(is_final=true)会在轮次仍未关闭时锁定约 3 秒文本;话语最终(speech_final=true)则关闭该轮次。

从 transcript created 到 interim、chunk-final、utterance-final 与 transcript done 的流式事件流程

流式状态推动文本走向最终确定。图片来源:作者。

在 Python 中流式发送 16 kHz PCM 音频

用于流式处理时,先将源音频重采样为 16 kHz、单声道、16 位 PCM。核心客户端以实时节奏每 100 毫秒发送一块音频,同时由另一任务接收转写事件:

import asyncio, json, os, wave
import websockets
from dotenv import load_dotenv 

load_dotenv()

url = ("wss://api.x.ai/v1/stt?model=grok-voice-transcribe-2.0"
       "&sample_rate=16000&encoding=pcm&interim_results=true&diarize=true")
headers = {"Authorization": f"Bearer {os.environ['XAI_API_KEY'].strip()}"}

async def stream_call(path):
    async with websockets.connect(url, additional_headers=headers) as ws:
        assert json.loads(await ws.recv())["type"] == "transcript.created"

        async def send():
            with wave.open(path, "rb") as wf:
                assert wf.getframerate() == 16000
                assert wf.getnchannels() == 1
                assert wf.getsampwidth() == 2
                while chunk := wf.readframes(1600):
                    await ws.send(chunk)
                    await asyncio.sleep(0.1)
            await ws.send(json.dumps({"type": "audio.done"}))

        async def receive():
            async for raw in ws:
                event = json.loads(raw)
                if event["type"] == "transcript.partial":
                    print(event["text"])
                elif event["type"] == "transcript.done":
                    break

        await asyncio.gather(send(), receive())

中间文本大约每半秒增长一次。这是本地测量数据,非官方时延。

终端展示部分字幕不断更新,随后成为最终转写行

部分字幕最终沉淀为最终转写。图片来源:作者。

块级最终会冻结文本,而不关闭当前轮次。Smart Turn 则控制何时由 speech_final 关闭。

保持转写块的顺序

只显示当前事件会导致每个块设为最终后早先的单词“消失”,因为下一个中间结果会从新到达的音频重新开始。

保留所有锁定的块,追加当前中间结果,并在话语最终时用其替换两者。

这样文本可在不丢失早先块的情况下增长。显示状态就绪后,余下的流式问题在于轮次边界。

使用 Smart Turn 进行轮次结束检测

Smart Turn 会评估每次静音并估计说话人是否结束。它正是为 Khalid 报读号码而设:“zero one zero, five five five, [停顿], one two three four”,仅凭静音无法分辨思考停顿与结束。

测试 Smart Turn 阈值

该阈值不是转写置信度,也不是 VAD 阈值。它是静音需要超过的轮次结束概率,超过才会触发 speech_final;低于则保持轮次打开。两个查询参数用于设置:

params += [
    ("smart_turn", "0.7"),           # end-of-turn probability needed to close
    ("smart_turn_timeout", "3000"),  # close anyway after 3 s of silence
]

文档称 0.5 为均衡,0.7 在数字序列上更保守,0.9 非常保守。在本场景中,短于默认 endpointing 窗口的停顿未产生有用的 Smart Turn 判定。这是观察结果,而非文档化的时序规则。

在流式测试中,停止发送音频帧不会推进观测到的静音计时;持续发送数字静音可让 Smart Turn 关闭该话语。

延长口述号码过程中的停顿可让行为更明显。短停顿保持在同一轮次,而长停顿在置信度超过三档设置时,会在每个阈值处分割。

电话号码话语的时间线,显示语音、停顿,以及在各阈值下的轮次结束置信度

长停顿可能分割号码口述。图片来源:作者。

真人来电更难预测。短数字序列可能看似结束,随后又继续。

若 Smart Turn 在口述号码时关闭轮次,请稍作等待并在回复前合并后续部分。

设置 Smart Turn 超时

smart_turn_timeout 会在固定静音后关闭轮次,即使 Smart Turn 仍不确定。在快速的三说话人流中,Smart Turn 将若干已知轮次合到一起,随后超时强制关闭。

若您已知轮次边界位置,请在每个边界发送 {"type": "finalize"};否则将 Smart Turn 与超时配合使用。

当轮次边界得到控制后,还需让相同来电在 8 kHz 线路上依旧可用。

转写 8 kHz 电话音频

此处的电话级音频为 8 kHz G.711 mu-law,由同一通话生成:

ffmpeg -i support_call.wav -ar 8000 -ac 1 -f mulaw support_call_8k.raw

原始电话音频没有容器,因此在批处理表单中设置 audio_format=mulaw 和 sample_rate=8000;或在套接字上设置 encoding=mulaw&sample_rate=8000。请分别检查文本与说话人标签。

比较干净音频与电话音频

先前关于关键词、格式化与语言切换的结论在 8 kHz 下变化不大。

说话人标签变得不可靠。电话版本引入了一个额外说话人 ID,并将结尾轮次分配给了错误的人。仅统计分段数量会掩盖这两类错误。

不稳定版本将通话带宽限制在 300–3400 Hz,编码为 8 kHz mu-law,并以 0.03 的概率丢弃每个 20 毫秒分组。固定随机种子 7 确保每次回放间隙一致。

该分组丢失并未显著改变本样本的英文转写,且口述的联系信息保持顺序。该结果仅适用于本样本。

电话仿真会缩窄音频并丢包。图片来源:作者。

为电话音频调整 VAD

语音活动检测(VAD)判断音频是否为语音。文档建议在安静的电话语音场景下降低 vad_threshold,但存在因噪声产生杂散文本的风险。

在干净的电话音频上降低 vad_threshold 并未带来变化,因为没有可恢复的低音量语音。该空结果支持一条规则:仅在电话语音缺失时才降低阈值。

使用多通道转写分离说话人

使用不含 diarize 的全新批处理表单:

data = [
    ("model", "grok-voice-transcribe-2.0"),
    ("multichannel", "true"),
]

API 会从 WAV 或其他容器中检测声道数。对于原始多通道音频,请添加 ("channels", "3");WebSocket 的多通道输入同样需要显式声道数。

将带多通道文件的表单通过前述 REST 请求发送,然后读取 result["channels"]。每个条目包含索引、转写文本与逐词时间。在受控的三通道场景中,每个通道仅包含其指定说话人。流式同样拆分,并在事件中添加 channel_index。

若电话系统可提供独立通路,我会优先使用。与电话音频部分中的分离不同,已知的通道拆分无需推断说话人。

构建完整的 Python 客服转写器

完整客户端暴露一组设置,然后分别构建 REST 表单或 WebSocket URL。共享设置涵盖说话人分离、关键词、填充词、音频编码与轮次处理;格式化遵循前文所述的传输特定规则。

将最终设置应用于电话音质录音,然后分别检查产品拼写、语言切换、联系信息与说话人标签。在受控场景中,文本检查通过,但仍有一个说话人标签需要复核。与每份转写一并保存设置与说话人映射,以便后续对比使用相同配置。

探索完整的语音代理演示

客服转写教程以该最终检查收尾。仓库还包含一个独立的语音代理扩展,支持生成式回复、语音输出、打断与回声处理。

在该演示中,Transcribe 的角色不变:负责产出文本。语言模型负责编写回复,Grok TTS 负责朗读。

实时通话在对话中途切换音频路径。视频来源:作者。

Grok Voice Transcribe 2.0 的局限

客服转写可能包含姓名、电话号码与邮箱。SpaceXAI 的 安全常见问题称其会将 API 数据以静态加密存储 30 天,用于滥用审计;同时表示未经许可不会用这些数据进行训练。符合条件的团队可在团队层面开启零数据保留(Zero Data Retention)。

请将 API 密钥保存在您的服务器端。语音转文字文档建议通过后端代理 WebSocket。

一次受控通话无法代表所有口音、环境或电话线路。在上线前,请使用目标环境中的音频验证设置。

常见错误与故障排查

此处的大多数失败源自音频格式或套接字处理:

  • InvalidHeader ... return character(s) in header value 是密钥中存在 Windows 的 \r。

  • 400 可能意味着缺少 file 或 url、不支持的格式、原始音频未提供 sample_rate,或 format=true 未配合 language。

  • 在流式测试中,停止发送音频帧不会推进观测到的静音计时;持续发送数字静音可使轮次关闭。

  • cannot call recv while another coroutine is already running recv 表示有两个协程在读取同一套接字。请确保每个连接仅有一个读取协程。

  • 在此 Windows 设置中,输入路径上的音频处理会截断轻声音节。关闭该处理或使用独占采集可修复输入。

若以上均不适用,请将原始事件与源音频逐一对比以定位原因。

Grok Voice Transcribe 2.0 定价

SpaceXAI 的 定价页面列出:REST 转写 $0.10/小时,流式 $0.20/小时。公告称已包含说话人分离、时间戳与关键词。请按音频时长而非请求次数计费。

每个打开的流按其音频时长计费。仅当两个流都接收相同的完整时长时,第二个监听者才会增加流式成本并使 STT 分钟数翻倍。

结语

我不会仅在干净音频上评估通话转写器。电话音频部分已展示原因。

API 返回转写数据;客户端仍需负责会话状态与校验。此外,请保留带版本的模型 ID。将其他设置视为起点,并用目标音频进行验证。

下一步扩展包括 SIP 电话输入、按通话定制词汇,以及导出到 CRM。若您希望构建代理而非转写器,我们的 Grok Voice Agent API 教程涵盖了这一路线。

常见问题

Grok Voice Transcribe 2.0 是否支持实时转写?

支持,通过 WebSocket,且不止原始 PCM。带宽受限的客户端可流式传输 encoding=opus,约 4 KB/s,而 24 kHz PCM 约 48 KB/s,只要每个帧携带一个 Opus 包即可。Opus 仅支持单声道,因此不支持流式多通道。

Grok Voice Transcribe 2.0 是否支持说话人分离?

在任一端点设置 diarize=true。在本场景的流式分离响应中,词项还包含一个未文档化的 speaker_confidence 字段。我不会将应用逻辑建立在其上。将说话人 ID 视为请求或会话内的本地标签,而非持久身份识别。

Grok Voice Transcribe 2.0 能否转写同一录音中的多种语言?

自动检测可在无提示的情况下保留录音中的中途语言切换。language 参数为 25 种列出的语言(包括阿拉伯语 ar)控制格式化,因此在依赖格式化输出前,请用您自己的音频测试相关代码。

Smart Turn 与 VAD 有何区别?

VAD 判断音频是否为语音;Smart Turn 判断语音是否已结束。vad_threshold 在批处理中的默认值为 0.5,在流式中为 0.08。endpointing 默认 400 毫秒,表示话语可关闭前所需的静音时长。

可以通过 URL 转写录音而不是上传文件吗?

使用批处理端点的 url 字段代替 file。SpaceXAI 会在服务器端下载录音,若下载失败会返回 502。

主题
人工智能
AI 代理

在 DataCamp 学习 AI!

学习路径

面向开发者的 AI 工程师助理

26 小时
了解如何使用 API 和开源库将 AI 集成到软件应用程序中。 今天就开始你的 AI 工程师之旅吧!
查看详情Right Arrow
开始课程
查看更多Right Arrow