realtime / runtime /turn_manager.py
teganmosi
Fix VAD queue corruption, ASR duplicate calls, and LLM TTFT bottlenecks
b3e64c8
Raw
History Blame Contribute Delete
3.97 kB
import logging
from typing import Any
from models.event import Event, EventType
from models.session_state import TurnState
from queues import queue_names
logger = logging.getLogger("s2s_server")
class TurnManager:
def __init__(self, context: Any):
self.context = context
self.state = TurnState.IDLE
# Subscribe handlers
self.context.event_bus.subscribe(EventType.USER_SPEECH_START, self.on_user_speech_start)
self.context.event_bus.subscribe(EventType.USER_SPEECH_END, self.on_user_speech_end)
self.context.event_bus.subscribe(EventType.STABLE_TRANSCRIPT, self.on_stable_transcript)
self.context.event_bus.subscribe(EventType.LLM_STARTED, self.on_llm_started)
self.context.event_bus.subscribe(EventType.BARGE_IN, self.on_barge_in)
self.context.event_bus.subscribe(EventType.CANCEL_ALL, self.on_cancel_all)
self.context.event_bus.subscribe(EventType.TTS_FINISHED, self.on_tts_finished)
self.context.event_bus.subscribe(EventType.TTS_STARTED, self.on_tts_started)
async def on_user_speech_start(self, event: Event):
import uuid
self.context.turn_id = str(uuid.uuid4())[:8]
self.context.logger.info(
f"[{self.context.session_id}] TurnManager: User speech start detected (state: {self.state.name}, turn_id: {self.context.turn_id})"
)
# Interruption check
if self.context.session_config.allow_interruptions and self.state in (
TurnState.THINKING,
TurnState.SPEAKING,
):
await self.context.event_bus.publish(EventType.BARGE_IN)
self.state = TurnState.USER_SPEAKING
await self.context.websocket.send_json({"type": "status", "message": "Listening..."})
async def on_user_speech_end(self, event: Event):
self.context.logger.info(f"[{self.context.session_id}] TurnManager: User speech end detected.")
full_audio = event.payload
self.context.queue_registry.get(queue_names.TRANSCRIPT).put_nowait(full_audio)
async def on_stable_transcript(self, event: Event):
text = event.payload
self.context.logger.info(
f"[{self.context.session_id}] TurnManager: Stable transcript received: '{text}'"
)
await self.context.websocket.send_json({"type": "transcription", "text": text})
# Feed the text to the LLM queue
self.context.queue_registry.get(queue_names.LLM).put_nowait(text)
async def on_llm_started(self, event: Event):
self.state = TurnState.THINKING
await self.context.websocket.send_json({"type": "status", "message": "Thinking..."})
async def on_tts_started(self, event: Event):
self.state = TurnState.SPEAKING
async def on_barge_in(self, event: Event):
self.context.logger.info(f"[{self.context.session_id}] TurnManager: Barge-in triggered.")
self.state = TurnState.INTERRUPTED
await self.context.websocket.send_json({"type": "interrupt"})
await self.context.event_bus.publish(EventType.CANCEL_ALL)
async def on_cancel_all(self, event: Event):
self.context.logger.info(f"[{self.context.session_id}] TurnManager: Cancelling active response.")
# Cancel tasks in task supervisor
await self.context.task_supervisor.cancel_tasks("llm_generation", "tts_generation")
# Perform pipeline queue and accumulator flushes
self.context.cleanup_manager.perform_cleanup()
async def on_tts_finished(self, event: Event):
tts_queue = self.context.queue_registry.get(queue_names.TTS)
if tts_queue.empty() and self.state != TurnState.USER_SPEAKING:
self.context.logger.info(f"[{self.context.session_id}] TurnManager: Turn complete. Returning to IDLE.")
self.state = TurnState.IDLE
await self.context.websocket.send_json({"type": "done"})
await self.context.event_bus.publish(EventType.TURN_COMPLETED)