模型能力
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.mp3响应体包含原始音频字节。可直接保存到文件,或通过管道传给音频播放器。
请求体
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
text | string | ✓ | 要转换为语音的文本。最多 60,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 时,模型会在生成音频前将书面形式的文本(例如数字、缩写和符号)标准化为口语形式。默认为 false。 | |
with_timestamps | boolean | 随音频返回 character-level timing metadata。设为 true 时,response 为 JSON envelope,其中包含 base64 编码的音频以及每个字符的开始/结束时间。合成后的 alignment pass 会增加延迟。默认为 false。请参阅 Character-level timestamp。 | |
replace | object | 将短语映射为合成前应用的朗读替换。值可以是重拼形式({"Acme Mobile": "Acme Mobull"}),或使用 IPA 音标({"nginx": "/ˈɛndʒɪn ˈɛks/"})。请参阅 发音替换。 |
包含全部选项的示例
{
"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
每种声音都有独特的个性。所有声音都能使用每一种支持的语言。试听示例,选择最适合你用途的声音(eve 为默认值):
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 验证不区分大小写,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 |
该模型还能够生成上述列表之外的其他语言,准确度因语言而异。
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.json响应结构
{
"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 切分输入文本。
发音替换
使用 replace 来修正特定单词或短语的发音。每个 key 都会在你的文本中匹配,并在合成之前前替换为对应值,因此只有音频会变化,你仍然发送原始文本,并按原始文本计费。
这对品牌名、缩写词和领域术语很有用。将 "Acme Mobile" 到 "Acme Mobull" 可让音频正确读出,同时请求体中仍然是 "Acme Mobile"。
response = requests.post(
"https://api.x.ai/v1/tts",
headers={"Authorization": f"Bearer {XAI_API_KEY}"},
json={
"text": "Welcome to Acme Mobile.",
"voice_id": "eve",
"language": "en",
"replace": {"Acme Mobile": "Acme Mobull"},
},
)匹配行为:
匹配不区分大小写;替换内容会按你提供的大小写朗读。
要求完整单词边界,因此
Acme, Mobile、Acme-Mobile和Acme Mobiles并不会匹配。在不使用空格分词的文字(中文、日文、泰文、老挝文、高棉文、缅甸文)中,匹配按字符进行,因此单字符 key 也会匹配较长词语内部的字符。当多个 key 共享前缀时,最长匹配优先。
key 会按你发送的文本精确匹配,因此请按输入中出现的写法填写。
映射限制
map 会在生成任何音频前验证,因此违反规则会使请求失败,而不是静默丢弃某个条目:
| 约束 | 限制 | 错误(400) |
|---|---|---|
| 每个 map 的条目数 | 200 | replace has too many entries |
| key 长度 | 100 个字符 | replace key "…" is too long |
| value 长度 | 128 个字符 | replace value for "…" is too long |
| key 字符 | 字母、数字、撇号、空格 | replace key "C++" may not contain punctuation or symbols |
| key 非空 | — | replace keys must not be blank |
| key 作为短语必须互不重复 | 比较时不区分大小写和空白差异 | replace keys "ACME" and "Acme" are the same phrase; keep one |
| 替换后的文本 | 240,000 个字符 | `replace` expands the text to … characters |
最后一行限制的是改写后的文本的长度——在大段文本中,如果短键映射到很长的值,需要留意这一限制。60,000 个字符的输入上限和计费仍按你发送的文本计算。
同一个 replace map 也可用于 Speech to Speech API 以及 WebSocket endpoint。
使用 IPA 的音标发音
模型可以直接在 IPA 中读取 text:
{"text": "Restart /ˈɛndʒɪn ˈɛks/ on the edge nodes."}把它放在 replace value 中,可以让音标不出现在你发送的文本里。这适用于文本由上游生成,或一个条目需要覆盖文档/会话中每次出现的场景。
当发音是约定俗成、而不是能从拼写推导出来时,音标就很有价值:nginx 读作 "engine X",kubectl 读作 "kube cuttle",而 SQL 按内部风格可读作 "sequel" 或 "S-Q-L"。不加控制时,模型会把这些拼读出来,而且每次不一定相同;添加条目可让读法确定下来。
response = requests.post(
"https://api.x.ai/v1/tts",
headers={"Authorization": f"Bearer {XAI_API_KEY}"},
json={
"text": "nginx is returning errors on the Acme Mobile edge nodes.",
"voice_id": "eve",
"language": "en",
"replace": {
"nginx": "/ˈɛndʒɪn ˈɛks/",
"Acme Mobile": "Acme Mobull",
},
},
)如上所示,音标条目和重拼条目可以放在同一个 map 中。
先合成这个词试听。知名名称通常已经正确,例如
IEEE无需提示也会读作 "I triple E",冗余条目只会增加维护成本而没有收益。斜杠只是约定,不是标记:
/ˈɛndʒɪn ˈɛks/与ˈɛndʒɪn ˈɛks会以相同方式朗读,斜杠本身不会读出声。IPA 应放在 value 中。key 保持你要匹配的普通拼写。
音标不受
text_normalization影响,并可用于任何受支持语言。
最佳实践
以下建议可帮助你从 TTS API 获得最高质量的输出。
编写有效文本
使用自然的标点。逗号、句号和问号会引导节奏与语调。
"Wait, really?"听起来比"Wait really"。加入情绪语境。感叹号和问号会影响表达方式,
"That's amazing!"听起来热情,而"That's amazing."则平实直接。将长内容拆分成段落。段落分隔会产生自然停顿,并帮助模型在较长文本中保持一致质量。
单次非流式请求应少于 60,000 个字符。对于更长的内容,请使用没有文本长度限制的 双向 WebSocket endpoint,或者按段落或句子拆分成逻辑片段,再拼接音频输出。
与 AI coding assistant 集成
该 Cloud Console Playground 提供了现成的 Agent instructions(Agent 指令),可复制粘贴到 Cursor、GitHub Copilot 或 Windsurf 等工具中。这些指令已预先配置当前 voice 和格式设置。打开 Playground、调整设置并复制 prompt,即可获得针对 coding agent 定制的集成指南。
针对生产环境进行优化
在服务端代理请求。绝不要在客户端代码中暴露 API key。请通过后端转发 TTS 请求。
缓存生成的音频。如果同一文本被重复请求,请缓存 audio byte,以减少 API 调用并降低延迟。
根据使用场景选择格式。请使用
mulaw或alaw8 kHz 处理电话音频;使用mp324 kHz 处理 Web 音频;使用wav44.1 kHz 或更高采样率进行后期制作。
浏览器播放
要在浏览器中播放 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 | 成功 | 响应体中的音频字节 |
400 | 错误请求 | 检查:文本非空且少于 60,000 个字符;编解码器和采样率有效 |
401 | 未授权 | API key 缺失或无效 |
404 | 未找到 | 未知的 voice_id,请通过 GET /v1/tts/voices(内置)或 GET /v1/custom-voices(custom)进行验证 |
429 | 触发速率限制 | 退避并按指数延迟重试 |
503 | 服务不可用 | TTS 服务暂时不可用,请重试 |
500 | 服务器错误 | 采用指数退避重试 |
采用退避策略重试
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) | |
|---|---|---|
| 最大文本长度 | 每个请求 60,000 个字符 | 无限制,但每条 text.delta 消息各自最多 60,000 个字符 |
| 请求超时 | 15 分钟 | 无超时(连接保持打开) |
| 并发会话数 | — | 每个团队 50 个 |
replace map | 200 个条目;key ≤ 100 个字符,value ≤ 128 个字符 | 相同,按每个 session.update |
经过 replace | 240,000 个字符 | 每段话语 240,000 个字符 |
对于超过 60,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。
客户端 → 服务器消息
以 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 最多 60,000 个字符。 |
text.done | 表示当前 utterance 结束。Server 将完成音频生成并发送 audio.done。 |
text.clear | 取消当前 utterance。Server 会停止生成音频、丢弃所有 buffered data,并返回 audio.clear。 |
session.update | 设置或更改会话的 replace map。任何时间都可接受;它会在下一个开始的 utterance 生效。 |
服务器 → 客户端消息
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。 |
session.updated | 确认一个 session.update,并回显当前生效的 replace map。 |
error | 发生错误。message 字段包含便于阅读的说明。 |
会话配置
发音替换 通过 session.update 消息配置。请在第一个 text.delta 之前发送,让它覆盖整个连接:
{"type": "session.update", "replace": {"Acme Mobile": "Acme Mobull"}}
{"type": "text.delta", "delta": "Welcome to Acme Mobile."}
{"type": "text.done"}服务器会回复 {"type": "session.updated", "replace": {...}},回显当前生效的 map。value 可以是重拼形式或 IPA,与 HTTP 中完全相同。
该 map 会应用于连接生命周期内的每个 utterance,包括在 text.clear 之后。可在任意时间再发送一个 session.update 来更改它,无需重新连接。它影响哪个 utterance 由到达时机决定:
一个 utterance 会使用其第一段文本到达时生效的 map 来朗读。
因此,utterance 中途到达的更新会在下一个 utterance 生效,同一个短语不会在两个 map 下匹配。
匹配会跨越 text.delta 边界,因此拆分到两条消息中的短语仍可匹配。
该 相同的 map 限制 也适用。验证失败的 map 会收到 error 帧,保持映射不变并维持连接;但如果映射将一轮文本扩展到超过 240,000 个字符,会结束会话,因为此时超长文本已被接受。
多话语会话
收到 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.done服务器流式发送新的
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 消息最多 60,000 个字符 |
| 并发会话数 | 每个团队 50 个 |
| Session permit TTL | 600 秒 |
replace 扩展 | 替换后的话语文本超过 240,000 个字符时,会结束会话 |
| 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
最后更新:2026 年 9 月 19 日