Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.26.0
Phase 3: Real-Audio Validation Report
How real audio got into this sandbox
The user uploaded a real Parquet shard:
/mnt/user-data/uploads/train-00000-of-00010.parquet (464 MB, confirmed
via du), one of ten training shards of
pipecat-ai/smart-turn-data-v3.2-train. Verified as a genuine Parquet
file by its magic bytes (PAR1 at both start and end) before doing
anything else.
No pyarrow, fastparquet, or datasets library is installed in this
sandbox, and none are installable β pip/uv pip install pyarrow
fails with the same 403 host_not_allowed block that covers
huggingface.co (confirmed again this phase: pypi.org returns the
identical proxy denial). So reading this real file required writing a
pure-Python Parquet reader from scratch:
src/turn_detector/parquet_reader.py. This is the single biggest piece of
new engineering this phase, and it's worth documenting honestly, including
the real bugs it had and how they were caught β because "trust but verify"
matters more than usual when the verification code and the code under test
were written by the same process in the same session.
What was built, and the real bugs caught while building it
- Thrift compact-protocol decoder (generic struct/list/map/binary reader) β needed because Parquet's footer metadata is Thrift-encoded.
- Footer parser β extracts schema, row groups, column chunk metadata (codec, sizes, offsets) from the file's trailing metadata block. First real result: this shard has 3,153 rows across 32 row groups, not the ~27,000 I'd have guessed by linearly scaling 270,946/10 β a good reminder not to estimate when you can just read the actual footer.
- Hand-written Snappy decompressor (raw block format) β the shard's
columns are all SNAPPY-compressed (confirmed from the footer, not
assumed). Verified correct via round-trip: decompressed sizes matched
the footer's declared
uncompressed_sizeexactly for every page decoded. - RLE/bit-packed hybrid decoder for definition levels (needed because
every column here is
OPTIONAL/nullable, including scalars).- Real bug #1, caught and fixed: my first version hardcoded a
1-bit definition-level width for every column. This happened to work
for top-level scalar columns (
language,endpoint_bool, etc.) but silently corruptedaudio.bytes, which is nested two levels deep inside theOPTIONALaudiostruct and therefore needs a 2-bit definition level (0 = audio missing, 1 = audio present but bytes null, 2 = value present). The symptom was concrete and easy to catch: every decodedaudio.bytesvalue came backNone, even thoughlanguagefor the same rows decoded correctly. Fixed by computing the true max-definition-level per column from a real schema-tree walk (_build_max_def_levels), not an assumption. - Real bug #2, caught and fixed: dictionary-page decoding only
handled
BYTE_ARRAYvalues;spoken_text(typedINT32, entirely null) hit a dictionary page and raisedValueError: Unsupported dictionary value type: INT32. Fixed by adding an INT32 dictionary decode path. Re-verified afterward:spoken_textdecodes cleanly to 100%Noneacross the row group tested β consistent with the schema-leveldtype: nullfact already confirmed in Phase 1, now independently re-confirmed by actually decoding real values, not just trusting the declared schema.
- Real bug #1, caught and fixed: my first version hardcoded a
1-bit definition-level width for every column. This happened to work
for top-level scalar columns (
- FLAC STREAMINFO parser (
parse_flac_streaminfo) β onceaudio.bytesdecoded correctly, the first 4 bytes werefLaC: the audio is FLAC, not raw WAV. Rather than write a full FLAC decoder (LPC prediction + Rice coding β a much bigger undertaking), I parse just the 34-byteSTREAMINFOmetadata block to get sample rate, channel count, and total samples (hence duration) without decoding any audio β cheap enough to run across the entire 3,153-row shard in a few seconds. Cross-checked againstffmpeg's own reported duration for a sample file: exact match (7.62775s both ways). ffmpeg-based audio decoding for actual PCM samples (real decoding, not header-only):ffmpegis already installed in this sandbox (unlikesoundfile), sosrc/turn_detector/audio_io.pygained anffmpegsubprocess fallback path, used transparently whensoundfileis unavailable. Verified end-to-end: FLAC bytes βffmpegβ WAV β loaded via the same fallback path β duration matches the STREAMINFO-derived duration exactly.
Every one of these was tested against the real file at each step, not just written and assumed correct β see the tool-call history in this conversation for the actual intermediate outputs (row counts, sample values, byte-for-byte size checks) that caught bugs #1 and #2 above.
Step 1: Full-shard inventory (real, all 3,153 rows)
Computed by fully decoding every scalar metadata column plus STREAMINFO-derived duration for every row in the shard (not a sample β this was cheap enough, ~5s, to just do exhaustively):
| Property | Value |
|---|---|
| Total rows | 3,153 |
| Corrupted/unreadable rows | 0 |
endpoint_bool |
1,589 True (END) / 1,564 False (CONTINUE) β 50.4%/49.6%, matching the dataset authors' stated 50:50 design target almost exactly |
| Sample rate | 16,000 Hz for all 3,153 rows (uniform) |
| Channels | 1 (mono) for all 3,153 rows (uniform) |
| Duration | min 0.36s, max 30.0s, mean 7.73s, median 7.32s |
synthetic |
2,575 True (82%) / 578 False (18%) |
midfiller |
1,293 True / 1,187 False / 673 None (null β false β unavailable for those rows) |
endfiller |
810 True / 1,670 False / 673 None |
Language distribution (real, full shard, 23 languages present):
English dominates (789, 25%); Hindi (hin) = 132 rows (4.2%) β a real
measurement, not the ~7.6% rough estimate from Phase 1's 79-row hand
sample (which was too small and non-random to trust for this β this is
exactly why that estimate was labeled provisional at the time).
Source (dataset column) distribution β 12 distinct values, all now
seen (Phase 1's 79-row sample only ever saw 6 of these):
chirp3_1 (1,617), chirp3_2 (852), liva_1 (386), midcentury_1 (108),
mundo_1 (40), rime_2 (39), human_5 (35), orpheus_grammar_1 (24),
orpheus_endfiller_1 (19), orpheus_midfiller_1 (13),
chirp3_3_short (11), human_convcollector_1 (9). The orpheus_* and
human_* sources are new information this phase β not visible in Phase 1's
smaller sample.
Step 2: Verify label mapping
Confirmed the same way documented in Phase 1 (upstream contribution guide:
endpoint_bool=trueβEND, falseβCONTINUE), plus a real smoke test on 60
real clips this phase: mean trailing silence for endpoint_bool=True
clips was 0.300s vs. 0.114s for endpoint_bool=False clips β directionally
consistent with the documented semantics (not proof, but nothing suggests
an inverted or meaningless label).
Step 3: Development sample materialized
Stratified reservoir sample (seed=42, src/turn_detector/data.py),
target 300, actual 300, stratified across endpoint_bool Γ language Γ
dataset Γ synthetic Γ midfiller Γ endfiller Γ duration bucket.
Materialized as real 16kHz mono WAV files at
data/raw/phase3_sample/audio/<id>.wav + data/raw/phase3_sample/metadata.csv.
300/300 clips converted successfully, 0 ffmpeg failures.
Steps 4-6: EXP-001 / EXP-002 / EXP-003(b) results
All SMALL REAL-AUDIO VALIDATION β 210 dev / 90 val clips (random 70/30 split, seed=42), threshold/model tuned on dev only, all metrics below on held-out val:
| EXP-001 (energy/silence) | EXP-002 (classical features + LR) | EXP-003b Ξ (temporal features) | |
|---|---|---|---|
| Accuracy | 0.567 | 0.622 | global-only 0.511 β both 0.622 |
| F1 | 0.400 | 0.575 | +0.112 |
| False END rate | 0.309 | 0.400 | β0.109 (improved) |
| False CONTINUE rate | 0.629 | 0.343 | β0.114 (improved) |
| Params / size | 2 thresholds | 92 params / 2,920 bytes | same classifier, feature-set ablation |
| Latency (this machine) | negligible (rule-based) | mean 12.2ms / p95 22.4ms per clip | n/a |
EXP-003b's hypothesis is supported on this validation set: adding recent-window (100β1000ms tail) features to the global whole-clip statistics improved F1 by +0.112 and reduced both error rates simultaneously β not just a precision/recall tradeoff in one direction. This is a real, if small-sample, result in favor of temporal features mattering for this task, consistent with what streaming-relevant intuition would predict.
Step 7: Slice / filler analysis (real, on EXP-002's val predictions)
- By language: only
eng(n=22) met themin_samples=10bar in the 90-clip val set; every other language slice (including Hindi, n=3 in val) was too small and excluded rather than reported as if meaningful.eng-only: accuracy 0.636, F1 0.600. - By synthetic flag: non-synthetic (n=12): accuracy 0.583, F1 0.667. Synthetic (n=78): accuracy 0.628, F1 0.554. Interesting but n=12 for the non-synthetic slice is thin β flagged as exploratory, not conclusive.
- Filler metadata: 72/90 val rows had
midfillerpopulated (18Noneβ correctly excluded, not treated as "no filler").has_midfiller(n=39): F1 0.529.no_midfiller(n=33): F1 0.593 β filler-present clips were somewhat harder for EXP-002, consistent with the qualitative error pattern below.
Step 8: Error analysis (real false END / false CONTINUE cases)
Full write-up: docs/ERROR_ANALYSIS.md. Headline, real, measured finding:
16 of 22 (73%) false-END errors have a filler flag present, versus
50% for false-CONTINUE errors β and false-END clips have longer mean
trailing silence (0.332s) than false-CONTINUE clips (0.217s). This is
exactly the failure mode the assessment brief names as the hard case:
a pause that acoustically resembles an ending but linguistically isn't
(filler-preceded), which an audio-only, transcript-blind model has no way
to catch beyond acoustic residue.
Step 10/11: Decision
Question: is the acoustic-only approach promising enough to continue, per the three-option framework?
Answer: Option B β acoustic-only provides useful signal but is insufficient. Evidence:
- EXP-002 (0.622 accuracy / 0.575 F1) clearly beats EXP-001 (0.567 / 0.400) and both clearly beat chance-level performance on a roughly balanced task, so acoustic features are picking up real signal β this isn't noise.
- But 0.622 accuracy on a near-50/50 binary task is not close to production-usable on its own, and the error analysis identifies a specific, well-understood, real ceiling: filler-word-preceded pauses are systematically harder, and no acoustic-only feature set can fully resolve that without something closer to lexical/semantic content β the exact thing Whisper Tiny's speech representations (not just its transcription output) are trained to encode implicitly, which is the actual empirical justification the brief asked for, now grounded in a real measured failure mode rather than "the brief suggested it."
- EXP-003b gives a concrete, real reason to believe temporal/recency features help (+0.112 F1), which is a design element worth carrying forward into whichever architecture comes next, Whisper-based or not.
This is 90 validation clips from one of ten shards β a first real signal, not a final verdict. It's a directionally clear enough result to justify moving toward EXP-003 (Whisper Tiny) as the next architecture to actually try, while keeping EXP-002 as the benchmark it needs to beat.
One recommended next experiment
EXP-003: Whisper Tiny encoder representations + the same lightweight classifier head, evaluated on the identical 210/90 dev/val split used here, so the comparison against EXP-002's 0.575 F1 / 0.400 false-END rate / 0.343 false-CONTINUE rate is apples-to-apples. This directly tests whether Whisper's representations close the filler-word gap identified in error analysis, which is the specific, evidenced reason to expect it might help β not just "the brief suggested Whisper Tiny."