"""Stream local MP3 fixtures through the backend path to the configured ASR model. Run from talkingq-url: .venv/bin/python scripts/smoke_fun_asr.py MANIFEST --output RESULT Manifest: [{"name": "short", "audio": "path.mp3", "expected_keywords": ["你好"]}] Uses the local ALIYUN_API_KEY. Does not start the app, connect to DB/MQTT, invoke LLM/TTS, or send anything to a physical device. Audio must be 16 kbps MP3 unless --bytes-per-second is supplied. Empty keywords means silence is expected. """ import argparse import asyncio import json from pathlib import Path import sys import time sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from implementations.aliyun_asr import AliyunASR from services.audio_session import AudioSession from handlers.audio_session_handler import handle_audio_packet from handlers.service_connection_handler import send_audio_task, end_asr_session async def run(args): cases = json.loads(args.manifest.read_text()) results = [] for role in ['Banban', 'Baikeboshi']: for case in cases: audio = Path(case['audio']).read_bytes() asr = AliyunASR(selected_role={'role_key': role}) asr.device_id = 'LOCAL_ASR_TEST' asr.session_id = f"{role}-{case['name']}" session = AudioSession(asr_service=asr) session.device_id, session.session_id = asr.device_id, asr.session_id session.send_queue = asyncio.Queue() started = time.perf_counter() result = {'role': role, 'case': case['name'], 'model': asr.model, 'bytes': len(audio)} try: await asr.connect() result['ready_seconds'] = round(time.perf_counter() - started, 3) session.send_task = asyncio.create_task(send_audio_task(session)) for offset in range(0, len(audio), 640): chunk = audio[offset:offset + 640] await handle_audio_packet(session, chunk) await asyncio.sleep(len(chunk) / args.bytes_per_second) ending = time.perf_counter() await end_asr_session(session) result['finish_seconds'] = round(time.perf_counter() - ending, 3) result['transcript'] = session.transcript result['error'] = session.asr_error expected = case['expected_keywords'] result['passed'] = not session.asr_error and ( all(word.casefold() in session.transcript.casefold() for word in expected) if expected else session.transcript == '' ) except Exception as exc: result.update(error=f'{type(exc).__name__}: {exc}', passed=False) finally: if session.send_task: session.send_task.cancel() await asyncio.gather(session.send_task, return_exceptions=True) await asr.close() result['task_id'] = asr.task_id result['resources_released'] = asr._client is None and asr._receiver is None results.append(result) args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(results, ensure_ascii=False, indent=2)) print(json.dumps(result, ensure_ascii=False), flush=True) return all(item['passed'] and item['resources_released'] for item in results) if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('manifest', type=Path) parser.add_argument('--output', type=Path, required=True) parser.add_argument('--bytes-per-second', type=float, default=2000) args = parser.parse_args() sys.exit(0 if asyncio.run(run(args)) else 1)