πŸ”₯ DeepEval for TypeScript is now in beta. Read the announcement.

Voice Connectors

Beta

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:

ConnectorVoiceProtocolTalks to
ElevenLabsConnectorWEBSOCKETElevenLabs conversational agents, by agent_id.
LiveKitConnectorWEBRTCAgents deployed in LiveKit rooms.
WebSocketConnectorWEBSOCKETAny custom agent that speaks raw audio over a WebSocket.
CallbackVoiceConnectorCALLBACKAn 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, reads ELEVENLABS_API_KEY from the environment. Defaulted to None.
  • [Optional] region: a string region subdomain for the ElevenLabs API host (e.g. "eu"). Defaulted to None (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-api
from 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, reads LIVEKIT_URL. Defaulted to None.
  • [Optional] api_key: the LiveKit API key. When omitted, reads LIVEKIT_API_KEY. Defaulted to None.
  • [Optional] api_secret: the LiveKit API secret. When omitted, reads LIVEKIT_API_SECRET. Defaulted to None.
  • [Optional] room_name: the room to join. When omitted, a room is created for the session. Defaulted to None.
  • [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 to None.
  • [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 to 15.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 to None.
  • [Optional] sample_rate: an integer sample rate in Hz for outbound audio. Defaulted to 24000.
  • [Optional] send_key: a string JSON key for outbound audio payloads. Defaulted to "audio".
  • [Optional] binary_outbound: a boolean which when set to True, sends outbound frames as binary instead of JSON. Defaulted to False.
  • [Optional] receive_audio_key: a string JSON key for inbound audio payloads. Defaulted to "audio".
  • [Optional] binary_inbound: a boolean which when set to True, treats inbound frames as binary audio. Defaulted to False.
  • [Optional] receive_transcript_key: a string JSON key for inbound transcripts when the platform provides them. Defaulted to None.
  • [Optional] turn_complete_type: a string message type that marks end-of-turn. Defaulted to None.
  • [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 user Audio and returns a ConnectorTurn or plain Audio.
  • [Optional] sample_rate: an integer sample rate in Hz. Defaulted to 24000.
  • [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 Audio reply β€” the connector wraps it and fills latency_ms from wall time, or
  • a ConnectorTurn when 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 = False

There are ONE mandatory and THREE optional fields on a ConnectorTurn:

  • audio: an Audio object with the agent's spoken reply.
  • [Optional] transcript: a string with the agent's own transcript. When set, deepeval uses it as assistant Turn.content and skips STT for that turn. Defaulted to None.
  • [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 to None.
  • [Optional] interrupted: a boolean set to True when this reply was cut short by a barge-in. Defaulted to False β€” 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_detectionWaits through pauses ofGives up afterUse it when
"eager"500ms20sYour agent answers in one breath and you want the floor back quickly.
"balanced"800ms30sDefault. Suits most agents.
"patient"2.5s120sYour 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.

On this page