Voice Connectors
A connector manages the live call in voice mode: it establishes the connection, plays the simulated user's audio to your agent, captures the spoken reply, and measures response latency. Every connector declares the transport it speaks via a protocol class variable of type VoiceProtocol β see Voice concepts for the full transport landscape and how protocols define timing semantics.
Pick the connector that matches where your agent is deployed:
| Connector | VoiceProtocol | Talks to |
|---|---|---|
ElevenLabsConnector | WEBSOCKET | ElevenLabs conversational agents, by agent_id. |
LiveKitConnector | WEBRTC | Agents deployed in LiveKit rooms. |
WebSocketConnector | WEBSOCKET | Any custom agent that speaks raw audio over a WebSocket. |
CallbackVoiceConnector | CALLBACK | An in-process Python callable β no network, ideal for testing your pipeline. |
Pass the connector into VoiceConfig(connector=...). For the half-duplex vs duplex APIs a connector exposes, see Voice concepts β Connectors and Interruptions.
ElevenLabs
Connects to an ElevenLabs conversational agent.
from deepeval.voice import ElevenLabsConnector
connector = ElevenLabsConnector(agent_id="your-agent-id")There are ONE mandatory and THREE optional parameters when creating an ElevenLabsConnector:
agent_id: a string identifying the ElevenLabs conversational agent.- [Optional]
api_key: a string API key. When omitted, readsELEVENLABS_API_KEYfrom the environment. Defaulted toNone. - [Optional]
region: a string region subdomain for the ElevenLabs API host (e.g."eu"). Defaulted toNone(global host). - [Optional]
turn_detection: how long to wait before deciding your agent has finished speaking β"eager","balanced", or"patient". See Turn Detection. Defaulted to"balanced".
LiveKit
Joins a LiveKit room as a participant and exchanges audio with your agent over WebRTC. Requires LiveKit's own SDKs:
pip install livekit livekit-apifrom deepeval.voice import LiveKitConnector
connector = LiveKitConnector(agent_name="restaurant-agent")There are EIGHT optional parameters when creating a LiveKitConnector (credentials may also come from the environment):
- [Optional]
url: the LiveKit server URL. When omitted, readsLIVEKIT_URL. Defaulted toNone. - [Optional]
api_key: the LiveKit API key. When omitted, readsLIVEKIT_API_KEY. Defaulted toNone. - [Optional]
api_secret: the LiveKit API secret. When omitted, readsLIVEKIT_API_SECRET. Defaulted toNone. - [Optional]
room_name: the room to join. When omitted, a room is created for the session. Defaulted toNone. - [Optional]
identity: the participant identity for the simulator. Defaulted to"deepeval-test". - [Optional]
agent_name: the name of the agent to dispatch into the room. Defaulted toNone. - [Optional]
turn_detection: how long to wait before deciding your agent has finished speaking β"eager","balanced", or"patient". See Turn Detection. Defaulted to"balanced". - [Optional]
connect_timeout_s: timeout (seconds) for establishing the room connection. Defaulted to15.0.
Generic WebSocket
For custom agents that accept and emit raw audio over a WebSocket, WebSocketConnector lets you describe your agent's message shape β which JSON keys carry audio in and out, whether frames are binary, and what marks the end of a turn.
from deepeval.voice import WebSocketConnector
connector = WebSocketConnector(
url="wss://your-agent.example.com/audio",
send_key="audio",
receive_audio_key="audio",
turn_complete_type="turn_end",
)There are ONE mandatory and TWELVE optional parameters when creating a WebSocketConnector:
url: a string WebSocket URL for your agent (e.g."wss://your-agent.example.com/audio").- [Optional]
headers: a dictionary of HTTP headers for the WebSocket handshake. Defaulted toNone. - [Optional]
sample_rate: an integer sample rate in Hz for outbound audio. Defaulted to24000. - [Optional]
send_key: a string JSON key for outbound audio payloads. Defaulted to"audio". - [Optional]
binary_outbound: a boolean which when set toTrue, sends outbound frames as binary instead of JSON. Defaulted toFalse. - [Optional]
receive_audio_key: a string JSON key for inbound audio payloads. Defaulted to"audio". - [Optional]
binary_inbound: a boolean which when set toTrue, treats inbound frames as binary audio. Defaulted toFalse. - [Optional]
receive_transcript_key: a string JSON key for inbound transcripts when the platform provides them. Defaulted toNone. - [Optional]
turn_complete_type: a string message type that marks end-of-turn. Defaulted toNone. - [Optional]
type_key: a string JSON key used to read the message type. Defaulted to"type". - [Optional]
init_messages: a list of strings or dicts sent after connect. Defaulted to[]. - [Optional]
ready_on: when the session is considered ready β"connect"by default. Defaulted to"connect". - [Optional]
turn_detection: how long to wait before deciding your agent has finished speaking β"eager","balanced", or"patient". See Turn Detection. Defaulted to"balanced".
Callback
Wraps an in-process Python callable, so you can exercise the full TTS β agent β STT pipeline without any network transport β useful for testing your simulation setup before pointing it at a deployed agent.
There are ONE mandatory and THREE optional parameters when creating a CallbackVoiceConnector:
agent: a sync or async callable that accepts userAudioand returns aConnectorTurnor plainAudio.- [Optional]
sample_rate: an integer sample rate in Hz. Defaulted to24000. - [Optional]
encoding: a string container/codec label for audio metadata. Defaulted to"wav". - [Optional]
turn_detection: how long to wait before deciding your agent has finished speaking β"eager","balanced", or"patient". See Turn Detection. Defaulted to"balanced".
Returning a ConnectorTurn
Your callback is the agent. Return either:
- an
Audioreply β the connector wraps it and fillslatency_msfrom wall time, or - a
ConnectorTurnwhen you also want to supply a transcript and/or your own latency.
class ConnectorTurn:
audio: Audio
transcript: Optional[str] = None
latency_ms: Optional[float] = None
interrupted: bool = FalseThere are ONE mandatory and THREE optional fields on a ConnectorTurn:
audio: anAudioobject with the agent's spoken reply.- [Optional]
transcript: a string with the agent's own transcript. When set,deepevaluses it as assistantTurn.contentand skips STT for that turn. Defaulted toNone. - [Optional]
latency_ms: a number measuring time from receiving the user audio to the start of the reply. When omitted, the connector measures wall time around your callback. Defaulted toNone. - [Optional]
interrupted: a boolean set toTruewhen this reply was cut short by a barge-in. Defaulted toFalseβ leave it alone unless you're simulating interruption behavior yourself.
from deepeval.voice import CallbackVoiceConnector
from deepeval.voice.connectors import ConnectorTurn
# Minimal β return Audio or ConnectorTurn(audio=...)
async def my_agent(user_audio) -> ConnectorTurn:
reply_audio = await your_voice_agent(user_audio)
return ConnectorTurn(audio=reply_audio)
# With a known transcript (skips STT) and measured latency
async def my_agent(user_audio) -> ConnectorTurn:
reply_audio, text, latency_ms = await your_voice_agent(user_audio)
return ConnectorTurn(
audio=reply_audio,
transcript=text,
latency_ms=latency_ms,
)
connector = CallbackVoiceConnector(my_agent)Turn Detection
Nothing in an audio stream announces that your agent has finished speaking, so the end of its turn is inferred from silence. Every connector takes a turn_detection preset controlling how long that silence has to last, along with the hard ceiling that stops an agent which never goes quiet from hanging the simulation:
turn_detection | Waits through pauses of | Gives up after | Use it when |
|---|---|---|---|
"eager" | 500ms | 20s | Your agent answers in one breath and you want the floor back quickly. |
"balanced" | 800ms | 30s | Default. Suits most agents. |
"patient" | 2.5s | 120s | Your agent pauses mid-reply β to think, call a tool, or look something up. |
connector = CallbackVoiceConnector(my_agent, turn_detection="patient")Pick by listening to your agent's longest natural pause. Too eager and its turn ends at that pause, cutting the reply short and discarding the rest of it; too patient and the simulator sits through dead air before responding, which inflates the gaps in the recording and slows the run.
When your transport closes each turn explicitly, silence is only the fallback: ElevenLabsConnector, CallbackVoiceConnector, and a WebSocketConnector given a turn_complete_type are all believed over a pause, so the reply is heard out in full however long your agent stops for. Only the ceiling still applies. LiveKitConnector carries a bare audio track with nothing to say when a turn is over, so silence is all it has and the preset matters more.