Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hiccup

Flag suspected agent-channel gaps in voice-agent call recordings: mid-turn dead air and dropouts the agent caused itself, read straight from stereo audio (agent left, caller right). Works with any voice agent.

hiccup dashboard showing a dead-air gap highlighted on the agent channel

What it is

Voice agents sometimes break their own turn. The transcript looks perfect, but the delivered audio stutters or goes silent mid-sentence — "Thank you for calling," then three seconds of dead air, then "my name is Adam, how can I help you?" The text pipeline was fine; the audio wasn't.

hiccup catches that class of problem from the recording alone. It reads a stereo call (agent on one channel, caller on the other), finds stretches where the agent's audio drops out mid-turn, and rules out the ones the caller actually caused.

The core rule: a suspected gap is a low-energy stretch on the agent channel, bounded by agent speech on both sides, that the caller's own speech doesn't explain. Every result carries a confidence, and the whole file is marked unreliable when the two channels aren't cleanly separated. Audio alone can't prove a gap is a bug, so hiccup surfaces symptoms instead of handing out a pass/fail score.

Energy-based (adaptive per-channel floor), no ML.

Requirements

  • Python 3.10+
  • numpy and soundfile (installed automatically)
  • ffmpeg — optional, only needed for formats soundfile can't decode on its own. WAV always works; most mp3/flac/ogg files work without it.
  • The dashboard adds fastapi and uvicorn, pulled in by the [dashboard] extra.

Install

pip install -e .              # library + CLI
pip install -e ".[dashboard]" # also install the annotation dashboard

(Published to an index later; for now install from a clone.)

Quickstart

As a library:

from hiccup import analyze

result = analyze("call.wav")          # one file -> FileResult
results = analyze(["a.wav", "b.mp3"]) # many -> list[FileResult]

if not result.reliable:
    print("channels not cleanly separated:", result.separation.reasons)

for g in result.gaps:
    print(g.type, g.start_s, g.duration_ms, g.confidence, g.caller_context)

From the command line:

hiccup call.wav                 # human-readable table
hiccup calls/*.wav --json       # machine-readable JSON for many files
hiccup call.wav --agent-channel 1   # agent is the right channel instead of the left

Dashboard (needs the [dashboard] extra) — point it at a folder of recordings and open the browser. Click a file, or drop in a new one with + analyze audio to upload and analyze it on the spot. You get the waveform with each gap highlighted, click-to-seek playback, and confirm/reject buttons that save your verdict to a <recording>.hiccup.json sidecar:

hiccup serve calls/             # http://127.0.0.1:8000
hiccup serve calls/ --port 9000

Combining separate tracks — hiccup needs the agent and caller on separate channels. If your recorder exports them as two isolated mono files (e.g. LiveKit per-participant egress), stitch them into the stereo file hiccup expects. Differing sample rates and lengths are handled for you:

hiccup combine agent.wav caller.wav -o call.wav   # writes L=agent, R=caller
hiccup call.wav

A single mixed/dual-mono recording (both channels carrying the same audio) can't be analyzed — hiccup flags it as unreliable rather than guessing, because separating two speakers from one mixed stream is out of scope for v1.

What it detects

Type What it means
dead_air A mid-turn silence longer than ~800ms while the caller is quiet. The high-confidence case.
micro_dropout A short near-silent hole (packet-loss-like) inside agent speech. Low confidence by default.
clustered_short_gaps Several micro-dropouts bunched together. Reported honestly as clustered gaps, not as linguistic stutter.

The caller channel decides whether a silence counts. If the caller started talking at or before the gap, it's a barge-in and gets suppressed. If the caller only spoke up after the dead air, hiccup flags the silence up to that point and labels it reacted_after.

How it works

The pipeline is a short chain of small pieces:

load (decode stereo) → channel_qa (is the separation trustworthy?) → activity (per-channel speech mask, adaptive noise floor + hysteresis) → detect (the core rule + the caller gate) → report.

If channel_qa decides the two channels are a duplicated mono mix or bleed into each other, the file's results are marked reliable = False — a "blind" analysis never gets to read as clean.

Output

analyze() returns FileResult(path, sample_rate, separation, gaps, summary, reliable). Each gap is Gap(type, start_s, end_s, duration_ms, confidence, caller_context). confidence is 0–1, folding in gap duration and channel-separation quality; caller_context is one of silent, reacted_after, or barge_in_suppressed.

Known limitations (v1)

  • Mid-turn silences between 300 and 800ms aren't flagged. They're too short for the dead-air threshold (~800ms) and not near-silent enough to look like a packet-loss hole, so they fall between the dead_air and micro_dropout buckets. That's deliberate: it keeps natural pauses from tripping the detector.
  • Bleed below the channel_qa threshold can fool the caller gate. When a bit of agent audio leaks into the caller channel, the gate can read it as caller speech and suppress a real gap. That's the price of an audio-only approach, so use isolated or dual-channel recordings when you can.

About

Flag suspected agent-channel gaps in voice-agent call recordings

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages