WebSocket

Twórz interaktywne rozmowy głosowe z agentami AI w czasie rzeczywistym

Ta dokumentacja jest dla deweloperów integrujących się bezpośrednio z API WebSocket ElevenLabs. Dla wygody rozważ użycie oficjalnych SDK od ElevenLabs.

API WebSocket ElevenAgents umożliwia interaktywne rozmowy głosowe z agentami AI w czasie rzeczywistym. Po nawiązaniu połączenia WebSocket możesz wysyłać dźwięk wejściowy i otrzymywać odpowiedzi audio w czasie rzeczywistym, tworząc naturalne doświadczenia konwersacyjne.

Endpoint: wss://api.elevenlabs.io/v1/convai/conversation?agent_id={agent_id}

Uwierzytelnianie

Użycie identyfikatora agenta

W przypadku agentów publicznych możesz użyć agent_id bezpośrednio w adresie URL WebSocket, bez dodatkowego uwierzytelniania:

wss://api.elevenlabs.io/v1/convai/conversation?agent_id=<your-agent-id>

Użycie podpisanego URL

W przypadku prywatnych agentów lub rozmów wymagających autoryzacji uzyskaj podpisany URL z serwera, który bezpiecznie komunikuje się z API ElevenLabs za pomocą twojego klucza API.

Przykład z cURL

Żądanie:

curl -X GET "https://api.elevenlabs.io/v1/convai/conversation/get-signed-url?agent_id=<your-agent-id>" \
-H "xi-api-key: <your-api-key>"

Odpowiedź:

{
"signed_url": "wss://api.elevenlabs.io/v1/convai/conversation?agent_id=<your-agent-id>&token=<token>"
}
Nigdy nie ujawniaj klucza API ElevenLabs po stronie klienta.

Zdarzenia WebSocket

Zdarzenia klient-serwer

Poniższe zdarzenia można wysyłać z klienta na serwer:

Wysyłaj nieprzerywające informacje kontekstowe, aby zaktualizować stan rozmowy. Dzięki temu możesz przekazać dodatkowy kontekst bez zakłócania trwającej rozmowy.

{
"type": "contextual_update",
"text": "User clicked on pricing page"
}

Przypadki użycia:

  • Aktualizowanie statusu lub preferencji użytkownika
  • Przekazywanie kontekstu środowiskowego
  • Dodawanie informacji w tle
  • Śledzenie interakcji z interfejsem użytkownika

Kluczowe informacje:

  • Nie przerywa bieżącej rozmowy
  • Aktualizacje są dodawane jako wywołania narzędzi w historii rozmowy
  • Pomaga zachować kontekst bez przerywania naturalnego dialogu

Aktualizacje kontekstowe są przetwarzane asynchronicznie i nie wymagają bezpośredniej odpowiedzi serwera.

Przykład implementacji w Next.js

Ten przykład pokazuje, jak wdrożyć klienta agenta konwersacyjnego opartego na WebSocket w Next.js za pomocą API WebSocket ElevenLabs.

Ten przykład używa pakietu voice-stream do obsługi wejścia z mikrofonu, ale możesz wdrożyć własne rozwiązanie do przechwytywania i kodowania dźwięku. Skupiamy się tu na pokazaniu połączenia WebSocket i obsługi zdarzeń z API ElevenLabs.

1

Zainstaluj wymagane zależności

Najpierw zainstaluj potrzebne pakiety:

npm install voice-stream

Pakiet voice-stream obsługuje dostęp do mikrofonu i strumieniowanie dźwięku, automatycznie kodując dźwięk w formacie base64 wymaganym przez API ElevenLabs.

Ten przykład używa Tailwind CSS do stylowania. Aby dodać Tailwind do projektu Next.js:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Następnie postępuj zgodnie z oficjalnym przewodnikiem konfiguracji Tailwind CSS dla Next.js.

Możesz też zastąpić atrybuty className własnymi stylami CSS.

2

Utwórz typy WebSocket

Zdefiniuj typy zdarzeń WebSocket:

app/types/websocket.ts
type BaseEvent = {
type: string;
};
type UserTranscriptEvent = BaseEvent & {
type: "user_transcript";
user_transcription_event: {
user_transcript: string;
};
};
type AgentResponseEvent = BaseEvent & {
type: "agent_response";
agent_response_event: {
agent_response: string;
};
};
type AgentResponseCorrectionEvent = BaseEvent & {
type: "agent_response_correction";
agent_response_correction_event: {
original_agent_response: string;
corrected_agent_response: string;
};
};
type AudioResponseEvent = BaseEvent & {
type: "audio";
audio_event: {
audio_base_64: string;
event_id: number;
alignment: {
chars: string[];
char_durations_ms: number[];
char_start_times_ms: number[];
};
};
};
type InterruptionEvent = BaseEvent & {
type: "interruption";
interruption_event: {
reason: string;
};
};
type PingEvent = BaseEvent & {
type: "ping";
ping_event: {
event_id: number;
ping_ms?: number;
};
};
type AgentChatResponsePartEvent = BaseEvent & {
type: "agent_chat_response_part";
text_response_part: {
type: "start" | "delta" | "stop";
text: string;
event_id: number;
response_id: string;
};
};
export type ElevenLabsWebSocketEvent =
| UserTranscriptEvent
| AgentResponseEvent
| AgentResponseCorrectionEvent
| AudioResponseEvent
| InterruptionEvent
| PingEvent
| AgentChatResponsePartEvent;
3

Utwórz hook WebSocket

