implementacao de interrupcao em audio streming
This commit is contained in:
@@ -281,12 +281,110 @@ async def audio_socket(websocket: WebSocket) -> None:
|
||||
stt: STTAdapter | None = None
|
||||
stt_events_task: asyncio.Task | None = None
|
||||
|
||||
|
||||
finalization_task: asyncio.Task[None] | None = None
|
||||
turn_transition_lock = asyncio.Lock()
|
||||
interrupting_turns: set[int] = set()
|
||||
completed_turns: set[int] = set()
|
||||
|
||||
turn_manager = create_turn_manager()
|
||||
agent: AgentAdapter | None = None
|
||||
|
||||
tts: TTSAdapter | None = None
|
||||
pipeline: VoicePipeline | None = None
|
||||
|
||||
async def finalize_turn(
|
||||
turn_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
Complete a turn without blocking WebSocket message reception.
|
||||
|
||||
Normal completion must not race with interruption.
|
||||
"""
|
||||
if pipeline is None or session is None:
|
||||
return
|
||||
|
||||
try:
|
||||
await pipeline.wait_turn_task()
|
||||
|
||||
async with turn_transition_lock:
|
||||
if turn_id in interrupting_turns:
|
||||
return
|
||||
|
||||
if turn_id in completed_turns:
|
||||
return
|
||||
|
||||
if not pipeline.is_active_turn(turn_id):
|
||||
return
|
||||
|
||||
pipeline.complete_turn(turn_id)
|
||||
completed_turns.add(turn_id)
|
||||
|
||||
await send_json(
|
||||
websocket,
|
||||
TurnEnded(
|
||||
session_id=session.session_id,
|
||||
turn_id=turn_id,
|
||||
),
|
||||
)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Turn finalization failed. session_id=%s turn_id=%s",
|
||||
session.session_id,
|
||||
turn_id,
|
||||
)
|
||||
|
||||
async def cancel_finalization_task() -> None:
|
||||
"""
|
||||
Cancel and await the Gateway-owned finalization task.
|
||||
|
||||
This task is separate from the execution task owned
|
||||
by VoicePipeline.
|
||||
"""
|
||||
nonlocal finalization_task
|
||||
|
||||
task = finalization_task
|
||||
|
||||
if task is None:
|
||||
return
|
||||
|
||||
if task is asyncio.current_task():
|
||||
raise RuntimeError(
|
||||
"Finalization task cannot cancel itself."
|
||||
)
|
||||
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.shield(task),
|
||||
timeout=5.0,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
current = asyncio.current_task()
|
||||
|
||||
if current is not None and current.cancelling():
|
||||
raise
|
||||
|
||||
if not task.cancelled():
|
||||
raise
|
||||
|
||||
except TimeoutError:
|
||||
logger.error(
|
||||
"Finalization task cancellation timed out. "
|
||||
"session_id=%s",
|
||||
session.session_id if session else None,
|
||||
)
|
||||
|
||||
finally:
|
||||
if task.done() and finalization_task is task:
|
||||
finalization_task = None
|
||||
|
||||
try:
|
||||
while True:
|
||||
message = await websocket.receive()
|
||||
@@ -729,7 +827,6 @@ async def audio_socket(websocket: WebSocket) -> None:
|
||||
|
||||
# Finish the STT session normally.
|
||||
await stt.stop_session()
|
||||
|
||||
await pipeline.wait_turn_task()
|
||||
stt_events_task = None
|
||||
|
||||
@@ -904,6 +1001,15 @@ async def audio_socket(websocket: WebSocket) -> None:
|
||||
pass
|
||||
|
||||
finally:
|
||||
# Stop Gateway-owned finalization before provider cleanup.
|
||||
try:
|
||||
await cancel_finalization_task()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to cancel finalization task. session_id=%s",
|
||||
session.session_id if session else None,
|
||||
)
|
||||
|
||||
# Cancel and await the task owned by VoicePipeline.
|
||||
if pipeline is not None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user