Voice-AI-Agent-Clean / test_app.py
Toadoum's picture
Upload 12 files
15fbd84 verified
Raw
History Blame Contribute Delete
5.57 kB
"""
Verifies the app ACTUALLY LAUNCHES under the pinned gradio, and that every
event handler is wired with a matching signature.
This is the check that was missing before: the previous app.py used
`stream_every`, which does not exist in gradio 4.44 β€” it would have crashed on
Space startup no matter how correct the rest of the code was.
Models are stubbed, so this tests the app wiring, not the ML.
"""
import os
import sys
import types
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# ── Stub the pipeline so no 3.5GB download happens ───────────────────────────
import pipeline as _pl
def _fake_transcribe(self, audio, sr=16000):
return "duba asusuna sannan ka aika 35000 zuwa Amina"
def _fake_translate(self, text, src, tgt, **kw):
table = {
"duba asusuna sannan ka aika 35000 zuwa Amina":
"check my balance and also send 35000 to amina",
"2234567890": "2234567890",
"eh": "yes",
}
return table.get(text, text)
def _fake_tts(self, text):
return 16000, (np.sin(np.arange(8000) / 20) * 8000).astype(np.int16)
_pl.HausaVoiceAIPipeline.transcribe = _fake_transcribe
_pl.HausaVoiceAIPipeline.transcribe_partial = _fake_transcribe
_pl.HausaVoiceAIPipeline.translate = _fake_translate
_pl.HausaVoiceAIPipeline.synthesize = _fake_tts
_pl.HausaVoiceAIPipeline.hausa_text_to_audio = _fake_tts
_pl.HausaVoiceAIPipeline._load_asr = lambda self: None
_pl.HausaVoiceAIPipeline._load_asr_fast = lambda self: None
_pl.HausaVoiceAIPipeline._load_nllb = lambda self: None
_pl.HausaVoiceAIPipeline._load_tts = lambda self: None
os.environ.setdefault("SHOW_PARTIALS", "0")
PASS, FAIL = "\033[92mPASS\033[0m", "\033[91mFAIL\033[0m"
results = []
def check(name, cond, detail=""):
results.append((name, cond))
print(f" [{PASS if cond else FAIL}] {name}" + (f" β€” {detail}" if detail else ""))
print("\n══ App imports and builds under the pinned gradio ══")
import gradio as gr
print(f" gradio {gr.__version__}")
import app as A
check("app module imported", True)
check("Blocks object built", isinstance(A.demo, gr.Blocks))
print("\n══ Streaming API exists in this gradio version ══")
import inspect
p = inspect.signature(gr.Audio(streaming=True).stream).parameters
check("Audio.stream accepts stream_every", "stream_every" in p)
check("gr.skip available", hasattr(gr, "skip"))
print("\n══ Event handlers are wired ══")
fns = A.demo.fns
check("events registered", len(fns) > 0, f"{len(fns)} handlers")
print("\n══ Text handler: full compound-request conversation ══")
st = None
audio, convo, status, st = A.on_text(
"duba asusuna sannan ka aika 35000 zuwa Amina", st)
check("turn 1 returns audio", audio is not None and audio is not gr.skip())
check("turn 1 acknowledges BOTH tasks",
"one at a time" in convo.lower() or "balance" in convo.lower(),
status[:70])
audio, convo, status, st = A.on_text("2234567890", st)
check("turn 2 delivers balance + continues transfer",
"balance is" in convo.lower() and "amina" in convo.lower())
audio, convo, status, st = A.on_text("eh", st)
check("turn 3 executes the transfer", "sent to amina" in convo.lower())
check("latency breakdown in status", "total" in status, status[-52:])
print("\n══ Empty input does not crash ══")
a, c, s, st2 = A.on_text("", None)
check("empty text handled", a == gr.skip() or a is None)
a, c, s, st2 = A.on_record(None, None)
check("no recording handled", a == gr.skip() or a is None)
print("\n══ Streaming handler with synthetic audio ══")
SR = 16000
def sil(ms, n=0.0005):
k = int(SR * ms / 1000)
return (np.random.randn(k) * n).astype(np.float32)
def sp(ms, amp=0.25):
k = int(SR * ms / 1000)
t = np.arange(k) / SR
s = (np.sin(2*np.pi*120*t) + 0.5*np.sin(2*np.pi*700*t)
+ 0.3*np.sin(2*np.pi*1220*t))
s = s * (0.6 + 0.4*np.sin(2*np.pi*4*t)) + np.random.randn(k)*0.01
return (s / np.abs(s).max() * amp).astype(np.float32)
st = A.new_state()
stream = np.concatenate([sil(400), sp(1600), sil(1200)])
step = int(SR * 0.5)
got_reply = False
n_skips = 0
for i in range(0, len(stream), step):
out = A.on_stream((SR, stream[i:i+step]), st)
check_len = len(out) == 5
audio_o, convo_o, status_o, partial_o, st = out
if audio_o == gr.skip():
n_skips += 1
elif audio_o is not None:
got_reply = True
check("stream handler returns 5 values", check_len)
check("VAD endpointed and produced a reply", got_reply)
check("idle ticks return gr.skip (no player restart)", n_skips > 0,
f"{n_skips} skipped ticks")
print("\n══ Reset ══")
r = A.reset(st)
check("reset returns 5 values", len(r) == 5)
check("history cleared", "Press" in r[1] or "empty" in r[1])
print("\n══ Launch smoke test (real server, then shut down) ══")
try:
A.demo.queue(max_size=4)
_, url, _ = A.demo.launch(prevent_thread_lock=True, quiet=True,
server_port=7899, show_api=False)
check("Gradio server started", True, url or "local")
import urllib.request
code = urllib.request.urlopen("http://127.0.0.1:7899/", timeout=15).getcode()
check("HTTP 200 from the app root", code == 200, f"status {code}")
A.demo.close()
except Exception as e:
check("Gradio server started", False, str(e)[:110])
passed = sum(1 for _, ok in results if ok)
print(f"\n{'='*62}\n {passed}/{len(results)} checks passed\n{'='*62}")
sys.exit(0 if passed == len(results) else 1)