Utwórz własny hook do zarządzania połączeniem WebSocket:

app/hooks/useAgentConversation.ts
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useVoiceStream } from 'voice-stream';
import type { ElevenLabsWebSocketEvent } from '../types/websocket';
const sendMessage = (websocket: WebSocket, request: object) => {
if (websocket.readyState !== WebSocket.OPEN) {
return;
}
websocket.send(JSON.stringify(request));
};
export const useAgentConversation = () => {
const websocketRef = useRef<WebSocket>(null);
const [isConnected, setIsConnected] = useState<boolean>(false);
const { startStreaming, stopStreaming } = useVoiceStream({
onAudioChunked: (audioData) => {
if (!websocketRef.current) return;
sendMessage(websocketRef.current, {
user_audio_chunk: audioData,
});
},
});
const startConversation = useCallback(async () => {
if (isConnected) return;
const websocket = new WebSocket("wss://api.elevenlabs.io/v1/convai/conversation");
websocket.onopen = async () => {
setIsConnected(true);
sendMessage(websocket, {
type: "conversation_initiation_client_data",
});
await startStreaming();
};
websocket.onmessage = async (event) => {
const data = JSON.parse(event.data) as ElevenLabsWebSocketEvent;
// Handle ping events to keep connection alive
if (data.type === "ping") {
setTimeout(() => {
sendMessage(websocket, {
type: "pong",
event_id: data.ping_event.event_id,
});
}, data.ping_event.ping_ms);
}
if (data.type === "user_transcript") {
const { user_transcription_event } = data;
console.log("User transcript", user_transcription_event.user_transcript);
}
if (data.type === "agent_response") {
const { agent_response_event } = data;
console.log("Agent response", agent_response_event.agent_response);
}
if (data.type === "agent_response_correction") {
const { agent_response_correction_event } = data;
console.log("Agent response correction", agent_response_correction_event.corrected_agent_response);
}
if (data.type === "interruption") {
// Handle interruption
}
if (data.type === "audio") {
const { audio_event } = data;
// Implement your own audio playback system here
// Note: You'll need to handle audio queuing to prevent overlapping
// as the WebSocket sends audio events in chunks
}
if (data.type === "agent_chat_response_part") {
const { text_response_part } = data;
const { type: partType, text, response_id } = text_response_part;
// Handle the agent's response text as it is generated. Enable
// agent_chat_response_part in the agent's client_events to receive
// this during voice conversations.
console.log("Chat response part:", partType, text, response_id);
}
};
websocketRef.current = websocket;
websocket.onclose = async () => {
websocketRef.current = null;
setIsConnected(false);
stopStreaming();
};
}, [startStreaming, isConnected, stopStreaming]);
const stopConversation = useCallback(async () => {
if (!websocketRef.current) return;
websocketRef.current.close();
}, []);
useEffect(() => {
return () => {
if (websocketRef.current) {
websocketRef.current.close();
}
};
}, []);
return {
startConversation,
stopConversation,
isConnected,
};
};
4

Utwórz komponent rozmowy

Utwórz komponent korzystający z hooka WebSocket:

app/components/Conversation.tsx
'use client';
import { useCallback } from 'react';
import { useAgentConversation } from '../hooks/useAgentConversation';
export function Conversation() {
const { startConversation, stopConversation, isConnected } = useAgentConversation();
const handleStart = useCallback(async () => {
try {
await navigator.mediaDevices.getUserMedia({ audio: true });
await startConversation();
} catch (error) {
console.error('Failed to start conversation:', error);
}
}, [startConversation]);
return (
<div className="flex flex-col items-center gap-4">
<div className="flex gap-2">
<button
onClick={handleStart}
disabled={isConnected}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
>
Start Conversation
</button>
<button
onClick={stopConversation}
disabled={!isConnected}
className="px-4 py-2 bg-red-500 text-white rounded disabled:bg-gray-300"
>
Stop Conversation
</button>
</div>
<div className="flex flex-col items-center">
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
</div>
</div>
);
}

Kolejne kroki

  1. Odtwarzanie audio: Wdroż własny system odtwarzania audio z użyciem Web Audio API lub biblioteki. Pamiętaj o kolejkowaniu audio, aby uniknąć nakładania się dźwięków, ponieważ WebSocket wysyła zdarzenia audio w fragmentach.
  2. Obsługa błędów: Dodaj logikę ponawiania prób i mechanizmy odzyskiwania po błędach
  3. Informacje zwrotne UI: Dodaj wizualne wskaźniki aktywności głosowej i stanu połączenia

Zarządzanie opóźnieniami

Aby rozmowy przebiegały płynnie, wdroż te strategie:

  • Buforowanie adaptacyjne: Dostosuj buforowanie audio do warunków sieciowych.
  • Bufor jitter: Wdroż bufor jitter, aby wygładzić różnice w czasie docierania pakietów.
  • Monitorowanie ping-pong: Używaj zdarzeń ping i pong do mierzenia czasu podróży w obie strony i odpowiednio dostosowuj ustawienia.

Dobre praktyki bezpieczeństwa

  • Regularnie rotuj klucze API i przechowuj je w zmiennych środowiskowych.
  • Wdróż ograniczanie liczby żądań, aby zapobiegać nadużyciom.
  • Jasno wyjaśniaj cel, gdy prosisz użytkowników o dostęp do mikrofonu.
  • Zoptymalizowany podział na fragmenty: Dostosuj czas trwania fragmentów audio, aby zrównoważyć opóźnienia i wydajność.

Dodatkowe materiały