feat: establish voice assistant V1 baseline
This commit is contained in:
246
tools/stt_gateway_smoke_test.py
Normal file
246
tools/stt_gateway_smoke_test.py
Normal file
@@ -0,0 +1,246 @@
|
||||
import asyncio
|
||||
import json
|
||||
import wave
|
||||
|
||||
import websockets
|
||||
|
||||
|
||||
GATEWAY_URL = "ws://127.0.0.1:8000/ws/audio"
|
||||
|
||||
AUDIO_FILE = (
|
||||
"/home/ubuntu/voice-assistant/audios/chafik_normalize.wav"
|
||||
)
|
||||
|
||||
CHUNK_FRAMES = 1600
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("========================================")
|
||||
print("STT GATEWAY SMOKE TEST")
|
||||
print("========================================")
|
||||
print(f"Gateway : {GATEWAY_URL}")
|
||||
print(f"Audio : {AUDIO_FILE}")
|
||||
print(f"Chunk : {CHUNK_FRAMES} frames")
|
||||
print()
|
||||
|
||||
async with websockets.connect(GATEWAY_URL) as websocket:
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 1. session.start
|
||||
# -------------------------------------------------------------
|
||||
await websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "session.start",
|
||||
"audio": {
|
||||
"encoding": "pcm_s16le",
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
response = json.loads(
|
||||
await websocket.recv()
|
||||
)
|
||||
|
||||
print(
|
||||
f"[RECV] {response['type']}"
|
||||
)
|
||||
|
||||
if response["type"] != "session.ready":
|
||||
raise RuntimeError(
|
||||
f"Expected session.ready, got {response}"
|
||||
)
|
||||
|
||||
session_id = response["session_id"]
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 2. audio.start
|
||||
# -------------------------------------------------------------
|
||||
await websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "audio.start",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
response = json.loads(
|
||||
await websocket.recv()
|
||||
)
|
||||
|
||||
print(
|
||||
f"[RECV] {response['type']}"
|
||||
)
|
||||
|
||||
if response["type"] != "turn.started":
|
||||
raise RuntimeError(
|
||||
f"Expected turn.started, got {response}"
|
||||
)
|
||||
|
||||
turn_id = response["turn_id"]
|
||||
|
||||
print(
|
||||
f"Session : {session_id}"
|
||||
)
|
||||
print(
|
||||
f"Turn : {turn_id}"
|
||||
)
|
||||
print()
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 3. Start a receiver task.
|
||||
#
|
||||
# This is necessary because partial/final events can arrive
|
||||
# while we are still sending audio.
|
||||
# -------------------------------------------------------------
|
||||
received_events: list[dict] = []
|
||||
|
||||
async def receive_events() -> None:
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
event = json.loads(message)
|
||||
|
||||
received_events.append(event)
|
||||
|
||||
event_type = event.get("type")
|
||||
|
||||
if event_type == "stt.partial":
|
||||
print(
|
||||
f"[PARTIAL] {event.get('text', '')}"
|
||||
)
|
||||
|
||||
elif event_type == "stt.final":
|
||||
print(
|
||||
f"[FINAL ] {event.get('text', '')}"
|
||||
)
|
||||
|
||||
elif event_type == "turn.ended":
|
||||
print(
|
||||
"[RECV] turn.ended"
|
||||
)
|
||||
break
|
||||
|
||||
elif event_type == "error":
|
||||
print(
|
||||
"[ERROR]",
|
||||
event,
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
else:
|
||||
print(
|
||||
f"[RECV] {event_type}"
|
||||
)
|
||||
|
||||
receiver_task = asyncio.create_task(
|
||||
receive_events()
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 4. Stream WAV as:
|
||||
#
|
||||
# audio.chunk JSON
|
||||
# binary PCM
|
||||
#
|
||||
# The sequence number belongs to the logical chunk.
|
||||
# -------------------------------------------------------------
|
||||
with wave.open(AUDIO_FILE, "rb") as wav:
|
||||
sequence_number = 0
|
||||
|
||||
while True:
|
||||
audio = wav.readframes(CHUNK_FRAMES)
|
||||
|
||||
if not audio:
|
||||
break
|
||||
|
||||
await websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "audio.chunk",
|
||||
"turn_id": turn_id,
|
||||
"sequence_number": sequence_number,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
await websocket.send(audio)
|
||||
|
||||
sequence_number += 1
|
||||
|
||||
print()
|
||||
print(
|
||||
f"Audio sent: {sequence_number} chunks"
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 5. audio.stop
|
||||
# -------------------------------------------------------------
|
||||
await websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "audio.stop",
|
||||
"turn_id": turn_id,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 6. Wait for:
|
||||
#
|
||||
# stt.final
|
||||
# turn.ended
|
||||
#
|
||||
# The receiver task handles both.
|
||||
# -------------------------------------------------------------
|
||||
await receiver_task
|
||||
|
||||
print()
|
||||
print("========================================")
|
||||
print("RESULT")
|
||||
print("========================================")
|
||||
|
||||
partials = [
|
||||
event
|
||||
for event in received_events
|
||||
if event.get("type") == "stt.partial"
|
||||
]
|
||||
|
||||
finals = [
|
||||
event
|
||||
for event in received_events
|
||||
if event.get("type") == "stt.final"
|
||||
]
|
||||
|
||||
turn_ended = [
|
||||
event
|
||||
for event in received_events
|
||||
if event.get("type") == "turn.ended"
|
||||
]
|
||||
|
||||
print(
|
||||
f"Partial events : {len(partials)}"
|
||||
)
|
||||
print(
|
||||
f"Final events : {len(finals)}"
|
||||
)
|
||||
print(
|
||||
f"Turn ended : {len(turn_ended)}"
|
||||
)
|
||||
|
||||
if finals:
|
||||
print()
|
||||
print("Final transcript:")
|
||||
print(
|
||||
finals[-1].get("text", "")
|
||||
)
|
||||
|
||||
print()
|
||||
print("STT Gateway smoke test finished.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user