模型能力

Text to Speech

查看 Markdown

只需一次 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

响应体包含原始音频字节。可直接保存到文件,或通过管道传给音频播放器。


请求体

参数类型必需说明
textstring要转换为语音的文本。最多 60,000 个字符。支持 speech tag
voice_idstring用于合成的 voice。默认为 eve。请参阅 Voices
languagestringBCP-47 language code(例如 enzhpt-BR),或使用 auto 自动检测语言。请参阅 支持的语言
output_formatobject输出格式配置。默认为 24 kHz / 128 kbps 的 MP3。请参阅 输出格式
speednumber语速倍数。1.0 为正常语速。低于 1.0 会减慢语速,高于 1.0 会加快语速。范围:0.71.5。默认为 1.0
optimize_streaming_latencyintegerStreaming synthesis 的延迟优化级别。0(默认):不优化,音频质量最佳。1:减小首个 chunk,以降低 time-to-first-audio;chunk 边界处的质量会略有下降。2:进一步减小首个 chunk,以获得最低 time-to-first-audio;chunk 边界处的质量下降会更明显。
text_normalizationboolean在合成前启用 text normalization。设为 true 时,模型会在生成音频前将书面形式的文本(例如数字、缩写和符号)标准化为口语形式。默认为 false
with_timestampsboolean随音频返回 character-level timing metadata。设为 true 时,response 为 JSON envelope,其中包含 base64 编码的音频以及每个字符的开始/结束时间。合成后的 alignment pass 会增加延迟。默认为 false。请参阅 Character-level timestamp
replaceobject将短语映射为合成前应用的朗读替换。值可以是重拼形式({"Acme Mobile": "Acme Mobull"}),或使用 IPA 音标({"nginx": "/ˈɛndʒɪn ˈɛks/"})。请参阅 发音替换

包含全部选项的示例

JSON

{
  "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 不区分大小写eveEveEVE 均可使用。在 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 验证不区分大小写enENEn 均可使用。

语言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.mp3

Speech 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

CodecContent-Type最适合
mp3audio/mpeg通用场景,兼容性广、压缩效果好
wavaudio/wav无损音频,适用于编辑和后期制作
pcmaudio/pcmRaw audio,适用于实时处理 pipeline
mulawaudio/basic电话音频(G.711 μ-law)
alawaudio/alaw电话音频(G.711 A-law)

Sample Rate

采样率说明
8000Narrowband,适用于电话音频
16000Wideband,适用于语音识别
22050标准,质量均衡
24000高质量,默认值,推荐用于大多数场景
44100CD 质量,适用于媒体制作
48000专业级,适用于录音室级音频

Bit Rate(仅 MP3)

采样率质量
32000低,文件最小
64000中,适合语音
96000标准,效果均衡
128000高,默认值,推荐
192000最高,保真度最高

示例:高保真 MP3

JSON

{
  "text": "Crystal clear audio at maximum quality.",
  "voice_id": "rex",
  "language": "en",
  "output_format": {
    "codec": "mp3",
    "sample_rate": 44100,
    "bit_rate": 192000
  }
}

示例:电话音频(μ-law)

JSON

{
  "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_timestampstrue,即可接收每个字符的开始和结束 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

响应结构

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]
    ]
  }
}
字段类型说明
audiostring使用请求 codec 的 base64 编码音频。解码后可像普通 response 一样播放。
content_typestring解码后音频的 MIME type(例如 audio/mpegaudio/wav)。
durationnumber音频总时长,单位为秒。
audio_timestamps.graph_charsstring[]按顺序排列的每个输入字符,包括空格、标点和 speech tag。
audio_timestamps.graph_timesnumber[][][start, end] pair 组成的并行 array,单位为秒。

graph_charsgraph_times 按 index 对齐,两者逐项对应。因此 graph_chars[i] 是在 graph_times[i] 时间区间内读出的字符。对于 "Hello world."

Text

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, MobileAcme-MobileAcme Mobiles不会匹配。在不使用空格分词的文字(中文、日文、泰文、老挝文、高棉文、缅甸文)中,匹配按字符进行,因此单字符 key 也会匹配较长词语内部的字符。

  • 当多个 key 共享前缀时,最长匹配优先。

  • key 会按你发送的文本精确匹配,因此请按输入中出现的写法填写。

映射限制

map 会在生成任何音频前验证,因此违反规则会使请求失败,而不是静默丢弃某个条目:

约束限制错误(400
每个 map 的条目数200replace 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

JSON

{"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 调用并降低延迟。

  • 根据使用场景选择格式。请使用 mulawalaw 8 kHz 处理电话音频;使用 mp3 24 kHz 处理 Web 音频;使用 wav 44.1 kHz 或更高采样率进行后期制作。


浏览器播放

要在浏览器中播放 TTS 音频,请通过 backend 代理请求,并使用 Web Audio API 或 <audio> 元素:

JavaScript

// 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.durationInfinity,导致 seek bar 和时间显示失效。请改用 AudioContext.decodeAudioData()

JavaScript

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

JavaScript

// 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 格式。浏览器播放请使用 mp3wav。如果在服务端处理 raw 格式(例如通过 pipe 传给电话系统),可根据 byte 数估算时长:

JavaScript

// 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。对于下载,请延迟撤销,以便浏览器完成文件保存:

JavaScript

// 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 map200 个条目;key ≤ 100 个字符,value ≤ 128 个字符相同,按每个 session.update
经过 replace240,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 和音频格式:

Text

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
languageauto 或 BCP-47 code(例如 enzhpt-BR)。请参阅 支持的语言
codecmp3mp3wavpcmmulaw(或 ulaw)、alaw
sample_rate2400080001600022050240004410048000
bit_rate128000320006400096000128000192000(仅 MP3)
speed1.0语速倍数(0.71.5
optimize_streaming_latency00(关闭,质量最佳)、1(中等,降低 time-to-first-audio)、2(激进,time-to-first-audio 最低)
text_normalizationfalsetruefalse
with_timestampsfalsetruefalse。设为 true 时,每个 audio.delta 都会携带 audio_timestampsaudio_duration,用于描述该 chunk 中字符的 timing。

无效的 voicelanguagecodecsample_rate 会在 WebSocket upgrade 之前被拒绝,并返回 HTTP 400 或 404。


客户端 → 服务器消息

以 JSON text frame 将文本发送到 server。将文本拆分到多条 text.delta message 中,然后使用 text.done

JSON

{"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:

JSON

{"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_timestampsgraph_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 之前发送,让它覆盖整个连接:

JSON

{"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.deltatext.done message 来合成更多文本,无需重新连接。这适用于按顺序为每个 assistant response 生成音频的对话式 UI。

Multi-turn session 流程:

  1. Turn 1:Client 发送 text.deltatext.done

  2. Server 返回 audio.delta chunk → audio.done

  3. 连接保持打开

  4. Turn 2:Client 发送 text.deltatext.done

  5. Server 返回 audio.delta chunk → audio.done

  6. 按需重复

每条 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 流程:

  1. Client 发送 text.deltatext.done

  2. Server 开始 streaming audio.delta chunk

  3. 用户中断,client 发送 text.clear

  4. Server 返回 audio.clear

  5. Client 发送新的 text.deltatext.done

  6. 服务器流式发送新的 audio.delta chunk → 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 TTL600 秒
replace 扩展替换后的话语文本超过 240,000 个字符时,会结束会话
Moderation在音频发送后,对累积文本异步执行(fail-open)
计费按 session 根据输入字符总数记录


最后更新:2026 年 9 月 19 日