客户端事件

了解并处理对话应用中客户端实时接收的事件。

客户端事件是从服务器发送到客户端的系统级事件,用于实现实时通信。这些事件向客户端应用传递音频、转写内容、智能体回复及其他关键信息。

有关可从客户端发送到服务器的事件,请参阅客户端到服务器事件文档。

概述

客户端事件对于维持对话的实时性至关重要。它们提供从初始化元数据到已处理音频和智能体回复的所有信息。

这些事件属于 WebSocket 通信协议的一部分,由我们的 SDK 自动处理。对于高级实现和调试,了解这些事件至关重要。

客户端事件类型

  • 开始对话时自动发送
  • 初始化对话设置和参数
// Example initialization metadata
{
"type": "conversation_initiation_metadata",
"conversation_initiation_metadata_event": {
"conversation_id": "conv_123",
"agent_output_audio_format": "pcm_44100", // TTS output format
"user_input_audio_format": "pcm_16000" // ASR input format
}
}
  • 智能体达到并发限制时,仅发送给在通话队列中等待的来电者
  • waiting 在 conversation_initiation_metadata 之后、任何等待音频之前发送一次
  • 等待结束时,admitted 或 timed_out 会发送一次。timed_out 后会以代码 4300 关闭 WebSocket
  • 始终发送给排队中的来电者。无需在智能体的 client_events 配置中启用

来电者排队时,等待音频会作为常规 audio 事件到达。请使用此事件显示等待状态,而不要将等待音频视为智能体语音。

// Example queue status event structure
{
"type": "queue_status",
"queue_status_event": {
"status": "waiting" // "waiting" | "admitted" | "timed_out"
}
}
// Example queue status handler
websocket.on('queue_status', (event) => {
const { status } = event.queue_status_event;
if (status === 'waiting') {
showWaitingState();
} else if (status === 'admitted') {
hideWaitingState();
} else if (status === 'timed_out') {
showAllAgentsBusyMessage();
}
});
  • 需要立即响应的运行状况检查事件
  • 由 SDK 自动处理
  • 用于维持 WebSocket 连接
// Example ping event structure
{
"ping_event": {
"event_id": 123456,
"ping_ms": 50 // Optional, estimated latency in milliseconds
},
"type": "ping"
}
// Example ping handler
websocket.on('ping', () => {
websocket.send('pong');
});
  • 包含用于播放的 Base64 编码音频
  • 包含用于跟踪和排序的数字事件 ID
  • 处理语音输出流
  • 包含具有字符级时间信息的对齐数据

通过 WebRTC 连接时,不会发送 audio 事件,因为音频由 LiveKit 直接处理。

// Example audio event structure
{
"audio_event": {
"audio_base_64": "base64_encoded_audio_string",
"event_id": 12345,
"alignment": { // Character-level timing data
"chars": ["H", "e", "l", "l", "o"],
"char_durations_ms": [50, 30, 40, 40, 60],
"char_start_times_ms": [0, 50, 80, 120, 160]
}
},
"type": "audio"
}
// Example audio event handler
websocket.on('audio', (event) => {
const { audio_event } = event;
const { audio_base_64, event_id, alignment } = audio_event;
audioPlayer.play(audio_base_64);
// Use alignment data for synchronized text display
const { chars, char_start_times_ms } = alignment;
chars.forEach((char, i) => {
setTimeout(() => highlightCharacter(char, i), char_start_times_ms[i]);
});
});
  • 包含已完成的语音转文本结果
  • 表示完整的用户话语
  • 用于对话历史记录
// Example transcript event structure
{
"type": "user_transcript",
"user_transcription_event": {
"user_transcript": "Hello, how can you help me today?"
}
}
// Example transcript handler
websocket.on('user_transcript', (event) => {
const { user_transcription_event } = event;
const { user_transcript } = user_transcription_event;
updateConversationHistory(user_transcript);
});
  • 包含完整的智能体消息
  • 消息完成后发送一次,因此在语音对话中,通常会在该消息的音频开始流式传输后才到达
  • 用于显示和历史记录

如需在智能体文本生成时显示,请使用下文所述的 agent_chat_response_part 事件,而非等待此事件。

// Example response event structure
{
"type": "agent_response",
"agent_response_event": {
"agent_response": "Hello, how can I assist you today?"
}
}
// Example response handler
websocket.on('agent_response', (event) => {
const { agent_response_event } = event;
const { agent_response } = agent_response_event;
displayAgentMessage(agent_response);
});
  • 包含中断后的截断回复
  • 更新显示的消息
  • 保持对话准确性
// Example response correction event structure
{
"type": "agent_response_correction",
"agent_response_correction_event": {
"original_agent_response": "Let me tell you about the complete history...",
"corrected_agent_response": "Let me tell you about..." // Truncated after interruption
}
}
// Example response correction handler
websocket.on('agent_response_correction', (event) => {
const { agent_response_correction_event } = event;
const { corrected_agent_response } = agent_response_correction_event;
displayAgentMessage(corrected_agent_response);
});
  • 包含来自自定义 LLM 回复的任意元数据
  • 仅在使用自定义 LLM时发送
  • 必须在智能体的 client_events 配置中明确启用

