模型能力
Speech to Speech
构建由 Grok 驱动的实时语音应用。通过 WebSocket 双向 streaming 音频和文本,适用于 voice assistant、电话 Agent 和交互式语音系统。
快速入门
连接 Speech to Speech API 并开始对话:
import asyncio
import json
import os
import websockets
async def voice_agent():
async with websockets.connect(
"wss://api.x.ai/v1/realtime?model=grok-voice-latest",
additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"}
) as ws:
# Configure session
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "eve",
"instructions": "You are a helpful assistant.",
"turn_detection": {"type": "server_vad"}
}
}))
# Send a text message
await ws.send(json.dumps({
"type": "conversation.item.create",
"item": {"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "Hello!"}]}
}))
await ws.send(json.dumps({"type": "response.create"}))
# Receive audio/text responses
async for msg in ws:
event = json.loads(msg)
print(f"Event: {event['type']}")
asyncio.run(voice_agent())使用 Tester App 开始体验
iOS Tester App,一个基于 Swift 的 iOS app,可作为在应用中设置 voice Agent 的指南。
Web Agent(WebSocket),使用 WebSocket 的 Web app voice Agent。
WebRTC Agent,使用 WebRTC 的 Web app voice Agent。
Telephony Agent,使用 Twilio、可通过电话呼叫的 Agent。
身份验证
使用以下任一方式验证 WebSocket 连接:
Ephemeral Token(推荐),用于 client-side app(浏览器、移动端)的短期 token,可避免在 client 中暴露 API key。
API Key,直接在
Authorizationheader 中传入 xAI API key。仅限服务端使用。
更多信息请参阅 API 文档。
Event
WebSocket 建立后,即可开始双向 event 通信。Client event 用于提供对话信息并向 Voice API 发送用户音频,server event 则包含音频和文本 response。
Model 选择
将 model 作为 query parameter 传入;使用带版本的名称可固定到特定 release。
MODEL = "grok-voice-latest"
url = f"wss://api.x.ai/v1/realtime?model={MODEL}"| Model | 说明 |
|---|---|
grok-voice-latest | 以下 model 的 alias:grok-voice-think-fast-1.0。将在 2026 年 8 月 5 日更新为 grok-voice-think-fast-2.0。 |
grok-voice-think-fast-2.0 | 旗舰 voice model |
grok-voice-think-fast-1.0 | 上一代 voice model |
Session 参数
Session 创建后,client 可以发送 session.update event 来配置 session。
| 参数 | 类型 | 说明 |
|---|---|---|
instructions | string | System prompt |
reasoning.effort | "high" | "none" | 可选 | 控制 model 是否使用 reasoning。默认为 "high"。 |
voice | string | Voice 选择:任意内置 voice(例如 eve)或 custom voice ID(请参阅 可用 Voice) |
tools | array | Voice Agent 可用的 tool。支持 file_search、web_search、x_search、mcp 和 function 类型。请参阅 使用 Tool。 |
turn_detection.type | string | null | "server_vad" 用于自动检测,null 用于手动文本 turn |
turn_detection.threshold | number | 可选 | VAD 激活 threshold(0.1–0.9)。值越高,需要越响的音频才能触发。默认值:0.85。 |
turn_detection.silence_duration_ms | number | 可选 | Server 结束 turn 前用户必须保持静音的时长(毫秒,0–10000)。值越高,用户可以停顿更久而不会被打断。 |
turn_detection.prefix_padding_ms | number | 可选 | 在检测到语音开始前一并包含的音频时长(毫秒,0–10000)。有助于捕获原本可能被 VAD 截断的词首。默认值:333。 |
turn_detection.idle_timeout_ms | number | 可选 | 设置后,如果 assistant 完成 response 后在指定毫秒数内未检测到语音,server 会主动重新与用户互动。Timer 会在每次 response 后重新启动,因此会按每个 idle_timeout_ms 周期重复触发,直到用户开始说话。默认值:null。 |
resumption.enabled | boolean | 可选 | 选择启用 Session Resumption:server 使用 conversation_id 作为 key 缓存 conversation turn,并在重新连接时 replay,使 model 继续基于先前 context。默认为 false。请参阅 Session Resumption。 |
audio.input.format.type | string | 输入 codec:"audio/pcm"、"audio/pcmu"、"audio/pcma" 或 "audio/opus" |
audio.input.format.rate | number | 输入 sample rate(仅 PCM):8000、16000、22050、24000、32000、44100、48000 |
audio.input.transport | "json" | "binary" | 可选 | 输入音频的 wire path。默认值:"json"(在 input_audio_buffer.append 中使用 base64)。"binary":将 raw codec byte 作为 WebSocket binary frame。请参阅 音频传输。 |
audio.output.format.type | string | 输出 codec:"audio/pcm"、"audio/pcmu"、"audio/pcma" 或 "audio/opus" |
audio.output.format.rate | number | 输出 sample rate(仅 PCM):8000、16000、22050、24000、32000、44100、48000 |
audio.output.transport | "json" | "binary" | 可选 | Assistant 音频的 wire path。默认值:"json"(在 response.output_audio.delta / response.audio.delta 中使用 base64)。"binary":将 raw codec byte 作为 WebSocket binary frame。Session 中途的更改会在下一个 response 边界生效。请参阅 音频传输。 |
audio.input.transcription.language_hint | string | BCP-47 language code(例如 "ja"、"ar"、"es-MX"、"pt-BR"),用于引导 ASR transcription 偏向特定语言。可在 session 中途更新。请参阅 Language Hint。 |
audio.input.transcription.keyterms | array | 用于引导 transcription 的 key term 列表(例如产品名、专有名词)。最多 100 个 term,每个最多 50 个字符。可在 session 中途更新。请参阅 Keyterm。 |
audio.output.speed | number | Assistant 音频输出的播放速度倍数。范围:0.7–1.5。默认值:1.0。低于 1.0 会减慢语速,高于 1.0 会加快语速。 |
replace | object | 可选 | 在 TTS 前应用于 model 输出的短语与口语替代项映射,例如 {"Acme Mobile": "Acme Mobull"}。通过改变语音而不修改 transcript 来修正发音。请参阅 发音替换。 |
可用 Voice
同一组 voice 可用于 Speech to Speech API 和 Text to Speech API。可在 voice 表格 中查看包含音色说明和 sample 的完整列表,或通过 GET /v1/tts/voices 以编程方式获取。将小写 voice ID 作为 voice 参数传给 session.update。
Custom Voice
需要列表之外的 voice?使用 Custom Voices API 从简短的参考 clip 克隆任意 voice。生成的 voice_id 可作为 voice 参数传给 session.update 使用,其行为与内置 voice 完全相同。
选择 Voice
在 session 配置中使用 voice 参数指定 voice:
# Configure session with a specific voice
session_config = {
"type": "session.update",
"session": {
"voice": "eve", # any built-in voice or custom voice ID
"instructions": "You are a helpful assistant.",
# Audio format settings (these are the defaults if not specified)
"audio": {
"input": {"format": {"type": "audio/pcm", "rate": 24000}},
"output": {"format": {"type": "audio/pcm", "rate": 24000}}
}
}
}
await ws.send(json.dumps(session_config))音频
当 turn_detection.type 设置为 server_vad 时,我们会执行 Voice Activity Detection(VAD),并自动检测用户何时结束说话。使用 server VAD 时,只需要 input_audio_buffer.append event。
否则,需要在用户结束说话后发送 commit event,并使用 clear 丢弃所有已 append 但尚未 commit 的音频。
配置音频格式
在 audio session 参数 中指定 audio codec 和 sample rate。输入与输出分别指定,无需保持一致。Codec(format)独立于 wire path(transport);请参阅 音频传输。
| 格式 | Encoding | Container 类型 | Sample Rate |
|---|---|---|---|
audio/pcm(默认) | Linear16,Little-endian | Raw、WAV、AIFF | 可配置(见下文) |
audio/pcmu | G.711 μ-law(Mulaw) | Raw | 8000 Hz |
audio/pcma | G.711 A-law | Raw | 8000 Hz |
audio/opus | Opus | Raw packet(每个 payload 一个 packet) | 24000 Hz |
使用 audio/pcm 格式时,可将 sample rate 配置为以下任一支持值:
| Sample Rate | 质量 | 说明 |
|---|---|---|
| 8000 Hz | 电话音频 | Narrowband,适合语音通话 |
| 16000 Hz | Wideband | 适合语音识别 |
| 22050 Hz | 标准 | 质量与带宽均衡 |
| 24000 Hz(默认) | 高 | 推荐用于大多数场景 |
| 32000 Hz | 很高 | 提升音频清晰度 |
| 44100 Hz | CD 质量 | 音乐 / 媒体的标准规格 |
| 48000 Hz | 专业级 | 录音室级音频 |
可以在 session 配置中分别设置输入与输出的音频格式和 sample rate:
# Configure audio format with custom sample rate for input and output
session_config = {
"type": "session.update",
"session": {
"audio": {
"input": {
"format": {
"type": "audio/pcm", # or "audio/pcmu" or "audio/pcma"
"rate": 16000 # Only applicable for audio/pcm
}
},
"output": {
"format": {
"type": "audio/pcm", # or "audio/pcmu" or "audio/pcma"
"rate": 16000 # Only applicable for audio/pcm
}
}
},
"instructions": "You are a helpful assistant.",
}
}
await ws.send(json.dumps(session_config))音频传输
format 用于选择 codec。transport 用于选择这些 byte 在 WebSocket 上的传输方式:
| 输入 | 输出 | |
|---|---|---|
json(默认) | 在 input_audio_buffer.append | 在 response.output_audio.delta / response.audio.delta |
binary | 将 raw codec byte 作为 WebSocket binary frame(无 protocol header) | 同样使用 binary frame;lifecycle event(response.created、response.done、transcript 等)仍使用 JSON |
省略 transport(或设置为 "json"),即可保持现有 client 不变。
输入双通道接受:配置输入格式后,server 会同时接受该 codec 的 两种方式,即 JSON append 和 binary frame。请使用 input.transport 作为 client 的首选发送路径;无需在 session 中途先 drain 一个 channel 再使用另一个。
输出是严格单通道的:assistant 音频只会通过 output.transport 输出。在 session 中途更改 output.transport 会在下一个 response 边界生效,因此单个 utterance 绝不会混用 JSON delta 和 binary frame。
Opus:每个 JSON delta / audio 字段或每个 binary frame 都是一个 raw Opus packet(24 kHz mono)。Binary frame 不包含额外的 framing header。
示例:两个方向均通过 binary 传输 PCM:
session_config = {
"type": "session.update",
"session": {
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transport": "binary",
},
"output": {
"format": {"type": "audio/pcm", "rate": 24000},
"transport": "binary",
},
},
},
}
await ws.send(json.dumps(session_config))
# Send mic audio as raw PCM16 little-endian frames (not base64 JSON)
await ws.send(pcm16_bytes) # WebSocket binary message
# Receive: binary messages are audio; text messages are JSON events
async for message in ws:
if isinstance(message, bytes):
# raw PCM16 (or Opus packets if format is audio/opus)
play(message)
else:
event = json.loads(message)
# response.done, transcripts, etc.接收并播放音频
当 output.transport 为 "json"(默认)时,解码并播放从 API 收到的 base64 PCM16 音频。请使用与配置相同的 sample rate。对于 transport: "binary",直接播放 binary frame payload(相同的 codec byte,不使用 base64)。
import base64
import numpy as np
# Configure session with 16kHz sample rate for lower bandwidth (input and output)
session_config = {
"type": "session.update",
"session": {
"instructions": "You are a helpful assistant.",
"voice": "eve",
"turn_detection": {
"type": "server_vad",
},
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 16000 # 16kHz for lower bandwidth usage
}
},
"output": {
"format": {
"type": "audio/pcm",
"rate": 16000 # 16kHz for lower bandwidth usage
}
}
}
}
}
await ws.send(json.dumps(session_config))
# When processing audio, use the same sample rate
SAMPLE_RATE = 16000
# Convert audio data to PCM16 and base64
def audio_to_base64(audio_data: np.ndarray) -> str:
"""Convert float32 audio array to base64 PCM16 string."""
# Normalize to [-1, 1] and convert to int16
audio_int16 = (audio_data * 32767).astype(np.int16)
# Encode to base64
audio_bytes = audio_int16.tobytes()
return base64.b64encode(audio_bytes).decode('utf-8')
# Convert base64 PCM16 to audio data
def base64_to_audio(base64_audio: str) -> np.ndarray:
"""Convert base64 PCM16 string to float32 audio array."""
# Decode base64
audio_bytes = base64.b64decode(base64_audio)
# Convert to int16 array
audio_int16 = np.frombuffer(audio_bytes, dtype=np.int16)
# Normalize to [-1, 1]
return audio_int16.astype(np.float32) / 32768.0发音替换
使用 replace 参数修正 model 对特定单词或短语的发音。每个 key 都会在 model 输出中进行不区分大小写的匹配,并在 text-to-speech 之前替换为对应值,因此只有语音会改变,用户看到的 transcript 仍保留原始文本。
这适用于 model 发音不正确的品牌名、缩写或领域术语。例如,将 "Acme Mobile" 映射为 "Acme Mobull" 可以让音频正确发音,而 transcript 仍显示 "Acme Mobile"。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "eve",
"instructions": "You are a helpful assistant.",
"replace": {"Acme Mobile": "Acme Mobull"}
}
}))匹配行为:
匹配不区分大小写;替代内容会按你提供的大小写形式朗读。
必须匹配完整单词边界,因此
Acme, Mobile、Acme-Mobile和Acme Mobiles不会匹配。当多个 key 具有相同 prefix 时,最长匹配优先。
可在 session 中途通过另一个
session.update更新映射;已应用的映射会通过session.updated。
支持的语言
Speech to Speech API 支持 20 多种语言,并提供母语级口音。Model 会自动检测输入语言,并使用相同语言自然回应,无需配置。
| 语言 | Code |
|---|---|
| 英语 | 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 也能使用上述列表之外的其他语言进行对话,准确度因语言而异。你可以在 system instructions 中指定首选语言或口音,以获得一致的多语言体验。
Language Hint
通过设置 audio.input.transcription.language_hint(位于 session.update 中)引导 transcription 偏向特定语言。请使用 支持的语言 表格中的 BCP-47 code。可在 session 中途更改。
对于西班牙语和葡萄牙语,必须指定地区变体(例如 "es-MX"、"es-ES"、"pt-BR"、"pt-PT"),不接受单独的 "es" 和 "pt"。无法识别的 code 会被静默忽略,并回退到自动语言检测。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"audio": {
"input": {
"transcription": {
"language_hint": "ja"
}
}
}
}
}))Keyterm
通过设置 audio.input.transcription.keyterms(位于 session.update),引导 transcription 偏向领域专用词汇,例如 model 可能误转录的产品名、专有名词、品牌名或技术术语。请提供 string array,最多 100 个 term,每个 term 最多 50 个字符。Keyterm 可在 session 中途更新。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"audio": {
"input": {
"transcription": {
"keyterms": ["xAI", "Grok", "Understand The Universe"]
}
}
}
}
}))在 Grok Speech to Speech API 中使用 Tool
Grok Speech to Speech API 支持多种可在 session 中配置的 tool,用于增强 voice Agent 的能力。可在 session.update message 中配置 tool。
可用的 Tool 类型
Collections Search(
file_search),搜索已上传的文档 collectionWeb Search(
web_search),搜索 Web 以获取最新信息X Search(
x_search),在 X(Twitter)中搜索帖子和信息Remote MCP Tool(
mcp),连接外部 MCP(Model Context Protocol) server 以使用 custom toolCustom Function,使用 JSON schema 定义自己的 function tool
使用 file_search
使用 file_search tool,让 voice Agent 能够搜索文档 collection。需要先通过 Collections API。
COLLECTION_ID = "your-collection-id" # Replace with your collection ID
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "file_search",
"vector_store_ids": [COLLECTION_ID],
"max_num_results": 10,
},
],
},
}Web Search 与 X Search
配置 web search 和 X search tool,让 voice Agent 能够访问 Web 和 X(Twitter)上的最新信息。两种 tool 均在服务端运行,只需将它们列入 session.tools 即可启用;还可选择使用与 text API Web Search 和 X Search tool 相同的过滤参数。
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "web_search",
"allowed_domains": ["x.ai", "docs.x.ai"],
"location": {"country": "US", "city": "San Francisco"},
},
{
"type": "x_search",
"allowed_x_handles": ["xai"],
"from_date": "2025-01-01",
"to_date": "2025-06-01",
},
],
},
}Web Search 参数
| 参数 | 必需 | 说明 |
|---|---|---|
allowed_domains | 否 | 仅包含来自这些 domain 的结果(不含 protocol 或 path,例如 example.com)。最多 5 个。与 excluded_domains 互斥,不要在同一个 tool 中同时设置二者。 |
excluded_domains | 否 | 排除来自这些 domain 的结果。最多 5 个。与 allowed_domains 互斥,不要在同一个 tool 中同时设置二者。 |
enable_image_understanding | 否 | 允许 Agent 查看 web search 中找到的图像。 |
location | 否 | 按位置引导结果:country(ISO 3166-1 alpha-2 或完整名称)、city、region、timezone(IANA,例如 America/Los_Angeles)。也接受 text API 中使用的名称 user_location。 |
X Search 参数
| 参数 | 必需 | 说明 |
|---|---|---|
allowed_x_handles | 否 | 仅包含来自这些 X handle 的帖子(不含 @)。最多 20 个。与 excluded_x_handles 互斥,不要在同一个 tool 中同时设置二者。 |
excluded_x_handles | 否 | 排除来自这些 X handle 的帖子。最多 20 个。与 allowed_x_handles 互斥,不要在同一个 tool 中同时设置二者。 |
from_date | 否 | 仅考虑从该日期开始的帖子,使用 ISO-8601 YYYY-MM-DD。不得晚于 to_date。 |
to_date | 否 | 仅考虑截至该日期的帖子,使用 ISO-8601 YYYY-MM-DD。 |
enable_image_understanding | 否 | 允许 Agent 查看帖子中的图像。 |
enable_video_understanding | 否 | 允许 Agent 查看帖子中的视频。 |
无效配置,例如条目过多、同时设置 allowed_* 和 excluded_*,或日期窗口格式错误 / 前后颠倒,会通过 error event 被拒绝并说明问题。Session 会保持连接,先前配置仍然有效。
Remote MCP Tool
使用 mcp tool 类型,将 voice Agent 连接到外部 MCP(Model Context Protocol) server。这样可以通过第三方或 custom tool 扩展 voice Agent,而无需将其实现为 client-side function;xAI 会代为管理 MCP server 连接和 tool 执行。
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "mcp",
"server_url": "https://mcp.example.com/mcp",
"server_label": "my-tools",
},
],
},
}MCP Tool 参数
| 参数 | 必需 | 说明 |
|---|---|---|
server_url | 是 | MCP server 的 URL。仅支持 Streaming HTTP 和 SSE transport。 |
server_label | 是 | 用于标识 server 的 label(用于 tool call prefix)。 |
server_description | 否 | Server 所提供内容的说明。 |
allowed_tools | 否 | 允许使用的特定 tool 名称列表。省略时,server 中的所有 tool 均可用。 |
authorization | 否 | 在发往 MCP server 的请求中,通过 Authorization header 设置的 token。 |
headers | 否 | 发往 MCP server 的请求中要包含的额外 header。 |
高级 MCP 配置
可以限制可用 tool、提供身份验证并添加 custom header:
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "mcp",
"server_url": "https://mcp.example.com/mcp",
"server_label": "my-tools",
"server_description": "Custom business tools for order management",
"allowed_tools": ["lookup_order", "check_inventory"],
"authorization": "Bearer your-token-here",
"headers": {
"X-Custom-Header": "value"
},
},
],
},
}多个 MCP Server
可以同时连接多个 MCP server,每个 server 提供不同能力:
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "mcp",
"server_url": "https://mcp.deepwiki.com/mcp",
"server_label": "deepwiki",
},
{
"type": "mcp",
"server_url": "https://your-tools.example.com/mcp",
"server_label": "custom-tools",
"allowed_tools": ["search_database", "format_data"],
},
],
},
}Custom Function Tool
可以使用 JSON schema 定义 custom function tool,以扩展 voice Agent 的能力。
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "function",
"name": "generate_random_number",
"description": "Generate a random number between min and max values",
"parameters": {
"type": "object",
"properties": {
"min": {
"type": "number",
"description": "Minimum value (inclusive)",
},
"max": {
"type": "number",
"description": "Maximum value (inclusive)",
},
},
"required": ["min", "max"],
},
},
],
},
}组合多种 Tool
可以在同一个 session 配置中组合多种 tool 类型,包括 server-side tool(web search、X search、collections、MCP)和 client-side function tool:
session_config = {
"type": "session.update",
"session": {
...
"tools": [
{
"type": "file_search",
"vector_store_ids": ["your-collection-id"],
"max_num_results": 10,
},
{
"type": "web_search",
},
{
"type": "x_search",
},
{
"type": "mcp",
"server_url": "https://mcp.example.com/mcp",
"server_label": "my-tools",
},
{
"type": "function",
"name": "generate_random_number",
"description": "Generate a random number",
"parameters": {
"type": "object",
"properties": {
"min": {"type": "number"},
"max": {"type": "number"},
},
"required": ["min", "max"],
},
},
],
},
}处理 Function Call Response
定义 custom function tool 后,voice Agent 会在对话中调用这些 function。需要处理并执行这些 function call,然后返回结果以继续对话。
Function Call 流程
Agent 决定调用 function,发送
response.function_call_arguments.doneevent你的代码执行 function,处理 argument 并生成结果
将结果返回给 Agent,发送
conversation.item.create,其中包含 function output请求继续执行,发送
response.create,让 Agent 继续
完整示例
import json
import websockets
# Define your function implementations
def get_weather(location: str, units: str = "celsius"):
"""Get current weather for a location"""
# In production, call a real weather API
return {
"location": location,
"temperature": 22,
"units": units,
"condition": "Sunny",
"humidity": 45
}
def book_appointment(date: str, time: str, service: str):
"""Book an appointment"""
# In production, interact with your booking system
import random
confirmation = f"CONF{random.randint(1000, 9999)}"
return {
"status": "confirmed",
"confirmation_code": confirmation,
"date": date,
"time": time,
"service": service
}
# Map function names to implementations
FUNCTION_HANDLERS = {
"get_weather": get_weather,
"book_appointment": book_appointment
}
async def handle_function_call(ws, event):
"""Handle function call from the voice agent"""
function_name = event["name"]
call_id = event["call_id"]
arguments = json.loads(event["arguments"])
print(f"Function called: {function_name} with args: {arguments}")
# Execute the function
if function_name in FUNCTION_HANDLERS:
result = FUNCTION_HANDLERS[function_name](**arguments)
# Send result back to agent
await ws.send(json.dumps({
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": call_id,
"output": json.dumps(result)
}
}))
# Request agent to continue with the result
await ws.send(json.dumps({
"type": "response.create"
}))
else:
print(f"Unknown function: {function_name}")
# In your WebSocket message handler
async def on_message(ws, message):
event = json.loads(message)
# Listen for function calls
if event["type"] == "response.function_call_arguments.done":
await handle_function_call(ws, event)
elif event["type"] == "response.output_audio.delta":
# Handle audio response
passFunction Call Event
| Event | 方向 | 说明 |
|---|---|---|
response.function_call_arguments.done | Server → Client | 触发 function call,并包含完整 argument |
conversation.item.create(function_call_output) | Client → Server | 返回 function 执行结果 |
response.create | Client → Server | 请求 Agent 继续处理 |
并行 Tool Calling
当 model 判断需要多个 function call 才能完成请求时,会在任何音频 response 之前发出多个 response.function_call_arguments.done event。在这种情况下,必须处理 全部 function call 并返回结果,然后才能发出 response.create。
预期行为:
接收多个
response.function_call_arguments.doneevent(每个 function call 一个)执行所有 function(可并行执行以提高性能)
为
conversation.item.create发送包含function_call_output的 每个 function call仅在发送所有 function output 后,发出一条
response.create以继续
Force Message
使用 force_message 让 Agent 说出一段硬编码、由 TTS 合成的语句,无需调用 model。这适用于预设问候语、合规声明(例如“本次通话正在录音”)、IVR prompt 或任何必须逐字传达的 utterance。
为conversation.item.create event,并将 item.type 设置为 "force_message":
await ws.send(json.dumps({
"type": "conversation.item.create",
"item": {
"type": "force_message",
"role": "assistant",
"interruptible": False,
"content": [{"type": "output_text", "text": "This call is being recorded."}]
}
}))
# Do NOT send response.create — the force_message IS the turn.| 字段 | 必需 | 默认值 | 说明 |
|---|---|---|---|
item.type | 是 | — | 必须为 "force_message" |
item.content[].text | 是 | — | 要通过 TTS 逐字合成的文本 |
item.interruptible | 否 | true | 当 false 时,在播放完成前会丢弃 caller 音频 |
Server 会注入完整的 response lifecycle(response.created → response.output_audio.delta → response.done),因此 force message 在 client 看来与普通 model turn 相同。
Per-Response Instructions
通过在 instructions 上设置 response.create:
await ws.send(json.dumps({
"type": "response.create",
"response": {
"instructions": "Respond in Spanish for this turn only."
}
})),可以为单个 response 覆盖 session-level system prompt。该覆盖仅适用于当前 response,后续 response 会恢复使用 session instructions。这适用于注入动态 context(例如 CRM data、caller 信息),或在不更新 session 的情况下临时改变行为。
Session Resumption
默认情况下,/v1/realtime 连接会在 WebSocket 关闭时丢失 conversation history。Session resumption 会缓存每个 turn,并在重新连接时 replay 先前 context,使 model 继续基于之前的对话。
要跨连接继续 session,请保存 server 的 conversation.created.conversation.id,并在重新连接时将其作为 ?conversation_id=<id> 传回(同时保持相同的 opt-in)。
连接并选择启用,发送
resumption.enabled: true上设置session.update。从conversation.created中读取并保存分配的 ID。使用该 ID 重新连接。使用
?conversation_id=<id>重新打开 WebSocket 并再次选择启用。缓存的 turn 会在第一个新 turn 前 replay,并以conversation.item.createdevent 的形式返回。
import json, websockets
# resume_id is None on a fresh conversation; pass the saved id to resume.
async def connect(resume_id=None):
url = f"wss://api.x.ai/v1/realtime?model={MODEL}"
if resume_id:
url += f"&conversation_id={resume_id}"
async with websockets.connect(url, additional_headers=headers) as ws:
# Opt in to resumption (required to cache and to replay).
await ws.send(json.dumps({
"type": "session.update",
"session": {"resumption": {"enabled": True}},
}))
async for raw in ws:
event = json.loads(raw)
if event["type"] == "conversation.created":
# Server-assigned id. Save it and pass it as
# ?conversation_id= on your next connect to resume.
saved_id = event["conversation"]["id"]
# ... handle the rest of the session会持久化并 replay:用户与 assistant transcript、assistant tool call,以及你的 function_call_output result。
两端都需要 opt-in。只有恢复的 session 也发送
resumption.enabled: true。过期时间。处于非活动状态 30 分钟后,history 会被删除。
最佳实践
本节介绍使用 xAI Speech to Speech API 构建低延迟、可靠且体验自然的语音应用时应遵循的关键建议。
通过并行初始化降低感知延迟
并行启动 WebSocket 连接和麦克风输入 streaming。
建立 WebSocket 连接(包括通过 ephemeral token 或 API key 进行身份验证)的时间应尽可能早,最好在语音界面加载或用户打开启用麦克风的页面时进行。
同时开始采集麦克风音频(浏览器中使用
getUserMedia,移动端 / native 平台使用等价 API)。请不会不要等到 WebSocket
openevent 后才开始收集麦克风 sample。
音频 Buffering 示例
// 1. Immediately request mic access and start capturing
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext({ sampleRate: 24000 });
const source = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(4096, 1, 1); // or AudioWorklet for better perf
source.connect(processor);
processor.connect(audioContext.destination); // optional
// Buffer incoming PCM data immediately
let earlyAudioBuffer = []; // Float32Array[] or Int16Array[]
processor.onaudioprocess = (e) => {
const input = e.inputBuffer.getChannelData(0);
earlyAudioBuffer.push(new Float32Array(input)); // or convert to PCM16
};
// 2. In parallel – connect WebSocket (may take time)
const ws = new WebSocket("wss://api.x.ai/v1/realtime?model=grok-voice-latest", [
`xai-client-secret.${token}`,
]);
ws.onopen = () => {
// Send session.update configuration
ws.send(JSON.stringify({ type: "session.update", session: { ... } }));
// Flush any buffered audio now that we're connected
if (earlyAudioBuffer.length > 0) {
flushBufferedAudioToWS(earlyAudioBuffer);
earlyAudioBuffer = [];
}
};生产环境建议
在 buffering 或 flush 前转换为 24 kHz PCM16 little-endian。
使用大小合理的 message 进行 flush(每个包含 100ms sample),以实现流畅传输。
重新连接时立即恢复 buffering。
避免 Tool Call 期间音频重叠
当 model 在语音 response 期间调用 tool 时,server 会先发送全部 audio delta,然后发送 function call event 和 response.done。如果 client 立即发送 conversation.item.create(包含 function result),随后发送 response.create,server 会立即开始生成下一个 response,即使 client 仍在播放上一 turn 的音频,从而导致音频重叠。
推荐顺序:
接收
response.function_call_arguments.done,然后执行 tool发送
conversation.item.create,其中包含function_call_output等待当前 turn 的音频播放完成(或接近完成)
然后发送
response.create
等待播放完成时,显示可视化的“思考中”指示器(例如动画圆点),让用户知道 Agent 正在处理。这样可在 model 的语音 response 与 tool result 之后的后续 response 之间形成自然停顿。
ws.on("message", async (message) => {
const event = JSON.parse(message);
if (event.type === "response.function_call_arguments.done") {
// 1. Execute the tool
const result = await executeFunction(event.name, JSON.parse(event.arguments));
// 2. Send the function result immediately
ws.send(JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
// 3. Show a "thinking" indicator in the UI
showThinkingIndicator();
// 4. Wait for current audio playback to finish
await waitForPlaybackComplete();
// 5. Now request the next response
ws.send(JSON.stringify({ type: "response.create" }));
hideThinkingIndicator();
}
});
其他重要建议
优先使用 ephemeral token,保障 client-side 安全。
启用
server_vad,实现自动且自然的 barge-in。保持输入 / 输出格式一致(24 kHz PCM),避免 resampling。
立即 streaming 输出 audio delta(
response.output_audio.delta)到扬声器,不要等待完整 response。实现平滑重连,同时继续 buffer 新音频。
监控 WebSocket 健康状态,并在需要时使用 exponential backoff。
为企业级 Voice 构建
电话系统集成,通过 SIP、WebSocket 或 LiveKit 连接。原生支持 G.711 μ-law/A-law codec,无 transcoding 开销。
Tool Calling,在实时对话期间通过 function calling 连接 CRM、日历、数据库以及任意 REST 或 GraphQL endpoint。
20 多种语言,支持自然发音、口音处理,以及在同一对话中无缝切换语言。
领域专业能力,精准转录医疗、法律、金融和技术术语,包括姓名、code 和地址。
SIP 电话呼叫
将 PSTN、contact center 或 PBX 呼叫路由到 Speech to Speech API session。请参阅 SIP 电话呼叫,了解使用 CreatePhoneNumberV2 进行 API 集成、call control、DTMF 和电话服务商示例。
从 OpenAI Realtime 迁移
如果已有基于 OpenAI Realtime API 构建的应用,只需进行少量更改即可切换到 Grok Speech to Speech API:更新 base URL、更换 API key,并选择 Grok voice model。
第 1 步:更新 Base URL 和 API Key
使用 OpenAI SDK
如果使用官方 OpenAI SDK,请将 client 指向 xAI endpoint,并提供 xAI API key:
import asyncio
from openai import AsyncOpenAI
# Before (OpenAI)
# client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
# After (xAI)
client = AsyncOpenAI(
api_key=os.environ["XAI_API_KEY"],
base_url="https://api.x.ai/v1",
)
async def main():
async with client.realtime.connect(
model="grok-voice-latest"
) as conn:
await conn.session.update(session={
"voice": "eve",
"instructions": "You are a helpful assistant.",
"turn_detection": {"type": "server_vad"},
})
# ... rest of your application code
asyncio.run(main())使用 Raw WebSocket
如果通过 WebSocket 直接连接,请更改 URL 和 Authorization header:
import os
import websockets
# Before (OpenAI)
# url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview"
# headers = {"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"}
# After (xAI)
url = "wss://api.x.ai/v1/realtime?model=grok-voice-latest"
headers = {"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"}
async with websockets.connect(url, additional_headers=headers) as ws:
# Your existing event handling code works as-is
pass第 2 步:选择 Model
建立连接时传入 model 名称:
# Pass the model in connect()
async with client.realtime.connect(model="grok-voice-latest") as conn:
...第 3 步:Model 特定最佳实践
grok-voice-think-fast-2.0(推荐)
这是最新的 voice model。新集成请使用 grok-voice-latest,使应用跟随当前推荐 model。迁移时:
简化 system prompt。该 model 的能力显著增强,因此 prompt 应大幅缩短。请让 Grok 对现有 system prompt 进行概括,而不是逐字迁移。
移除 workaround prompt。不再需要为 GPT model 编写的 prompt hack 和 edge case 修复。请删除仅为修补上一 model 的 bug 或限制而添加的 instructions。
Reasoning 默认启用。默认的
reasoning.effort为"high"适用于复杂的多步 instructions、细腻语气和模糊 query。将其设置为"none"可禁用 reasoning。
OpenAI Realtime API 兼容性
Grok Speech to Speech API 与 OpenAI Realtime API 兼容。大多数 OpenAI client library 和 SDK 只需将 base URL 更改为 wss://api.x.ai/v1/realtime,即可使用 xAI endpoint。本节记录 event 命名差异和不支持的 event。
Event 命名差异
对于少数 payload 不同的 event,xAI API 使用不同的 event 名称:
OpenAI 的
conversation.item.input_audio_transcription.delta在 xAI API 中名为conversation.item.input_audio_transcription.updated。updatedevent 包含累计 transcript(可能包含对先前更新的修正),而不是增量 delta。仅在audio.input.transcription.model设置为"grok-transcribe"。
不支持的 Client Event
| OpenAI Event | 说明 |
|---|---|
conversation.item.retrieve | 不支持。 |
output_audio_buffer.clear | 仅限 WebRTC/SIP。 |
不支持的 Server Event
| OpenAI Event | 说明 |
|---|---|
conversation.item.done | 不会发出。 |
conversation.item.input_audio_transcription.failed | 不会发出。 |
conversation.item.input_audio_transcription.segment | 不支持。 |
conversation.item.retrieved | 不支持。 |
output_audio_buffer.started | 仅限 WebRTC/SIP。 |
output_audio_buffer.stopped | 仅限 WebRTC/SIP。 |
output_audio_buffer.cleared | 仅限 WebRTC/SIP。 |
rate_limits.updated | 不会发出。 |
xAI Extension
以下 event 和功能为 xAI 特有,不属于 OpenAI Realtime API:
| Event / 功能 | 说明 |
|---|---|
force_message | 新的 conversation.item.create item 类型,用于由 TTS 合成的预设 utterance。请参阅 Force Message。 |
resumption | 位于 session.update 上的字段,用于缓存 conversation turn 并在重新连接时 replay。请参阅 Session Resumption。 |
replace | 位于 session.update,用于将短语映射到 TTS 前应用的口语替代项,在不更改 transcript 的情况下修正发音。请参阅 发音替换。 |