feat: establish voice assistant V1 baseline
This commit is contained in:
161
src/voice_assistant/stt/mock.py
Normal file
161
src/voice_assistant/stt/mock.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
In-memory mock STT provider for the Voice Assistant.
|
||||
|
||||
Purpose:
|
||||
Simulate a realtime STT provider without external services.
|
||||
|
||||
Responsibilities:
|
||||
- Simulate transcription session lifecycle.
|
||||
- Validate incoming audio chunks.
|
||||
- Expose partial and final transcript events.
|
||||
- Support normal stop and immediate abort.
|
||||
- Provide deterministic behavior for automated tests.
|
||||
|
||||
Non-responsibilities:
|
||||
- Performing real speech recognition.
|
||||
- Managing network connections.
|
||||
- Managing voice turns or Gateway sessions.
|
||||
- Executing Agent or TTS operations.
|
||||
|
||||
Architecture:
|
||||
Tests / VoicePipeline
|
||||
|
|
||||
v
|
||||
STTAdapter
|
||||
|
|
||||
v
|
||||
MockSTTAdapter
|
||||
|
|
||||
v
|
||||
Async event queue
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from .adapter import STTAdapter
|
||||
from .models import TranscriptEvent
|
||||
|
||||
|
||||
class MockSTTAdapter(STTAdapter):
|
||||
def __init__(self) -> None:
|
||||
self._events: asyncio.Queue[
|
||||
TranscriptEvent | None
|
||||
] = asyncio.Queue()
|
||||
|
||||
self._started = False
|
||||
|
||||
async def start_session(self) -> None:
|
||||
self._started = True
|
||||
self._events = asyncio.Queue()
|
||||
|
||||
async def send_audio(self, audio: bytes) -> None:
|
||||
if not self._started:
|
||||
raise RuntimeError(
|
||||
"STT session has not been started."
|
||||
)
|
||||
|
||||
if not audio:
|
||||
raise ValueError(
|
||||
"Audio chunk cannot be empty."
|
||||
)
|
||||
|
||||
async def stop_session(self) -> None:
|
||||
if not self._started:
|
||||
return
|
||||
|
||||
self._started = False
|
||||
|
||||
await self._events.put(None)
|
||||
|
||||
|
||||
async def abort_session(self) -> None:
|
||||
"""
|
||||
Abort transcription and discard pending transcript events.
|
||||
|
||||
Unlike stop_session(), abort_session() must not deliver
|
||||
queued partial or final transcription results.
|
||||
"""
|
||||
if not self._started:
|
||||
return
|
||||
|
||||
# Reject further audio and transcript events.
|
||||
self._started = False
|
||||
|
||||
# Discard queued transcript events from the aborted session.
|
||||
while True:
|
||||
try:
|
||||
self._events.get_nowait()
|
||||
except asyncio.QueueEmpty:
|
||||
break
|
||||
|
||||
# Unblock consumers waiting for the next event.
|
||||
await self._events.put(None)
|
||||
|
||||
|
||||
def events(self) -> AsyncIterator[TranscriptEvent]:
|
||||
return self._event_stream()
|
||||
|
||||
async def _event_stream(
|
||||
self,
|
||||
) -> AsyncIterator[TranscriptEvent]:
|
||||
while True:
|
||||
event = await self._events.get()
|
||||
|
||||
if event is None:
|
||||
break
|
||||
|
||||
yield event
|
||||
|
||||
async def emit_partial(self, text: str) -> None:
|
||||
if not self._started:
|
||||
raise RuntimeError(
|
||||
"STT session has not been started."
|
||||
)
|
||||
|
||||
await self._events.put(
|
||||
TranscriptEvent(
|
||||
type="partial",
|
||||
text=text,
|
||||
)
|
||||
)
|
||||
|
||||
async def emit_final(self, text: str) -> None:
|
||||
if not self._started:
|
||||
raise RuntimeError(
|
||||
"STT session has not been started."
|
||||
)
|
||||
|
||||
await self._events.put(
|
||||
TranscriptEvent(
|
||||
type="final",
|
||||
text=text,
|
||||
)
|
||||
)
|
||||
|
||||
def emit_partial_now(self, text: str) -> None:
|
||||
if not self._started:
|
||||
raise RuntimeError(
|
||||
"STT session has not been started."
|
||||
)
|
||||
|
||||
self._events.put_nowait(
|
||||
TranscriptEvent(
|
||||
type="partial",
|
||||
text=text,
|
||||
)
|
||||
)
|
||||
|
||||
def emit_final_now(self, text: str) -> None:
|
||||
if not self._started:
|
||||
raise RuntimeError(
|
||||
"STT session has not been started."
|
||||
)
|
||||
|
||||
self._events.put_nowait(
|
||||
TranscriptEvent(
|
||||
type="final",
|
||||
text=text,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user