模型能力
Text to Speech
只需一次 API 调用,即可将文本转换为语音。该 API 支持丰富且富有表现力的 voice、用于精细控制表达方式的 inline speech tag,以及从高保真 MP3 到针对电话优化的 μ-law 等多种输出格式。
快速入门
通过一次 API 调用生成语音:
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello! Welcome to the xAI Text to Speech API.",
"voice_id": "eve",
"language": "en"
}' \
--output hello.mp3Response body 包含 raw audio byte。可直接保存到文件,或通过 pipe 传给音频播放器。
Request Body
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
text | string | ✓ | 要转换为语音的文本。最多 15,000 个字符。支持 speech tag。 |
voice_id | string | 用于合成的 voice。默认为 eve。请参阅 Voices。 | |
language | string | ✓ | BCP-47 language code(例如 en、zh、pt-BR),或使用 auto 自动检测语言。请参阅 支持的语言。 |
output_format | object | 输出格式配置。默认为 24 kHz / 128 kbps 的 MP3。请参阅 输出格式。 | |
speed | number | 语速倍数。1.0 为正常语速。低于 1.0 会减慢语速,高于 1.0 会加快语速。范围:0.7 到 1.5。默认为 1.0。 | |
optimize_streaming_latency | integer | Streaming synthesis 的延迟优化级别。0(默认):不优化,音频质量最佳。1:减小首个 chunk,以降低 time-to-first-audio;chunk 边界处的质量会略有下降。2:进一步减小首个 chunk,以获得最低 time-to-first-audio;chunk 边界处的质量下降会更明显。 | |
text_normalization | boolean | 在合成前启用 text normalization。设为 true 时,model 会在生成音频前将书面形式的文本(例如数字、缩写和符号)标准化为口语形式。默认为 false。 | |
with_timestamps | boolean | 随音频返回 character-level timing metadata。设为 true 时,response 为 JSON envelope,其中包含 base64 编码的音频以及每个字符的开始/结束时间。合成后的 alignment pass 会增加延迟。默认为 false。请参阅 Character-level timestamp。 |
包含全部选项的示例
{
"text": "Hello! This is a high-fidelity text to speech example.",
"voice_id": "ara",
"language": "en",
"output_format": {
"codec": "mp3",
"sample_rate": 44100,
"bit_rate": 192000
},
"speed": 1.2
}Voices
每种 voice 都有鲜明的个性。试听 sample,并选择最适合你使用场景的一种(eve 为默认值):
| Voice | 音色与用例 | Sample |
|---|---|---|
carina | 柔和、富有同理心且令人安心 | |
zagan | 有力、戏剧化且极具辨识度 | |
helix | 大胆、动感且充满冲劲 | |
orion | 浑厚、电影感且富有共鸣 | |
luna | 温和、耐心且极具关怀感 | |
iris | 友好、积极且自然迷人 | |
altair | 优雅、精致且具有高级感 | |
zenith | 敏锐、专注且充满驱动力 | |
perseus | 坚定、自信且值得信赖 | |
helios | 活泼、充满能量且用途广泛 | |
lux | 沉稳、平静且含蓄睿智 | |
kepler | 创新、前瞻且富有魅力 | |
rigel | 精准、专业且从容自信 | |
cosmo | 明快、好奇且易于理解 | |
celeste | 富有同理心、自信且令人安心 | |
ursa | 友好、温暖且坚定可靠 | |
sirius | 机敏、聪慧且俏皮 | |
lumen | 温暖、表达清晰且富有感染力 | |
castor | 富有魅力、亲切且随和 | |
naksh | 温暖、周到且睿智 | |
atlas | 自信、有掌控力且令人安心 | |
ara | 温暖友好 | |
eve | 充满活力且积极 | |
leo | 权威有力 | |
rex | 自信清晰 | |
sal | 流畅均衡 |
Voice ID 不区分大小写,eve、Eve 和 EVE 均可使用。在 Playground 中试听所有 voice →
Custom voice
使用 Custom Voices API 从简短的参考 clip 克隆任意 voice,或在 Console 中免费创建 voice。要在 Console 中找到 custom voice ID,请点击 voice card 上的三点菜单,然后选择 Copy Voice ID。然后将其作为 voice_id:
# Replace YOUR_VOICE_ID with your custom voice ID from the console
# or the GET /v1/custom-voices endpoint.
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello! This is my custom voice.",
"voice_id": "YOUR_VOICE_ID",
"language": "en"
}' \
--output hello.mp3你也可以通过 Text to speech - List voices endpoint 以编程方式列出 voice:
curl -s https://api.x.ai/v1/tts/voices \
-H "Authorization: Bearer $XAI_API_KEY"支持的语言
TTS API 通过 BCP-47 language code 支持 20 种语言。使用 auto 自动检测语言,或明确指定 language code 以获得一致结果。
Language code 验证不区分大小写,en、EN 和 En 均可使用。
| 语言 | Language Code |
|---|---|
| 自动检测 | auto |
| 英语 | en |
| 阿拉伯语(埃及) | ar-EG |
| 阿拉伯语(沙特阿拉伯) | ar-SA |
| 阿拉伯语(阿拉伯联合酋长国) | ar-AE |
| 孟加拉语 | bn |
| 中文(简体) | zh |
| 法语 | fr |
| 德语 | de |
| 印地语 | hi |
| 印度尼西亚语 | id |
| 意大利语 | it |
| 日语 | ja |
| 韩语 | ko |
| 葡萄牙语(巴西) | pt-BR |
| 葡萄牙语(葡萄牙) | pt-PT |
| 俄语 | ru |
| 西班牙语(墨西哥) | es-MX |
| 西班牙语(西班牙) | es-ES |
| 土耳其语 | tr |
| 越南语 | vi |
该 model 还能够生成上述列表之外的其他语言,准确度因语言而异。
Speech Tag
示例: 我走进去,[pause] 它就在那里。[laugh] 我简直不敢相信!<whisper>原来它一直是个秘密。</whisper> 很酷,对吧?
在文本中添加 inline speech tag,以获得更富表现力的表达。Tag 分为两种:
Inline tag
[tag],放在文本中的特定位置,用于产生声音表达(例如笑声或停顿)Wrapping tag
<tag>text</tag>,包裹一段文本以改变其表达方式(例如耳语或歌唱)
Inline Tag
将这些 tag 插入需要产生相应表达的位置。点击任意 tag 即可试听示例:
Wrapping Tag
包裹文本以改变表达风格。请使用 opening tag 和匹配的 closing tag。点击任意 tag 即可试听示例:
示例
# Inline tags
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "So I walked in and [pause] there it was. [laugh] I honestly could not believe it!",
"voice_id": "eve",
"language": "en"
}' \
--output expressive.mp3
# Wrapping tags
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "I need to tell you something. <whisper>It is a secret.</whisper> Pretty cool, right?",
"voice_id": "eve",
"language": "en"
}' \
--output whisper.mp3Speech tag 使用建议:
将 inline tag 放在对话中自然出现相应表达的位置
将 tag 与标点结合使用,
"Really? [laugh] That's incredible!"的效果比堆叠 tag 更自然使用
[pause]或[long-pause]加入戏剧化的节奏,或让一句话有停顿沉淀的空间Wrapping tag 包裹完整短语时效果最佳,
<whisper>It is a secret.</whisper>比只包裹单个词读起来更自然组合多种风格以获得所需效果,
<slow><soft>Goodnight, sleep well.</soft></slow>
输出格式
使用 output_format object 控制 audio codec、sample rate 和 bit rate。省略时默认为 24 kHz / 128 kbps 的 MP3。
Codec
| Codec | Content-Type | 最适合 |
|---|---|---|
mp3 | audio/mpeg | 通用场景,兼容性广、压缩效果好 |
wav | audio/wav | 无损音频,适用于编辑和后期制作 |
pcm | audio/pcm | Raw audio,适用于实时处理 pipeline |
mulaw | audio/basic | 电话音频(G.711 μ-law) |
alaw | audio/alaw | 电话音频(G.711 A-law) |
Sample Rate
| 采样率 | 说明 |
|---|---|
8000 | Narrowband,适用于电话音频 |
16000 | Wideband,适用于语音识别 |
22050 | 标准,质量均衡 |
24000 | 高质量,默认值,推荐用于大多数场景 |
44100 | CD 质量,适用于媒体制作 |
48000 | 专业级,适用于录音室级音频 |
Bit Rate(仅 MP3)
| 采样率 | 质量 |
|---|---|
32000 | 低,文件最小 |
64000 | 中,适合语音 |
96000 | 标准,效果均衡 |
128000 | 高,默认值,推荐 |
192000 | 最高,保真度最高 |
示例:高保真 MP3
{
"text": "Crystal clear audio at maximum quality.",
"voice_id": "rex",
"language": "en",
"output_format": {
"codec": "mp3",
"sample_rate": 44100,
"bit_rate": 192000
}
}示例:电话音频(μ-law)
{
"text": "Hello, thank you for calling. How can I help you today?",
"voice_id": "ara",
"language": "en",
"output_format": {
"codec": "mulaw",
"sample_rate": 8000
}
}Character-level timestamp
设置 with_timestamps 到 true,即可接收每个字符的开始和结束 timestamp。非常适合字幕同步、卡拉 OK 高亮、实时口型同步以及其他需要时间对齐的应用。
此时 response 会从 raw audio byte 变为 JSON envelope(Content-Type: application/json),其中包含 base64 编码的音频和字符 timing。
请求 timestamp
在普通请求中加入该 flag。音频将包含在 JSON body 中返回,而不是以 raw byte 返回:
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello world.",
"voice_id": "eve",
"language": "en",
"with_timestamps": true
}' \
--output response.jsonResponse 结构
{
"audio": "<base64-encoded audio in the requested codec>",
"content_type": "audio/mpeg",
"duration": 0.92,
"audio_timestamps": {
"graph_chars": ["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "."],
"graph_times": [
[0.00, 0.06],
[0.06, 0.12],
[0.12, 0.18],
[0.18, 0.24],
[0.24, 0.34],
[0.34, 0.40],
[0.40, 0.48],
[0.48, 0.54],
[0.54, 0.62],
[0.62, 0.68],
[0.68, 0.78],
[0.78, 0.92]
]
}
}| 字段 | 类型 | 说明 |
|---|---|---|
audio | string | 使用请求 codec 的 base64 编码音频。解码后可像普通 response 一样播放。 |
content_type | string | 解码后音频的 MIME type(例如 audio/mpeg、audio/wav)。 |
duration | number | 音频总时长,单位为秒。 |
audio_timestamps.graph_chars | string[] | 按顺序排列的每个输入字符,包括空格、标点和 speech tag。 |
audio_timestamps.graph_times | number[][] | 由 [start, end] pair 组成的并行 array,单位为秒。 |
graph_chars 与 graph_times 按 index 对齐,两者逐项对应。因此 graph_chars[i] 是在 graph_times[i] 时间区间内读出的字符。对于 "Hello world.":
char: H e l l o ␣ w o r l d .
start: 0.00 0.06 0.12 0.18 0.24 0.34 0.40 0.48 0.54 0.62 0.68 0.78
└──────────── "Hello" ───────────┘ └──────────── "world." ──────────┘
0s ─────────────────────────────────────────────────────────────────────▶ 0.92s特殊字符
graph_chars 与输入逐字符对应,包括空格、标点和 speech tag。当一个书写 token 被读成多个单词时,其 timing 会分配给第一个字符,其余字符则在同一时间范围内插值。
这种情况主要发生在启用 text_normalization 时,该功能会把符号和数字展开为单词。启用 normalization 后,$5 会被读作 "five dollars",但仍然只有两个字符:$ 会获得 "five dollars" 的完整时间范围,而 5 会获得该范围内的插值时间。因此应始终按顺序遍历 graph_chars,不要按 index 切分输入文本。
最佳实践
以下建议可帮助你从 TTS API 获得最高质量的输出。
编写有效文本
使用自然的标点。逗号、句号和问号会引导节奏与语调。
"Wait, really?"听起来比"Wait really"。加入情绪语境。感叹号和问号会影响表达方式,
"That's amazing!"听起来热情,而"That's amazing."则平实直接。将长内容拆分成段落。段落分隔会产生自然停顿,并帮助 model 在较长文本中保持一致质量。
将 unary request 控制在 15,000 个字符以内。对于更长的内容,请使用没有文本长度限制的 双向 WebSocket endpoint,或者按段落或句子拆分成逻辑片段,再拼接音频输出。
与 AI coding assistant 集成
该 Cloud Console Playground 提供了现成的 Agent instructions,可复制粘贴到 Cursor、GitHub Copilot 或 Windsurf 等工具中。这些 instructions 已预先配置当前 voice 和格式设置。打开 Playground、调整设置并复制 prompt,即可获得针对 coding agent 定制的集成指南。
针对生产环境进行优化
在服务端代理请求。绝不要在 client-side code 中暴露 API key。请通过 backend 转发 TTS 请求。
缓存生成的音频。如果同一文本被重复请求,请缓存 audio byte,以减少 API 调用并降低延迟。
根据使用场景选择格式。请使用
mulaw或alaw8 kHz 处理电话音频;使用mp324 kHz 处理 Web 音频;使用wav44.1 kHz 或更高采样率进行后期制作。遵守 concurrent session 限制。Streaming WebSocket endpoint 最多允许 每个 team 50 个 concurrent session。对于高吞吐量服务,请使用连接池或对请求排队,以保持在该限制内。
浏览器播放
要在浏览器中播放 TTS 音频,请通过 backend 代理请求,并使用 Web Audio API 或 <audio> 元素:
// Client-side: fetch from your backend proxy, then play
async function speakText(text, voiceId = "eve") {
const response = await fetch("/api/tts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, voice_id: voiceId }),
});
if (!response.ok) throw new Error("TTS request failed");
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.addEventListener("ended", () => URL.revokeObjectURL(url));
await audio.play();
}
// Usage
await speakText("Hello from the browser!");浏览器注意事项
Safari 在 blob URL 上返回 Infinity,作为 audio.duration 的值。其中 loadedmetadata event 会触发,但 audio.duration 为 Infinity,导致 seek bar 和时间显示失效。请改用 AudioContext.decodeAudioData():
async function getAudioDuration(arrayBuffer) {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioCtx();
// Clone the buffer - decodeAudioData detaches the original
const decoded = await ctx.decodeAudioData(arrayBuffer.slice(0));
const durationMs = Math.round(decoded.duration * 1000);
await ctx.close();
return durationMs;
}AudioContext 在 Safari 中必须在 user gesture 期间创建。Safari 会永久挂起在 click/tap handler 外创建的 AudioContext,且无法恢复。Chrome 对此更宽松。请始终在按钮的 click handler 中创建或恢复 context,并在此之后再执行任何 await:
// Create the AudioContext once, in a click handler
let audioCtx;
button.addEventListener("click", async () => {
// This MUST happen synchronously in the click handler for Safari
if (!audioCtx) audioCtx = new AudioContext();
if (audioCtx.state === "suspended") await audioCtx.resume();
// Now it's safe to fetch and play audio asynchronously
const response = await fetch("/api/tts", { /* ... */ });
const arrayBuffer = await response.arrayBuffer();
const decoded = await audioCtx.decodeAudioData(arrayBuffer);
const source = audioCtx.createBufferSource();
source.buffer = decoded;
source.connect(audioCtx.destination);
source.start();
});Raw codec(pcm、mulaw、alaw)无法在浏览器中播放。 AudioContext.decodeAudioData() 与 <audio> 元素只支持 MP3 和 WAV 等 container 格式。浏览器播放请使用 mp3 或 wav。如果在服务端处理 raw 格式(例如通过 pipe 传给电话系统),可根据 byte 数估算时长:
// PCM = 16-bit LE (2 bytes/sample), mulaw/alaw = 8-bit (1 byte/sample)
const bytesPerSample = codec === "pcm" ? 2 : 1;
const durationMs = Math.round((byteLength / bytesPerSample / sampleRate) * 1000);撤销 blob URL 以避免内存泄漏。每次 URL.createObjectURL() 调用都会分配内存,直到明确释放才会回收。播放结束时请撤销 URL。对于下载,请延迟撤销,以便浏览器完成文件保存:
// Playback: revoke when done
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.addEventListener("ended", () => URL.revokeObjectURL(url));
// Downloads: delay revocation
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "speech.mp3";
a.click();
setTimeout(() => URL.revokeObjectURL(downloadUrl), 10_000);错误处理
| 状态 | 含义 | 处理方式 |
|---|---|---|
200 | 成功 | Response body 中的 audio byte |
400 | 错误请求 | 检查:text 非空且少于 15,000 个字符;codec 和 sample rate 有效 |
401 | 未授权 | API key 缺失或无效 |
404 | 未找到 | 未知的 voice_id,请通过 GET /v1/tts/voices(内置)或 GET /v1/custom-voices(custom)进行验证 |
429 | 触发速率限制 | 退避并按指数延迟重试 |
503 | 服务不可用 | TTS 服务暂时不可用,请重试 |
500 | Server 错误 | 使用 exponential backoff 重试 |
使用 backoff 重试
import os
import time
import requests
def generate_speech(text, language="en", voice_id="eve", max_retries=3):
for attempt in range(max_retries):
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={"text": text, "language": language, "voice_id": voice_id},
)
if response.ok:
return response.content
if response.status_code in (429, 500, 503):
wait = 2 ** attempt
time.sleep(wait)
continue
response.raise_for_status() # Non-retryable error
raise RuntimeError("Max retries exceeded")限制
Unary/server-streamed endpoint 与双向 WebSocket endpoint 的限制不同:
Unary 与 server-streamed(POST /v1/tts) | 双向 WebSocket(wss://api.x.ai/v1/tts) | |
|---|---|---|
| 最大文本长度 | 每个请求 15,000 个字符 | 无限制,但每条 text.delta message 最多 15,000 个字符 |
| 请求超时 | 15 分钟 | 无超时(连接保持打开) |
| Concurrent session | — | 每个 team 50 个 |
对于超过 15,000 个字符的内容,请使用 双向 WebSocket endpoint,该 endpoint 没有文本长度限制。
Streaming TTS(WebSocket)
要实时生成音频,请与 streaming TTS endpoint 建立 WebSocket 连接。文本以 delta 形式 streamed in,音频则以 base64 编码的 chunk streamed back;这非常适合希望在完整文本就绪前就开始播放音频的交互式应用。
Endpoint: wss://api.x.ai/v1/tts
连接
建立 WebSocket 连接,并通过 query parameter 配置语言、voice 和音频格式:
GET /v1/tts?language=en&voice=eve&codec=mp3&sample_rate=24000&bit_rate=128000
Upgrade: websocket
Authorization: Bearer $XAI_API_KEY| 参数 | 必需 | 默认值 | 可接受的值 |
|---|---|---|---|
voice | 任意内置 voice ID(请参阅 Voices)或 custom voice ID | ||
language | ✓ | auto 或 BCP-47 code(例如 en、zh、pt-BR)。请参阅 支持的语言。 | |
codec | mp3 | mp3、wav、pcm、mulaw(或 ulaw)、alaw | |
sample_rate | 24000 | 8000、16000、22050、24000、44100、48000 | |
bit_rate | 128000 | 32000、64000、96000、128000、192000(仅 MP3) | |
speed | 1.0 | 语速倍数(0.7 到 1.5) | |
optimize_streaming_latency | 0 | 0(关闭,质量最佳)、1(中等,降低 time-to-first-audio)、2(激进,time-to-first-audio 最低) | |
text_normalization | false | true、false | |
with_timestamps | false | true、false。设为 true 时,每个 audio.delta 都会携带 audio_timestamps 与 audio_duration,用于描述该 chunk 中字符的 timing。 |
无效的 voice、language、codec 或 sample_rate 会在 WebSocket upgrade 之前被拒绝,并返回 HTTP 400 或 404。
Client → Server Message
以 JSON text frame 将文本发送到 server。将文本拆分到多条 text.delta message 中,然后使用 text.done:
{"type": "text.delta", "delta": "Here is some text. "}
{"type": "text.delta", "delta": "More text follows."}
{"type": "text.done"}| Event | 说明 |
|---|---|
text.delta | 要合成的文本 chunk。每条 delta 最多 15,000 个字符。 |
text.done | 表示当前 utterance 结束。Server 将完成音频生成并发送 audio.done。 |
text.clear | 取消当前 utterance。Server 会停止生成音频、丢弃所有 buffered data,并返回 audio.clear。 |
Server → Client Message
Server 返回 base64 编码的 audio chunk 和 completion event:
{"type": "audio.delta", "delta": "<base64-encoded audio bytes>"}
{"type": "audio.done", "trace_id": "uuid"}
{"type": "audio.clear"}
{"type": "error", "message": "description"}| Event | 说明 |
|---|---|
audio.delta | 使用建立连接时所指定 codec 的 base64 编码 audio chunk。解码后加入播放队列。当连接以 with_timestamps=true 建立时,还会携带 audio_timestamps(graph_chars + graph_times)和 audio_duration,用于描述该 chunk 中的字符。请参阅 Character-level timestamp。 |
audio.done | 当前 utterance 的全部音频均已发送。包含用于调试的 trace_id。 |
audio.clear | 确认已响应 text.clear 取消当前 utterance。连接已准备好处理下一个 utterance。 |
error | 发生错误。message 字段包含便于阅读的说明。 |
Multi-Utterance Session
收到 audio.done 后连接会保持打开。你可以立即再发送一轮 text.delta → text.done message 来合成更多文本,无需重新连接。这适用于按顺序为每个 assistant response 生成音频的对话式 UI。
Multi-turn session 流程:
Turn 1:Client 发送
text.delta→text.doneServer 返回
audio.deltachunk →audio.done连接保持打开
Turn 2:Client 发送
text.delta→text.doneServer 返回
audio.deltachunk →audio.done按需重复
每条 text.done 都会 flush 累积的文本以生成音频。收到 audio.done 后,即可发送更多文本用于下一个 turn。每个 turn 的音频彼此独立,turn 1 的内容不会混入 turn 2。
import asyncio
import base64
import json
import os
import websockets # pip install websockets
XAI_API_KEY = os.environ["XAI_API_KEY"]
async def multi_turn_tts(language: str = "en", voice: str = "eve", codec: str = "mp3"):
uri = f"wss://api.x.ai/v1/tts?language={language}&voice={voice}&codec={codec}"
async with websockets.connect(
uri,
additional_headers={"Authorization": f"Bearer {XAI_API_KEY}"},
) as ws:
# Turn 1
await ws.send(json.dumps({"type": "text.delta", "delta": "Hello from turn one."}))
await ws.send(json.dumps({"type": "text.done"}))
turn1_audio = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
turn1_audio.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
print(f"Turn 1: {len(turn1_audio):,} bytes")
break
elif event["type"] == "error":
raise RuntimeError(event["message"])
# Connection is still open — send turn 2
await ws.send(json.dumps({"type": "text.delta", "delta": "And hello from turn two."}))
await ws.send(json.dumps({"type": "text.done"}))
turn2_audio = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
turn2_audio.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
print(f"Turn 2: {len(turn2_audio):,} bytes")
break
elif event["type"] == "error":
raise RuntimeError(event["message"])
asyncio.run(multi_turn_tts())取消(Barge-in)
发送 text.clear 可取消当前 utterance,并在同一连接上开始新的 utterance,无需重新连接。这可消除每次中断时的 WebSocket handshake 延迟(远距离 client 约为 600ms)。
Barge-in 流程:
Client 发送
text.delta→text.doneServer 开始 streaming
audio.deltachunk用户中断,client 发送
text.clearServer 返回
audio.clearClient 发送新的
text.delta→text.doneServer streaming 新的
audio.deltachunk →audio.done
text.clear 可随时安全发送;如果没有正在进行的 utterance,server 会立即返回 audio.clear。收到 audio.clear 时,请清空本地 audio playback buffer,防止播放过期音频。
import asyncio
import base64
import json
import os
import websockets # pip install websockets
XAI_API_KEY = os.environ["XAI_API_KEY"]
async def tts_with_barge_in(language: str = "en", voice: str = "eve", codec: str = "mp3"):
uri = f"wss://api.x.ai/v1/tts?language={language}&voice={voice}&codec={codec}"
async with websockets.connect(
uri,
additional_headers={"Authorization": f"Bearer {XAI_API_KEY}"},
) as ws:
# Start first utterance
await ws.send(json.dumps({"type": "text.delta", "delta": "The answer to your question is a long explanation..."}))
await ws.send(json.dumps({"type": "text.done"}))
# Wait for audio to start, then cancel
event = json.loads(await ws.recv())
print(f"Got {event['type']} — cancelling")
await ws.send(json.dumps({"type": "text.clear"}))
async for msg in ws:
if json.loads(msg)["type"] == "audio.clear":
break
# New utterance on the same connection
await ws.send(json.dumps({"type": "text.delta", "delta": "Actually, let me start over."}))
await ws.send(json.dumps({"type": "text.done"}))
audio = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
audio.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
print(f"New utterance: {len(audio):,} bytes")
break
asyncio.run(tts_with_barge_in())限制与行为
| 属性 | 值 |
|---|---|
| 文本总长度 | 无限制,可按需发送任意数量的 text.delta message |
| Delta 大小 | 每条 text.delta message 最多 15,000 个字符 |
| Concurrent session | 每个 team 50 个 |
| Session permit TTL | 600 秒 |
| Moderation | 在音频发送后,对累积文本异步执行(fail-open) |
| 计费 | 按 session 根据输入字符总数记录 |
相关内容
TTS Playground,在浏览器中试用 voice 和 speech tag
创建 API Key,开始使用 API
Voice 概览,了解所有 xAI voice 能力
Speech to Speech API,通过 WebSocket 进行实时语音对话
API Reference,完整的 TTS endpoint 规范
列出 Voice,以编程方式发现可用 voice