跳转到内容

Model Capabilities

语音转语音

构建由 Grok 驱动的实时语音应用程序。通过 WebSocket 进行双向音频和文本流式传输,用于语音助手、电话代理和交互式语音系统。

快速开始

连接到语音转语音 API 并开始对话:

python
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())
javascript
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);
});

获取 API Key →

API 文档

实时语音演示

定价

使用我们的测试应用开始

身份验证

使用以下任一方法验证您的 WebSocket 连接:

  • 临时令牌 (推荐) — 用于客户端应用(浏览器、移动设备)的短期令牌。使您的 API 密钥不会出现在客户端。
  • API Key — 直接在 Authorization 头中传递您的 xAI API 密钥。仅限服务端使用。

更多详情请参阅我们的 API 文档

事件

WebSocket 连接打开后,双向事件即可开始。客户端事件用于提供对话信息并将用户音频发送到语音 API,而服务器事件包括音频和文本响应。

API 文档 →

模型选择

model 作为查询参数传递;使用版本化名称以固定到特定版本。

python
MODEL = "grok-voice-latest"
url = f"wss://api.x.ai/v1/realtime?model={MODEL}"
javascript
const MODEL = "grok-voice-latest";
const url = `wss://api.x.ai/v1/realtime?model=${MODEL}`;
模型描述
grok-voice-latestgrok-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 事件来配置会话。

参数类型描述
instructionsstring系统提示
reasoning.effort"high""none"
voicestring语音选择:任何内置语音(如 eve)或自定义语音 ID(参见可用语音
toolsarray语音代理可用的工具。支持 file_searchweb_searchx_searchmcpfunction 类型。参见使用工具
turn_detection.typestringnull
turn_detection.thresholdnumber可选
turn_detection.silence_duration_msnumber可选
turn_detection.prefix_padding_msnumber可选
turn_detection.idle_timeout_msnumber可选
resumption.enabledboolean可选
audio.input.format.typestring输入编解码器:"audio/pcm""audio/pcmu""audio/pcma""audio/opus"
audio.input.format.ratenumber输入采样率(仅 PCM):8000、16000、22050、24000、32000、44100、48000
audio.input.transport"json""binary"
audio.output.format.typestring输出编解码器:"audio/pcm""audio/pcmu""audio/pcma""audio/opus"
audio.output.format.ratenumber输出采样率(仅 PCM):8000、16000、22050、24000、32000、44100、48000
audio.output.transport"json""binary"
audio.input.transcription.language_hintstring偏向特定语言的 BCP-47 语言代码(如 "ja""ar""es-MX""pt-BR")以优化 ASR 转录。可以在会话中更新。参见语言提示
audio.input.transcription.keytermsarray关键词列表(如产品名称、专有名词)以优化转录。最多 100 个术语,每个最多 50 个字符。可以在会话中更新。参见关键词
audio.output.speednumber助手音频输出的播放速度乘数。范围:0.7–1.5。默认值:1.0。低于 1.0 的值会减慢语音;高于 1.0 的值会加快语音。
replaceobject可选

可用语音

相同的语音列表适用于语音转语音 API 和文本转语音 API。在语音表中浏览完整列表,包含语调描述和样本,或通过 GET /v1/tts/voices 以编程方式获取。将小写语音 ID 作为 voice 参数传递给 session.update

自定义语音

需要列表中没有的语音吗?使用自定义语音 API 从简短的参考片段中克隆任何语音。生成的 voice_id 可以像内置语音一样用作 session.updatevoice 参数。

选择语音

在会话配置中使用 voice 参数指定语音:

python
# 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))
javascript
// 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-endianRaw, WAV, AIFF可配置(见下文)
audio/pcmuG.711 μ-law (Mulaw)Raw8000 Hz
audio/pcmaG.711 A-lawRaw8000 Hz
audio/opusOpusRaw packets (one packet per payload)24000 Hz

使用 audio/pcm 格式时,您可以将采样率配置为以下支持的值之一:

采样率质量描述
8000 Hz电话窄带,适合语音通话
16000 Hz宽带适合语音识别
22050 Hz标准平衡的质量和带宽
24000 Hz (默认)推荐用于大多数用例
32000 Hz非常高增强的音频清晰度
44100 HzCD 质量音乐/媒体的标准
48000 Hz专业录音棚级音频

您可以在会话配置中为输入和输出配置音频格式和采样率:

python
# 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))
javascript
// 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 中的 Base64response.output_audio.delta / response.audio.delta 中的 Base64
binary原始编解码器字节作为 WebSocket 二进制帧(无协议头)相同的二进制帧;生命周期事件(response.createdresponse.done、转录等)保持 JSON

省略 transport(或设置为 "json")以保持现有客户端不变。

输入双接受:当配置输入格式时,服务器接受该编解码器的两者 JSON 附加和二进制帧。使用 input.transport 作为客户端的首选发送路径;您不需要在会话中途排空一个通道后再使用另一个。

输出是严格的:助手音频仅在 output.transport 上发出。在会话中途更改 output.transport 会在下一个响应边界处应用,因此单个语音绝不会混合 JSON 增量和二进制帧。

Opus:每个 JSON delta / audio 字段或每个二进制帧都是一个原始 Opus 数据包(24 kHz 单声道)。二进制帧上没有额外的帧头。

示例:双向使用二进制 PCM:

python
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.
javascript
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)。

