246 lines
6.4 KiB
Python
246 lines
6.4 KiB
Python
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()) |