模型能力

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 开始体验

身份验证

使用以下任一方式验证 WebSocket 连接:

  • Ephemeral Token(推荐),用于 client-side app(浏览器、移动端)的短期 token,可避免在 client 中暴露 API key。

  • API Key,直接在 Authorization header 中传入 xAI API key。仅限服务端使用。

更多信息请参阅 API 文档

Event

WebSocket 建立后,即可开始双向 event 通信。Client event 用于提供对话信息并向 Voice API 发送用户音频,server event 则包含音频和文本 response。

API 文档 →

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。

参数类型说明
instructionsstringSystem prompt
reasoning.effort"high" | "none" | 可选控制 model 是否使用 reasoning。默认为 "high"
voicestringVoice 选择:任意内置 voice(例如 eve)或 custom voice ID(请参阅 可用 Voice
toolsarrayVoice Agent 可用的 tool。支持 file_searchweb_searchx_searchmcpfunction 类型。请参阅 使用 Tool
turn_detection.typestring | null"server_vad" 用于自动检测,null 用于手动文本 turn
turn_detection.thresholdnumber | 可选VAD 激活 threshold(0.1–0.9)。值越高,需要越响的音频才能触发。默认值:0.85
turn_detection.silence_duration_msnumber | 可选Server 结束 turn 前用户必须保持静音的时长(毫秒,0–10000)。值越高,用户可以停顿更久而不会被打断。
turn_detection.prefix_padding_msnumber | 可选在检测到语音开始前一并包含的音频时长(毫秒,0–10000)。有助于捕获原本可能被 VAD 截断的词首。默认值:333
turn_detection.idle_timeout_msnumber | 可选设置后,如果 assistant 完成 response 后在指定毫秒数内未检测到语音,server 会主动重新与用户互动。Timer 会在每次 response 后重新启动,因此会按每个 idle_timeout_ms 周期重复触发,直到用户开始说话。默认值:null
resumption.enabledboolean | 可选选择启用 Session Resumption:server 使用 conversation_id 作为 key 缓存 conversation turn,并在重新连接时 replay,使 model 继续基于先前 context。默认为 false。请参阅 Session Resumption
audio.input.format.typestring输入 codec:"audio/pcm""audio/pcmu""audio/pcma""audio/opus"
audio.input.format.ratenumber输入 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.typestring输出 codec:"audio/pcm""audio/pcmu""audio/pcma""audio/opus"
audio.output.format.ratenumber输出 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_hintstringBCP-47 language code(例如 "ja""ar""es-MX""pt-BR"),用于引导 ASR transcription 偏向特定语言。可在 session 中途更新。请参阅 Language Hint
audio.input.transcription.keytermsarray用于引导 transcription 的 key term 列表(例如产品名、专有名词)。最多 100 个 term,每个最多 50 个字符。可在 session 中途更新。请参阅 Keyterm
audio.output.speednumberAssistant 音频输出的播放速度倍数。范围:0.7–1.5。默认值:1.0。低于 1.0 会减慢语速,高于 1.0 会加快语速。
replaceobject | 可选在 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);请参阅 音频传输

格式EncodingContainer 类型Sample Rate
audio/pcm(默认)Linear16,Little-endianRaw、WAV、AIFF可配置(见下文)
audio/pcmuG.711 μ-law(Mulaw)Raw8000 Hz
audio/pcmaG.711 A-lawRaw8000 Hz
audio/opusOpusRaw packet(每个 payload 一个 packet)24000 Hz

使用 audio/pcm 格式时,可将 sample rate 配置为以下任一支持值:

Sample Rate质量说明
8000 Hz电话音频Narrowband,适合语音通话
16000 HzWideband适合语音识别
22050 Hz标准质量与带宽均衡
24000 Hz(默认)推荐用于大多数场景
32000 Hz很高提升音频清晰度
44100 HzCD 质量音乐 / 媒体的标准规格
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 用于选择 codectransport 用于选择这些 byte 在 WebSocket 上的传输方式:

输入输出
json(默认)input_audio_buffer.appendresponse.output_audio.delta / response.audio.delta
binary将 raw codec byte 作为 WebSocket binary frame(无 protocol header)同样使用 binary frame;lifecycle event(response.createdresponse.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, MobileAcme-MobileAcme 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,搜索已上传的文档 collection

  • Web Search(web_search,搜索 Web 以获取最新信息

  • X Search(x_search,在 X(Twitter)中搜索帖子和信息

  • Remote MCP Tool(mcp,连接外部 MCP(Model Context Protocol) server 以使用 custom tool

  • Custom Function,使用 JSON schema 定义自己的 function tool

使用 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 tool,让 voice Agent 能够访问 Web 和 X(Twitter)上的最新信息。两种 tool 均在服务端运行,只需将它们列入 session.tools 即可启用;还可选择使用与 text API Web SearchX 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 或完整名称)、cityregiontimezone(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_urlMCP server 的 URL。仅支持 Streaming HTTP 和 SSE transport。
server_label用于标识 server 的 label(用于 tool call prefix)。
server_descriptionServer 所提供内容的说明。
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 流程

  1. Agent 决定调用 function,发送 response.function_call_arguments.done event

  2. 你的代码执行 function,处理 argument 并生成结果

  3. 将结果返回给 Agent,发送 conversation.item.create,其中包含 function output

  4. 请求继续执行,发送 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
        pass

Function Call Event

Event方向说明
response.function_call_arguments.doneServer → Client触发 function call,并包含完整 argument
conversation.item.create(function_call_output)Client → Server返回 function 执行结果
response.createClient → Server请求 Agent 继续处理

并行 Tool Calling

当 model 判断需要多个 function call 才能完成请求时,会在任何音频 response 之前发出多个 response.function_call_arguments.done event。在这种情况下,必须处理 全部 function call 并返回结果,然后才能发出 response.create

预期行为:

  1. 接收多个 response.function_call_arguments.done event(每个 function call 一个)

  2. 执行所有 function(可并行执行以提高性能)

  3. conversation.item.create发送包含 function_call_output每个 function call

  4. 仅在发送所有 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.interruptibletruefalse 时,在播放完成前会丢弃 caller 音频

Server 会注入完整的 response lifecycle(response.createdresponse.output_audio.deltaresponse.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)。

  1. 连接并选择启用,发送 resumption.enabled: true 上设置 session.update。从 conversation.created 中读取并保存分配的 ID。

  2. 使用该 ID 重新连接。使用 ?conversation_id=<id> 重新打开 WebSocket 并再次选择启用。缓存的 turn 会在第一个新 turn 前 replay,并以 conversation.item.created event 的形式返回。

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 open event 后才开始收集麦克风 sample。

音频 Buffering 示例

JavaScript

// 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 的音频,从而导致音频重叠。

推荐顺序:

  1. 接收 response.function_call_arguments.done,然后执行 tool

  2. 发送 conversation.item.create,其中包含 function_call_output

  3. 等待当前 turn 的音频播放完成(或接近完成)

  4. 然后发送 response.create

等待播放完成时,显示可视化的“思考中”指示器(例如动画圆点),让用户知道 Agent 正在处理。这样可在 model 的语音 response 与 tool result 之后的后续 response 之间形成自然停顿。

JavaScript

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 deltaresponse.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 特定最佳实践

这是最新的 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.updatedupdated event 包含累计 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 的情况下修正发音。请参阅 发音替换