Model Capabilities
语音转语音
构建由 Grok 驱动的实时语音应用程序。通过 WebSocket 进行双向音频和文本流式传输,用于语音助手、电话代理和交互式语音系统。
快速开始
连接到语音转语音 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())import WebSocket from "ws";
const ws = new WebSocket("wss://api.x.ai/v1/realtime?model=grok-voice-latest", {
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` },
});
/*
Web browsers do not support WebSocket headers. Instead, pass an
Ephemeral Token (prefixed with xai-client-secret.) in the WebSocket protocol.
const ws = new WebSocket("wss://api.x.ai/v1/realtime",
[`xai-client-secret.${XAI_EPHEMERAL_TOKEN}`]);
*/
ws.on("open", () => {
// Configure session
ws.send(JSON.stringify({
type: "session.update",
session: {
voice: "eve",
instructions: "You are a helpful assistant.",
turn_detection: { type: "server_vad" }
}
}));
// Send a text message
ws.send(JSON.stringify({
type: "conversation.item.create",
item: { type: "message", role: "user",
content: [{ type: "input_text", text: "Hello!" }] }
}));
ws.send(JSON.stringify({ type: "response.create" }));
});
ws.on("message", (data) => {
const event = JSON.parse(data);
console.log("Event:", event.type);
});使用我们的测试应用开始
- iOS 测试应用 — 基于 Swift 的 iOS 应用,作为在您的应用中设置语音代理的指南。
- Web 代理 (WebSocket) — 使用 WebSocket 的 Web 应用语音代理。
- WebRTC 代理 — 使用 WebRTC 的 Web 应用语音代理。
- 电话代理 — 使用 Twilio 的可呼叫电话代理。
身份验证
使用以下任一方法验证您的 WebSocket 连接:
- 临时令牌 (推荐) — 用于客户端应用(浏览器、移动设备)的短期令牌。使您的 API 密钥不会出现在客户端。
- API Key — 直接在
Authorization头中传递您的 xAI API 密钥。仅限服务端使用。
更多详情请参阅我们的 API 文档。
事件
WebSocket 连接打开后,双向事件即可开始。客户端事件用于提供对话信息并将用户音频发送到语音 API,而服务器事件包括音频和文本响应。
模型选择
将 model 作为查询参数传递;使用版本化名称以固定到特定版本。
MODEL = "grok-voice-latest"
url = f"wss://api.x.ai/v1/realtime?model={MODEL}"const MODEL = "grok-voice-latest";
const url = `wss://api.x.ai/v1/realtime?model=${MODEL}`;| 模型 | 描述 |
|---|---|
grok-voice-latest | grok-voice-think-fast-1.0 的别名。将于 2026 年 8 月 5 日更新为 grok-voice-think-fast-2.0。 |
grok-voice-think-fast-2.0 | 主打语音模型 |
grok-voice-think-fast-1.0 | 前一代语音模型 |
会话参数
会话创建后,客户端可以发送 session.update 事件来配置会话。
| 参数 | 类型 | 描述 |
|---|---|---|
instructions | string | 系统提示 |
reasoning.effort | "high" | "none" |
voice | string | 语音选择:任何内置语音(如 eve)或自定义语音 ID(参见可用语音) |
tools | array | 语音代理可用的工具。支持 file_search、web_search、x_search、mcp 和 function 类型。参见使用工具。 |
turn_detection.type | string | null |
turn_detection.threshold | number | 可选 |
turn_detection.silence_duration_ms | number | 可选 |
turn_detection.prefix_padding_ms | number | 可选 |
turn_detection.idle_timeout_ms | number | 可选 |
resumption.enabled | boolean | 可选 |
audio.input.format.type | string | 输入编解码器:"audio/pcm"、"audio/pcmu"、"audio/pcma" 或 "audio/opus" |
audio.input.format.rate | number | 输入采样率(仅 PCM):8000、16000、22050、24000、32000、44100、48000 |
audio.input.transport | "json" | "binary" |
audio.output.format.type | string | 输出编解码器:"audio/pcm"、"audio/pcmu"、"audio/pcma" 或 "audio/opus" |
audio.output.format.rate | number | 输出采样率(仅 PCM):8000、16000、22050、24000、32000、44100、48000 |
audio.output.transport | "json" | "binary" |
audio.input.transcription.language_hint | string | 偏向特定语言的 BCP-47 语言代码(如 "ja"、"ar"、"es-MX"、"pt-BR")以优化 ASR 转录。可以在会话中更新。参见语言提示。 |
audio.input.transcription.keyterms | array | 关键词列表(如产品名称、专有名词)以优化转录。最多 100 个术语,每个最多 50 个字符。可以在会话中更新。参见关键词。 |
audio.output.speed | number | 助手音频输出的播放速度乘数。范围:0.7–1.5。默认值:1.0。低于 1.0 的值会减慢语音;高于 1.0 的值会加快语音。 |
replace | object | 可选 |
可用语音
相同的语音列表适用于语音转语音 API 和文本转语音 API。在语音表中浏览完整列表,包含语调描述和样本,或通过 GET /v1/tts/voices 以编程方式获取。将小写语音 ID 作为 voice 参数传递给 session.update。
自定义语音
需要列表中没有的语音吗?使用自定义语音 API 从简短的参考片段中克隆任何语音。生成的 voice_id 可以像内置语音一样用作 session.update 的 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))// Configure session with a specific voice
const sessionConfig = {
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 } }
}
}
};
ws.send(JSON.stringify(sessionConfig));音频
当 turn_detection.type 设置为 server_vad 时,我们将执行语音活动检测 (VAD) 并自动检测用户何时停止说话。如果您使用服务器 VAD,只需要 input_audio_buffer.append 事件。
否则,用户停止说话后,您需要发送 commit 事件,并使用 clear 来丢弃已附加但尚未提交的所有音频。
配置音频格式
在 audio 会话参数 中指定音频编解码器和采样率。输入和输出单独指定,不需要匹配。编解码器 (format) 与传输路径 (transport) 独立;参见音频传输。
| 格式 | 编码 | 容器类型 | 采样率 |
|---|---|---|---|
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 packets (one packet per payload) | 24000 Hz |
使用 audio/pcm 格式时,您可以将采样率配置为以下支持的值之一:
| 采样率 | 质量 | 描述 |
|---|---|---|
| 8000 Hz | 电话 | 窄带,适合语音通话 |
| 16000 Hz | 宽带 | 适合语音识别 |
| 22050 Hz | 标准 | 平衡的质量和带宽 |
| 24000 Hz (默认) | 高 | 推荐用于大多数用例 |
| 32000 Hz | 非常高 | 增强的音频清晰度 |
| 44100 Hz | CD 质量 | 音乐/媒体的标准 |
| 48000 Hz | 专业 | 录音棚级音频 |
您可以在会话配置中为输入和输出配置音频格式和采样率:
# 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))// Configure audio format with custom sample rate for input and output
const sessionConfig = {
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.",
}
};
ws.send(JSON.stringify(sessionConfig));音频传输
format 选择编解码器。transport 选择这些字节如何在 WebSocket 上传输:
| 输入 | 输出 | |
|---|---|---|
json (默认) | input_audio_buffer.append 中的 Base64 | response.output_audio.delta / response.audio.delta 中的 Base64 |
binary | 原始编解码器字节作为 WebSocket 二进制帧(无协议头) | 相同的二进制帧;生命周期事件(response.created、response.done、转录等)保持 JSON |
省略 transport(或设置为 "json")以保持现有客户端不变。
输入双接受:当配置输入格式时,服务器接受该编解码器的两者 JSON 附加和二进制帧。使用 input.transport 作为客户端的首选发送路径;您不需要在会话中途排空一个通道后再使用另一个。
输出是严格的:助手音频仅在 output.transport 上发出。在会话中途更改 output.transport 会在下一个响应边界处应用,因此单个语音绝不会混合 JSON 增量和二进制帧。
Opus:每个 JSON delta / audio 字段或每个二进制帧都是一个原始 Opus 数据包(24 kHz 单声道)。二进制帧上没有额外的帧头。
示例:双向使用二进制 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.const sessionConfig = {
type: "session.update",
session: {
audio: {
input: {
format: { type: "audio/pcm", rate: 24000 },
transport: "binary",
},
output: {
format: { type: "audio/pcm", rate: 24000 },
transport: "binary",
},
},
},
};
ws.send(JSON.stringify(sessionConfig));
// Send mic audio as raw PCM16 little-endian frames
ws.send(pcm16ArrayBuffer); // binary WebSocket frame
ws.binaryType = "arraybuffer";
ws.on("message", (data) => {
if (data instanceof ArrayBuffer || Buffer.isBuffer(data)) {
// raw PCM16 (or Opus packets if format is audio/opus)
play(data);
return;
}
const event = JSON.parse(data.toString());
// response.done, transcripts, etc.
});接收和播放音频
当 output.transport 为 "json"(默认)时,解码并播放从 API 接收的 base64 PCM16 音频。使用与配置相同的采样率。对于 transport: "binary",直接播放二进制帧负载(相同的编解码器字节,无需 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// Configure session with 16kHz sample rate for lower bandwidth (input and output)
const sessionConfig = {
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
}
}
}
}
};
ws.send(JSON.stringify(sessionConfig));
// When processing audio, use the same sample rate
const SAMPLE_RATE = 16000;
// Create AudioContext with matching sample rate
const audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
// Helper function to convert Float32Array to base64 PCM16
function float32ToBase64PCM16(float32Array) {
const pcm16 = new Int16Array(float32Array.length);
for (let i = 0; i < float32Array.length; i++) {
const s = Math.max(-1, Math.min(1, float32Array[i]));
pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
}
const bytes = new Uint8Array(pcm16.buffer);
return btoa(String.fromCharCode(...bytes));
}
// Helper function to convert base64 PCM16 to Float32Array
function base64PCM16ToFloat32(base64String) {
const binaryString = atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const pcm16 = new Int16Array(bytes.buffer);
const float32 = new Float32Array(pcm16.length);
for (let i = 0; i < pcm16.length; i++) {
float32[i] = pcm16[i] / 32768.0;
}
return float32;
}发音替换
使用 replace 参数来修正模型如何发音特定的单词或短语。每个键在模型的输出中匹配(不区分大小写)并在之前替换为其替换值进行文本转语音—因此只有口语音频发生变化;用户看到的转录本保持原始文本。
这对于品牌名称、缩写或模型发音错误的域名术语很有用。例如,将 "Acme Mobile" 映射到 "Acme Mobull" 可以使音频正确发音,而转录本仍然显示 "Acme Mobile"。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "eve",
"instructions": "You are a helpful assistant.",
"replace": {"Acme Mobile": "Acme Mobull"}
}
}))ws.send(JSON.stringify({
type: "session.update",
session: {
voice: "eve",
instructions: "You are a helpful assistant.",
replace: { "Acme Mobile": "Acme Mobull" }
}
}));匹配行为:
- 匹配不区分大小写;替换值使用您提供的大小写发音。
- 需要完整单词边界,因此
Acme, Mobile、Acme-Mobile和Acme Mobiles不匹配。 - 当多个键共享前缀时,最长匹配获胜。
- 可以使用另一个
session.update在会话中途更新映射;应用的映射会在session.updated上回显。
支持的语言
语音转语音 API 支持 20 多种语言,具有原生口音。模型自动检测输入语言并以相同语言自然响应—无需配置。
| 语言 | 代码 |
|---|---|
| 英语 | 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 |
模型还能够与上述列表之外的其他语言进行对话,准确度不同。您可以在系统指令中指定首选语言或口音,以实现一致的多语言体验。
语言提示
通过在 session.update 中设置 audio.input.transcription.language_hint 来偏向特定语言转录。使用支持的语言表中的 BCP-47 代码。可以在会话中途更改。
对于西班牙语和葡萄牙语,您必须指定区域变体(例如 "es-MX"、"es-ES"、"pt-BR"、"pt-PT")—不接受裸 "es" 和 "pt"。无法识别的代码会被静默忽略并回退到自动语言检测。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"audio": {
"input": {
"transcription": {
"language_hint": "ja"
}
}
}
}
}))ws.send(JSON.stringify({
type: "session.update",
session: {
audio: {
input: {
transcription: {
language_hint: "ja"
}
}
}
}
}));关键词
通过在 session.update 中设置 audio.input.transcription.keyterms 来偏向领域特定词汇的转录—产品名称、专有名词、品牌名称或模型可能错误转录的技术术语。提供一个字符串数组,最多 100 个术语,每个术语最多 50 个字符。关键词可以在会话中途更新。
await ws.send(json.dumps({
"type": "session.update",
"session": {
"audio": {
"input": {
"transcription": {
"keyterms": ["xAI", "Grok", "Understand The Universe"]
}
}
}
}
}))ws.send(JSON.stringify({
type: "session.update",
session: {
audio: {
input: {
transcription: {
keyterms: ["xAI", "Grok", "Understand The Universe"]
}
}
}
}
}));使用 Grok 语音转语音 API 的工具
Grok 语音转语音 API 支持各种工具,您可以在会话中配置这些工具来增强语音代理的功能。工具可以在 session.update 消息中配置。
可用工具类型
- 集合搜索 (
file_search) - 搜索您上传的文档集合 - 网络搜索 (
web_search) - 搜索网络上的最新信息 - X 搜索 (
x_search) - 搜索 X (Twitter) 上的帖子和信息 - 远程 MCP 工具 (
mcp) - 连接到外部 MCP (Model Context Protocol) 服务器以获取自定义工具 - 自定义函数 - 使用 JSON 模式定义您自己的函数工具
使用 file_search 进行集合搜索
使用 file_search 工具使您的语音代理能够搜索文档集合。您需要先使用集合 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,
},
],
},
}const COLLECTION_ID = "your-collection-id"; // Replace with your collection ID
const sessionConfig = {
type: "session.update",
session: {
...
tools: [
{
type: "file_search",
vector_store_ids: [COLLECTION_ID],
max_num_results: 10,
},
],
},
};网络搜索和 X 搜索
配置网络搜索和 X 搜索工具,为您的语音代理提供访问网络和 X (Twitter) 上最新信息的权限。这两种工具都在服务端运行—通过在 session.tools 中列出它们来启用,可以选择使用与文本 API 网络搜索 和 X 搜索 工具相同的过滤参数。
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",
},
],
},
}const sessionConfig = {
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",
},
],
},
};网络搜索参数
| 参数 | 必需 | 描述 |
|---|---|---|
allowed_domains |
默认情况下,/v1/realtime 连接会在 WebSocket 关闭时丢失其对话历史。会话恢复 会缓存每轮对话并在重新连接时重放之前的上下文,从而使模型能够基于之前的内容保持状态。
要在跨连接的会话中继续对话,请捕获服务器的 conversation.created.conversation.id,并在重新连接时将其作为 ?conversation_id=<id> 传递回来(使用相同的启用选项)。
- 连接并启用恢复功能:在
session.update中发送resumption.enabled: true。从conversation.created中读取分配的 ID 并存储它。 - 使用该 ID 重新连接:使用
?conversation_id=<id>重新打开 WebSocket 并再次启用恢复功能。缓存的回合会在你的第一个新回合之前重放,作为conversation.item.created事件回显。
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// resumeId is null on a fresh conversation; pass the saved id to resume.
function connect(resumeId = null) {
let url = `wss://api.x.ai/v1/realtime?model=${MODEL}`;
if (resumeId) url += `&conversation_id=${resumeId}`;
const ws = new WebSocket(url);
ws.addEventListener("open", () => {
// Opt in to resumption (required to cache and to replay).
ws.send(JSON.stringify({
type: "session.update",
session: { resumption: { enabled: true } },
}));
});
ws.addEventListener("message", (msg) => {
const event = JSON.parse(msg.data);
if (event.type === "conversation.created") {
// Server-assigned id. Save it and pass it as
// ?conversation_id= on your next connect to resume.
savedId = event.conversation.id;
}
// ... handle the rest of the session
});
}持久化和重放的内容:用户和助手的对话记录、助手工具调用以及你的 function_call_output 结果。
- 双向启用:除非恢复的会话也发送
resumption.enabled: true,否则不会重放历史记录。 - 过期:30 分钟不活动后,历史记录将被丢弃。
最佳实践
本节概述了使用 xAI 语音到语音 API 构建低延迟、可靠且自然的语音体验的关键建议。
通过并行初始化最小化感知延迟
并行启动 WebSocket 连接和麦克风输入流。
- 尽早启动 WebSocket 连接(包括通过临时令牌或 API 密钥进行身份验证)— 理想情况下是在语音界面加载或用户打开麦克风启用屏幕时。
- 同时开始捕获麦克风音频(在浏览器中使用
getUserMedia,或在移动/原生平台上使用等效 API)。 - 不要等待 WebSocket
open事件再开始收集麦克风样本。
音频缓冲示例
// 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 = [];
}
};生产环境提示
- 在缓冲或刷新前转换为 24 kHz PCM16 小端格式。
- 以合理大小的消息(每条 100ms 的样本)进行刷新,以实现平滑传输。
- 重新连接时,立即恢复缓冲。
避免工具调用期间的音频重叠
当模型在语音响应期间调用工具时,服务器首先发送所有音频增量,然后在 response.done 旁边发送函数调用事件。如果你的客户端立即发送 conversation.item.create(带有函数结果),然后发送 response.create,服务器会立即开始生成下一个响应 — 即使客户端仍在播放上一轮的音频。这会导致音频重叠。
推荐序列:
- 收到
response.function_call_arguments.done→ 执行你的工具 - 发送带有
function_call_output的conversation.item.create - 等待当前轮次的音频播放完成(或几乎完成)
- 然后发送
response.create
在等待播放完成时,显示视觉上的"思考"指示器(例如,动画点),这样用户就知道代理正在处理。这会在模型的语音响应和工具结果之后的跟进之间创造自然的停顿。
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();
}
});其他高影响力建议
- 优先使用临时令牌 进行客户端安全。
- 启用
server_vad以实现自动、自然的插话。 - 匹配输入/输出格式(24 kHz PCM)以避免重新采样。
- 即时流式输出音频增量(
response.output_audio.delta)到扬声器 — 不要等待完整响应。 - 实现优雅重新连接,同时继续缓冲新音频。
- 监控 WebSocket 健康状态,并在需要时使用指数退避。
为企业语音构建
电话集成 — 通过 SIP、WebSocket 或 LiveKit 连接。原生支持 G.711 μ-law/A-law 编解码器 — 无需转码开销。
工具调用 — 在实时对话期间,通过函数调用连接 CRM、日历、数据库以及任何 REST 或 GraphQL 端点。
20+ 种语言 — 自然发音、口音处理,以及在同一对话中无缝切换语言。
领域专业知识 — 精确转录医疗、法律、金融和技术术语 — 名称、代码和地址。
SIP 电话呼叫
将 PSTN、呼叫中心或 PBX 呼叫路由到语音到语音 API 会话。有关使用 CreatePhoneNumberV2、呼叫控制、DTMF 和电话提供商示例进行 API 集成的信息,请参阅 SIP 电话呼叫。
从 OpenAI Realtime 迁移
如果你有一个基于 OpenAI Realtime API 构建的现有应用程序,切换到 Grok 语音到语音 API 只需要几处更改:更新基础 URL、交换你的 API 密钥,并选择一个 Grok 语音模型。
第 1 步 — 更新基础 URL 和 API 密钥
使用 OpenAI SDK
如果你使用官方 OpenAI SDK,将客户端指向 xAI 端点并提供你的 xAI API 密钥:
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())import OpenAI from "openai";
import { OpenAIRealtimeWS } from "openai/realtime/ws";
// Before (OpenAI)
// const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// After (xAI)
const client = new OpenAI({
apiKey: process.env.XAI_API_KEY,
baseURL: "https://api.x.ai/v1",
});
// Pass the configured client so the connection uses the xAI base URL and key
const rt = new OpenAIRealtimeWS({ model: "grok-voice-latest" }, client);
rt.on("session.created", () => {
rt.send({
type: "session.update",
session: {
voice: "eve",
instructions: "You are a helpful assistant.",
turn_detection: { type: "server_vad" },
},
});
});
rt.on("error", (err) => {
console.error("Realtime error:", err);
});
// ... rest of your application code使用原始 WebSocket
如果你通过 WebSocket 直接连接,请更改 URL 和 Authorization 标头:
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
passimport WebSocket from "ws";
// Before (OpenAI)
// const url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview";
// const headers = { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` };
// After (xAI)
const url = "wss://api.x.ai/v1/realtime?model=grok-voice-latest";
const headers = { Authorization: `Bearer ${process.env.XAI_API_KEY}` };
const ws = new WebSocket(url, { headers });
ws.on("open", () => {
// Your existing event handling code works as-is
});第 2 步 — 选择模型
建立连接时传递模型名称:
# Pass the model in connect()
async with client.realtime.connect(model="grok-voice-latest") as conn:
...// Pass the model in the OpenAIRealtimeWS constructor
const rt = new OpenAIRealtimeWS({ model: "grok-voice-latest" }, client);第 3 步 — 模型特定最佳实践
grok-voice-think-fast-2.0(推荐)
这是最新的语音模型。对于新集成,使用 grok-voice-latest 以便你的应用程序跟踪当前推荐的模型。迁移时:
- 简化你的系统提示。 模型能力显著增强,因此你的提示应该短得多。要求 Grok 概括你现有的系统提示,而不是逐字移植。
- 删除变通提示。 为 GPT 模型所需的提示技巧和边缘情况修复是不必要的。删除仅用于修补旧模型错误或限制的说明。
- 默认启用推理。 对于复杂的多步骤指令、微妙的语调和模糊的查询,默认的
reasoning.effort是"high"。将其设置为"none"以禁用推理。
NOTE
grok-voice-latest 始终指向最新的模型(当前为 grok-voice-think-fast-2.0)。在生产环境中,使用版本化的模型名称以确保稳定性。
OpenAI Realtime API 兼容性
Grok 语音到语音 API 与 OpenAI Realtime API 兼容。大多数 OpenAI 客户端库和 SDK 可以通过将基础 URL 更改为 wss://api.x.ai/v1/realtime 来与 xAI 端点一起使用。本节记录了事件命名差异和不支持的事件。
事件命名差异
xAI API 对某些具有不同负载的事件使用了不同的事件名称:
- OpenAI 的
conversation.item.input_audio_transcription.delta在 xAI API 中命名为conversation.item.input_audio_transcription.updated。updated事件包含累积的转录文本(可能包括对之前更新的更正),而不是增量增量。仅在设置audio.input.transcription.model为"grok-transcribe"时发出。
不支持的客户端事件
| OpenAI 事件 | 说明 |
|---|---|
conversation.item.retrieve | 不支持。 |
output_audio_buffer.clear | 仅限 WebRTC/SIP。 |
不支持的服务器事件
| OpenAI 事件 | 说明 |
|---|---|
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 扩展
这些事件和功能是 xAI 特有的,不属于 OpenAI Realtime API:
| 事件/功能 | 描述 |
|---|---|
force_message | 用于 TTS 合成的脚本话语的新 conversation.item.create 项目类型。请参阅 强制消息。 |
resumption | session.update 上的字段,用于缓存对话回合并在重新连接时重放它们。请参阅 会话恢复。 |
replace | session.update 上的字段,将短语映射到在 TTS 之前应用的语音替换,以修复发音而不更改转录文本。请参阅 发音替换。 |