此事件专用于自定义 LLM 集成。它允许自定义 LLM 服务器随回复传递额外元数据,供客户端应用使用。

// Example agent response metadata event structure
{
"type": "agent_response_metadata",
"agent_response_metadata_event": {
"metadata": {
// Any key-value pairs returned by your custom LLM
"key": "value"
},
"event_id": 12345
}
}
// Example metadata handler
websocket.on('agent_response_metadata', (event) => {
const { agent_response_metadata_event } = event;
const { metadata, event_id } = agent_response_metadata_event;
// Use metadata for UI updates, logging, or analytics
console.log(`Response ${event_id} metadata:`, metadata);
updateResponseDetails(metadata);
});
  • 表示智能体希望客户端执行的函数调用
  • 包含工具名称、工具调用 ID 和参数
  • 需要在客户端执行函数,并将结果发送回服务器

如果使用 SDK,将提供回调来处理将结果发送回服务器的操作。

// Example tool call event structure
{
"type": "client_tool_call",
"client_tool_call": {
"tool_name": "search_database",
"tool_call_id": "call_123456",
"parameters": {
"query": "user information",
"filters": {
"date": "2024-01-01"
}
}
}
}
// Example tool call handler
websocket.on('client_tool_call', async (event) => {
const { client_tool_call } = event;
const { tool_name, tool_call_id, parameters } = client_tool_call;
try {
const result = await executeClientTool(tool_name, parameters);
// Send success response back to continue conversation
websocket.send({
type: "client_tool_result",
tool_call_id: tool_call_id,
result: result,
is_error: false
});
} catch (error) {
// Send error response if tool execution fails
websocket.send({
type: "client_tool_result",
tool_call_id: tool_call_id,
result: error.message,
is_error: true
});
}
});
  • 指示智能体何时执行了工具函数
  • 包含工具元数据和执行状态
  • 可了解智能体在对话期间的工具使用情况
// Example agent tool response event structure
{
"type": "agent_tool_response",
"agent_tool_response": {
"tool_name": "skip_turn",
"tool_call_id": "skip_turn_c82ca55355c840bab193effb9a7e8101",
"tool_type": "system",
"is_error": false
}
}
// Example agent tool response handler
websocket.on('agent_tool_response', (event) => {
const { agent_tool_response } = event;
const { tool_name, tool_call_id, tool_type, is_error } = agent_tool_response;
if (is_error) {
console.error(`Agent tool ${tool_name} failed:`, tool_call_id);
} else {
console.log(`Agent executed ${tool_type} tool: ${tool_name}`);
}
});
  • 与 agent_tool_response 对应,此外还会将工具的完整结果载荷作为字符串流式传输到 full_tool_result。
  • 在客户端呈现工具输出,以便显示或进行下游处理。
  • 必须在智能体的 client_events 配置中明确启用。

此事件会向客户端公开完整工具结果,其中可能包含敏感数据。仅在客户端受信任且能够处理此载荷时启用。超过 64 KB 的结果会自动截断。

// Example agent tool response full payload event structure
{
"type": "agent_tool_response_full_payload",
"agent_tool_response_full_payload": {
"tool_name": "lookup_order",
"tool_call_id": "lookup_order_c82ca55355c840bab193effb9a7e8101",
"tool_type": "webhook",
"is_error": false,
"full_tool_result": "{\"order_id\": \"ORD-789\", \"status\": \"shipped\"}",
"truncated": false
}
}
// Example agent tool response full payload handler (using @elevenlabs/react)
import { ConversationProvider } from '@elevenlabs/react';
function App() {
return (
<ConversationProvider
onAgentToolResponse={(response) => {
if (!('full_tool_result' in response)) return;
const { tool_name, tool_call_id, is_error, full_tool_result, truncated } = response;
if (is_error) {
console.error(`Agent tool ${tool_name} failed:`, tool_call_id);
} else {
console.log(`Tool ${tool_name} returned:`, full_tool_result);
}
if (truncated) {
console.warn(`Tool ${tool_name} result was truncated (exceeded 64 KB).`);
}
}}
>
<Agent />
</ConversationProvider>
);
}
  • 语音活动检测评分事件
  • 表示用户正在说话的概率
  • 值范围为 0 到 1,值越高表示语音置信度越高
// Example VAD score event
{
"type": "vad_score",
"vad_score_event": {
"vad_score": 0.95
}
}
  • 指示智能体何时执行了 MCP 工具函数
  • 包含工具名称、工具调用 ID 和参数
  • 以四种状态之一调用:loading、awaiting_approval、success 和 failure。
{
"type": "mcp_tool_call",
"mcp_tool_call": {
"service_id": "xJ8kP2nQ7sL9mW4vR6tY",
"tool_call_id": "call_123456",
"tool_name": "search_database",
"tool_description": "Search the database for user information",
"parameters": {
"query": "user information",
},
"timestamp": "2024-09-30T14:23:45.123456+00:00",
"state": "loading",
"approval_timeout_secs": 10
}
}
  • 将智能体生成中的回复文本作为 start、delta 和 stop 消息进行流式传输
  • 在纯文本模式下始终发送;在语音对话中,必须在智能体的 client_events 配置中明确启用
  • 当智能体或活跃流程使用阻塞型护栏时不会发送,因为护栏必须先评估完整回复,才能释放任何内容
  • response_id 用于标识正在流式传输的消息,并与稍后确认该消息的 agent_response 的 response_id 匹配
