Audio Glitch
The audio glitch metric checks whether a call's audio carried sounds nobody meant to make. It listens to the recording itself — not the transcript — for held tones, dead stream, stream cuts and clipping on both the caller's and the agent's side, and returns a score between 0 and 1, where 1 means every turn with audio was clean.
Usage
Voice metrics evaluate calls produced by the ConversationSimulator in voice mode, or any ConversationalTestCase whose turns carry Audio clips. Start with one ConversationalGolden, simulate one call, then hand what it returns to evaluate():
from deepeval import evaluate
from deepeval.dataset import ConversationalGolden
from deepeval.metrics import AudioGlitchMetric
from deepeval.simulator import ConversationSimulator
from deepeval.voice import ElevenLabsConnector, VoiceConfig
golden = ConversationalGolden(
scenario="The user asks the agent to confirm their start date.",
expected_outcome="The agent reads back the date and asks a follow-up.",
)
simulator = ConversationSimulator(
voice_config=VoiceConfig(
connector=ElevenLabsConnector(agent_id="your-agent-id"),
)
)
test_cases = simulator.simulate([golden])
metric = AudioGlitchMetric(threshold=0.8)
evaluate(test_cases=test_cases, metrics=[metric])There are EIGHT optional parameters when creating an AudioGlitchMetric:
- [Optional]
threshold: a number representing the minimum passing score. Set it toNoneto run the metric in score-only mode. Defaulted to0.5. - [Optional]
model: a string specifying which of OpenAI's GPT models to use, OR any custom LLM model of typeDeepEvalBaseLLM. The model only writes the reason; it never sees the audio and never changes the score. Defaulted togpt-5.4. - [Optional]
include_reason: a boolean which, when set toTrue, includes a reason for the evaluation score. Defaulted toTrue. - [Optional]
async_mode: a boolean which, when set toTrue, enables concurrent execution within themeasure()method. Defaulted toTrue. - [Optional]
strict_mode: a boolean which, when set toTrue, requires a perfect score. Any score below 1 becomes 0, and the threshold is set to 1. Defaulted toFalse. - [Optional]
verbose_mode: a boolean which, when set toTrue, prints the speaker baselines and every event during anevaluate()run. Defaulted toFalse. - [Optional]
flaky: a boolean which, when set toTrue, marks the metric as flaky. Defaulted toFalse. - [Optional]
evaluation_template: a class of typeAudioGlitchTemplate, which allows you to override the default prompt used to write the reason. Defaulted todeepeval'sAudioGlitchTemplate.
Every event the metric found is recorded on score_breakdown. Read it after a standalone measure() call, or turn on verbose_mode to have it printed during an evaluate() run:
metric.measure(test_cases[0])
print(metric.score) # e.g. 0.83
print(metric.score_breakdown)The breakdown lists the turns it checked, the events, and the per-speaker baselines the events were judged against:
{
"turns_with_audio": 12,
"turns_with_events": 2,
"events": [
{
"type": "held_tone",
"turn": 3,
"role": "assistant",
"offset_seconds": 6.44,
"duration_seconds": 1.6,
"start_time": 66.44,
"position": "end",
"pitch_hz": 190.8,
"periodicity": 0.985,
"level_dbfs": -24.0,
"jitter": 0.0039,
"glide": 0.0008,
"hold_ratio": 5.71,
"pitch_ratio": 0.98,
},
{
"type": "dead_stream",
"turn": 8,
"role": "user",
"offset_seconds": 2.1,
"duration_seconds": 3.4,
"start_time": 255.3,
"position": "middle",
"runs_to_end": False,
},
],
"baselines": {
"assistant": {"role": "assistant", "clips": 6, "median_pitch_hz": 195.2, "typical_hold_seconds": 0.28, "digital_silence": False},
"user": {"role": "user", "clips": 6, "median_pitch_hz": 197.0, "typical_hold_seconds": 0.36, "digital_silence": False},
},
"timeline_available": True,
}turn is the turn's zero-based position in the conversation, offset_seconds is where the event sits inside that turn's clip, and start_time is the same moment measured from the start of the call when the clip carries Audio.start_time, so the moment can be found in the recording. An empty events list means nothing was detected.
How Is It Calculated?
AudioGlitchMetric decodes every turn that has audio — caller and agent alike — and records an event for each glitch it finds. The score is the share of those turns with no event:
Each clip is cut into short frames and measured for loudness, pitch and how periodic the sound is. Speech has a texture: its pitch moves from frame to frame, its loudness rises and falls with syllables, and its pauses still carry faint room noise. Each event is a way that texture breaks:
| Event | Detected when |
|---|---|
held_tone | One pitch is held for at least 0.8 s with no frame-to-frame jitter and no glide, and the hold is at least three times as long as anything that speaker otherwise holds, at least 1.3× the pitch of their voice, or 2.5 s long outright. |
dead_stream | At least 0.5 s of exact digital zero between two stretches of speech in the same clip (1 s when the run reaches the end of the clip). Real pauses are quiet, not zero — so this only fires for a speaker whose pauses normally carry noise. |
stream_cut | A clip stops while the speaker is still loud, or ends in digital zero, and the same speaker starts again within 15 s. Needs Audio.start_time on every clip. |
clipping | More than 1% of samples sit at full scale. Carries the clipped span and fraction. |
audio_undecodable | The clip could not be decoded to PCM. Carries a reason. |
The thresholds are relative to the speaker, not fixed: typical_hold_seconds is the 90th percentile of how long that speaker holds any steady pitch across their clips, median_pitch_hz is the pitch of their voice with tone-length runs left out, and digital_silence records whether that speaker's typical pause is partly exact zeros — true for some synthetic voices and never for a recording, in which case a zero run is how their audio is silenced rather than lost stream, and dead_stream is not raised for them. A deep voice or a slow talker therefore sets their own bar. The remaining constants (the 0.8 s minimum, the jitter and glide bounds, the 0.5 s dead-stream floor) are fixed in code, as they are for every other voice metric.
A speaker with a single clip has no hold baseline, so a tone at their own pitch is only caught by the 2.5 s rule. A turn without audio is skipped; a test case where no turn has audio raises rather than scoring.
The reason is written by the LLM from the event list together with each event's turn text, the previous turn's text, the speaker and the position in the turn. It is asked to name every event with its time and to state patterns across events — for example that every tone follows a sentence ending in a number — but it cannot add or remove events, so the score is the same on every run.
FAQs
Why does it check the caller's audio too?
role, so the two sides are easy to tell apart in the breakdown.A tone I can hear was not flagged. Why?
verbose_mode. The usual reasons are that it is shorter than 0.8 s, that its pitch wobbles or slides the way a voice does, or that the speaker has only one clip, so there is no hold baseline and only the 2.5 s rule applies. Also check that the tone is inside a clip at all: TTS artifacts after the last word often fall past the turn's end timestamp.Does the LLM change the score?
include_reason=False to skip it entirely.