Spaces:
Running on Zero
Running on Zero
File size: 5,115 Bytes
875e4af 824cd37 875e4af 824cd37 875e4af | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | """
Tiny Turn Detector — Gradio demo.
SANDBOX NOTE: `gradio` and `torch`/`transformers` are not installed in the
environment this project was developed in, and cannot be installed there
(no network route to PyPI — see docs/INITIAL_ANALYSIS.md). This file is
written to run in a normal environment with those installed
(`pip install -r requirements.txt`); it has been validated here by static
syntax/import-structure checking only, not by actually starting the
Gradio server — see the final quality-check report for exactly what was
and wasn't verified.
Run with:
python app.py
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))
import numpy as np
import spaces
try:
import gradio as gr
except ImportError as e:
raise SystemExit(
"gradio is not installed. Install with `pip install -r requirements.txt` "
"in an environment with network access."
) from e
from turn_detector.inference import TurnDetector, TurnDetectorConfig, InferenceError, AudioValidationError
MODEL_INFO_TEXT = """
**Model:** Whisper Tiny (frozen encoder) + Logistic Regression
**Representation:** mean-pooled encoder hidden states (384-dim)
**Encoder params:** 8,208,384 (~32.8MB fp32)
**Classifier params:** 92 (~2.9KB)
**Measured F1 (75-clip real-audio validation, EXP-004):** 0.693
This is a research prototype validated on small real-audio samples from
`pipecat-ai/smart-turn-data-v3.2-train`, **not** the official held-out test
set. See `docs/RESULTS.md` and `docs/ERROR_ANALYSIS.md` for full, honestly
caveated results — including known weaknesses (filler-associated pauses,
small validation-set sizes, GPU-vs-CPU latency differences).
"""
EXPLANATION_TEXT = (
"Predicts whether the speaker appears to have completed their turn "
"(END) or is likely still speaking / pausing mid-thought (CONTINUE), "
"from audio alone. Trained and validated on real clips from one "
"public turn-detection dataset — not claimed to generalize perfectly "
"to all accents, languages, or code-switching patterns. In "
"particular, Hindi-English (\"Hinglish\") code-switching could not be "
"directly verified in the source dataset (no transcripts available), "
"so robustness to it is not a claim this demo makes."
)
_detector: TurnDetector | None = None
def get_detector() -> TurnDetector:
global _detector
if _detector is None:
_detector = TurnDetector(TurnDetectorConfig())
return _detector
@spaces.GPU(duration=30)
def predict_turn(audio) -> tuple[str, dict, str]:
"""Gradio callback. `audio` from gr.Audio(type="numpy") is either None
or a (sample_rate, numpy_array) tuple.
"""
if audio is None:
return "No audio provided.", {}, ""
sr, array = audio
array = np.asarray(array, dtype=np.float32)
# gr.Audio can hand back int16 PCM depending on source; normalize to
# [-1, 1] float32 if it looks like integer-range data.
if np.abs(array).max() > 1.5:
array = array / 32768.0
try:
detector = get_detector()
except InferenceError as e:
return f"Model unavailable: {e}", {}, ""
try:
result = detector.predict(array, sr=sr)
except AudioValidationError as e:
return f"Invalid audio: {e}", {}, ""
except InferenceError as e:
return f"Inference failed: {e}", {}, ""
decision_label = result["decision"]
probs = {"END": result["end_probability"], "CONTINUE": result["continue_probability"]}
detail = (
f"**Decision:** {decision_label}\n\n"
f"**END probability:** {result['end_probability']:.3f}\n\n"
f"**CONTINUE probability:** {result['continue_probability']:.3f}\n\n"
f"**Inference latency:** {result['latency_ms']:.1f} ms "
f"(this machine — see docs/RESULTS.md for the hardware caveat)"
)
return decision_label, probs, detail
def build_app() -> "gr.Blocks":
with gr.Blocks(title="Tiny Turn Detector") as demo:
gr.Markdown("# Tiny Turn Detector")
gr.Markdown("**Audio-based END vs CONTINUE detection for conversational voice AI**")
gr.Markdown(EXPLANATION_TEXT)
with gr.Row():
with gr.Column():
audio_input = gr.Audio(
sources=["upload", "microphone"],
type="numpy",
label="Audio input (WAV or microphone)",
)
submit_btn = gr.Button("Detect", variant="primary")
with gr.Column():
decision_output = gr.Label(label="Decision", elem_id="decision-output")
probs_output = gr.Label(label="Probabilities", num_top_classes=2)
detail_output = gr.Markdown()
submit_btn.click(
fn=predict_turn,
inputs=[audio_input],
outputs=[decision_output, probs_output, detail_output],
)
gr.Markdown("---")
gr.Markdown(MODEL_INFO_TEXT)
return demo
if __name__ == "__main__":
app = build_app()
app.launch()
|