// Example start event
{
"type": "agent_chat_response_part",
"text_response_part": {
"type": "start",
"text": "",
"event_id": 12345,
"response_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
}
// Example delta event with text chunk
{
"type": "agent_chat_response_part",
"text_response_part": {
"type": "delta",
"text": "Hello, how can I",
"event_id": 12345,
"response_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
}
// Example stop event
{
"type": "agent_chat_response_part",
"text_response_part": {
"type": "stop",
"text": "",
"event_id": 12345,
"response_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
}
// Example handler
websocket.on('agent_chat_response_part', (event) => {
const { text_response_part } = event;
const { type: partType, text, response_id } = text_response_part;
if (partType === 'start') {
initializeResponseBuffer(response_id);
} else if (partType === 'delta') {
appendToResponseBuffer(response_id, text);
} else if (partType === 'stop') {
finalizeResponse(response_id);
}
});

agent_reasoning_response_part 会在纯文本对话期间流式传输模型提供的推理内容。 在 client_events 中启用此事件,并为智能体开启推理摘要。服务器会发送 start、delta 和 stop 消息。在语音对话期间,或当智能体或活跃流程使用阻塞型护栏时,不会发送此事件。

此事件及对应的 SDK 回调属于实验性功能。其行为和结构可能在任何版本中发生变化。

事件载荷
{
"type": "agent_reasoning_response_part",
"reasoning_response_part": {
"type": "delta",
"text": "The user asked to cancel, so I should verify the account before continuing.",
"event_id": 123456
}
}

开始和停止事件使用空的 text 值。

处理推理事件
import { Conversation } from '@elevenlabs/client';
const conversation = await Conversation.startSession({
agentId: 'agent_7101k5zvyjhmfg983brhmhkd98n6',
textOnly: true,
onAgentReasoningResponsePart: ({ type, text, event_id }) => {
if (type === 'start') {
initializeReasoningBuffer(event_id);
} else if (type === 'delta') {
appendToReasoningBuffer(text);
} else if (type === 'stop') {
finalizeReasoning();
}
},
});
  • 当智能体完成回复(包括所有待处理工具调用)时触发。此事件之后,只有当用户提供新输入或轮次超时触发新轮次时,智能体才会继续产生输出。
  • 必须在智能体的 client_events 配置中明确启用
// Example agent response complete event structure
{
"type": "agent_response_complete",
"agent_response_complete_event": {
"event_id": 12345
}
}
// Example handler
websocket.on('agent_response_complete', (event) => {
const { agent_response_complete_event } = event;
const { event_id } = agent_response_complete_event;
console.log(`Agent response ${event_id} complete`);
});
  • 当护栏违规导致对话结束时触发。护栏触发重试且重试成功时不会发送。
  • 事件本身就是信号,除 type 字段外不携带任何载荷。
  • 必须在智能体的 client_events 配置中明确启用。
// Example guardrail triggered event structure
{
"type": "guardrail_triggered"
}
// Example guardrail triggered handler (using @elevenlabs/client)
import { Conversation } from '@elevenlabs/client';
const conversation = await Conversation.startSession({
agentId: 'agent_7101k5zvyjhmfg983brhmhkd98n6',
onGuardrailTriggered: () => {
console.warn('Guardrail triggered — conversation will end.');
},
});

事件流程

以下是对话期间的典型事件顺序:

conversation_initiation_metadata ping pong audio user_transcript audio agent_response client_tool_call client_tool_result audio agent_response agent_response_correction Connection established Playing audio User responds Client tool runs Playing audio Interruption detected Client Server

当智能体达到并发上限且已启用呼叫排队时,服务器会在 conversation_initiation_metadata 与第一个 audio 事件之间发送 queue_status 事件。等待音频会以 audio 事件的形式传送,直到呼叫者获准接入。

最佳实践

  1. 错误处理

    • 为每种事件类型实现适当的错误处理
    • 记录重要事件,便于调试
    • 妥善处理连接中断
  2. 音频管理

    • 合理缓冲音频块
    • 在中断时做好清理
    • 管理音频资源
  3. 连接管理

    • 及时响应 PING 事件
    • 实现重连逻辑
    • 监控连接状态

故障排除

  • 确保 WebSocket 连接正常
  • 检查 PING/PONG 响应
  • 验证 API 凭据
  • 检查音频块处理
  • 验证音频格式兼容性
  • 监控内存使用情况
  • 记录所有事件,便于调试
  • 实现错误边界
  • 检查事件处理程序注册情况

如需详细实现示例,请查看我们的 SDK 文档。