Files
banban/talkingq-url/scripts/smoke_fun_asr.py
T

79 lines
3.7 KiB
Python

"""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)