Yash-V1002's picture
Deploy Tiny Turn Detector ZeroGPU
824cd37 verified
Raw
History Blame Contribute Delete
5.12 kB
"""
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()