python
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
javascript
// 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"。

python
await ws.send(json.dumps({
    "type": "session.update",
    "session": {
        "voice": "eve",
        "instructions": "You are a helpful assistant.",
        "replace": {"Acme Mobile": "Acme Mobull"}
    }
}))
javascript
ws.send(JSON.stringify({
  type: "session.update",
  session: {
    voice: "eve",
    instructions: "You are a helpful assistant.",
    replace: { "Acme Mobile": "Acme Mobull" }
  }
}));

匹配行为:

  • 匹配不区分大小写;替换值使用您提供的大小写发音。
  • 需要完整单词边界,因此 Acme, MobileAcme-MobileAcme 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"。无法识别的代码会被静默忽略并回退到自动语言检测。

python
await ws.send(json.dumps({
    "type": "session.update",
    "session": {
        "audio": {
            "input": {
                "transcription": {
                    "language_hint": "ja"
                }
            }
        }
    }
}))
javascript
ws.send(JSON.stringify({
  type: "session.update",
  session: {
    audio: {
      input: {
        transcription: {
          language_hint: "ja"
        }
      }
    }
  }
}));

关键词

通过在 session.update 中设置 audio.input.transcription.keyterms 来偏向领域特定词汇的转录—产品名称、专有名词、品牌名称或模型可能错误转录的技术术语。提供一个字符串数组,最多 100 个术语,每个术语最多 50 个字符。关键词可以在会话中途更新。

python
await ws.send(json.dumps({
    "type": "session.update",
    "session": {
        "audio": {
            "input": {
                "transcription": {
                    "keyterms": ["xAI", "Grok", "Understand The Universe"]
                }
            }
        }
    }
}))
javascript
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 创建一个集合。

python
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,
            },
        ],
    },
}
javascript
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 搜索 工具相同的过滤参数。

python
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",
            },
        ],
    },
}
javascript
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> 传递回来(使用相同的启用选项)。

  1. 连接并启用恢复功能:在 session.update 中发送 resumption.enabled: true。从 conversation.created 中读取分配的 ID 并存储它。
  2. 使用该 ID 重新连接:使用 ?conversation_id=<id> 重新打开 WebSocket 并再次启用恢复功能。缓存的回合会在你的第一个新回合之前重放,作为 conversation.item.created 事件回显。
python
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
javascript
// 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 事件再开始收集麦克风样本。

音频缓冲示例

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 = [];
  }
};

生产环境提示

  • 在缓冲或刷新前转换为 24 kHz PCM16 小端格式。
  • 以合理大小的消息(每条 100ms 的样本)进行刷新,以实现平滑传输。
  • 重新连接时,立即恢复缓冲。

避免工具调用期间的音频重叠

当模型在语音响应期间调用工具时,服务器首先发送所有音频增量,然后在 response.done 旁边发送函数调用事件。如果你的客户端立即发送 conversation.item.create(带有函数结果),然后发送 response.create,服务器会立即开始生成下一个响应 — 即使客户端仍在播放上一轮的音频。这会导致音频重叠。

推荐序列:

  1. 收到 response.function_call_arguments.done → 执行你的工具
  2. 发送带有 function_call_outputconversation.item.create
  3. 等待当前轮次的音频播放完成(或几乎完成)
  4. 然后发送 response.create

在等待播放完成时,显示视觉上的"思考"指示器(例如,动画点),这样用户就知道代理正在处理。这会在模型的语音响应和工具结果之后的跟进之间创造自然的停顿。

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();
  }
});

其他高影响力建议

  • 优先使用临时令牌 进行客户端安全。
  • 启用 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 密钥:

python
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())
javascript
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 标头:

python
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
javascript
import 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 步 — 选择模型

建立连接时传递模型名称:

python
# Pass the model in connect()
async with client.realtime.connect(model="grok-voice-latest") as conn:
    ...
javascript
// 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.updatedupdated 事件包含累积的转录文本(可能包括对之前更新的更正),而不是增量增量。仅在设置 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 项目类型。请参阅 强制消息
resumptionsession.update 上的字段,用于缓存对话回合并在重新连接时重放它们。请参阅 会话恢复
replacesession.update 上的字段,将短语映射到在 TTS 之前应用的语音替换,以修复发音而不更改转录文本。请参阅 发音替换

本文档为 docs.x.ai 全站中文翻译,由 AI 自动翻译生成。代码示例请以原文为准。