fix: protege limpeza da sessão após desconexão
This commit is contained in:
@@ -5,6 +5,7 @@ description = "Realtime voice assistant with WebSocket audio gateway, streaming
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyio>=4.15.1,<5",
|
||||||
"deepagents>=0.7.26",
|
"deepagents>=0.7.26",
|
||||||
"fastapi>=0.141.1",
|
"fastapi>=0.141.1",
|
||||||
"httpx>=0.28,<1",
|
"httpx>=0.28,<1",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ Architecture:
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import anyio
|
||||||
|
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
@@ -1044,93 +1045,93 @@ async def audio_socket(websocket: WebSocket) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Stop Gateway-owned finalization before provider cleanup.
|
with anyio.CancelScope(shield=True):
|
||||||
try:
|
# Stop Gateway-owned finalization before provider cleanup.
|
||||||
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:
|
try:
|
||||||
await pipeline.cancel_turn_task()
|
await cancel_finalization_task()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to cancel voice turn task. session_id=%s",
|
"Failed to cancel finalization task. session_id=%s",
|
||||||
session.session_id if session else None,
|
session.session_id if session else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# A task may have been created before registration failed.
|
# Cancel and await the task owned by VoicePipeline.
|
||||||
# Handle it independently if the pipeline does not own it.
|
if pipeline is not None:
|
||||||
if stt_events_task is not None:
|
|
||||||
task = stt_events_task
|
|
||||||
|
|
||||||
if (
|
|
||||||
pipeline is None
|
|
||||||
or pipeline.turn_task is not task
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
if not task.done():
|
await pipeline.cancel_turn_task()
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
try:
|
|
||||||
await asyncio.wait_for(
|
|
||||||
asyncio.shield(task),
|
|
||||||
timeout=5.0,
|
|
||||||
)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
if not task.cancelled():
|
|
||||||
raise
|
|
||||||
|
|
||||||
except TimeoutError:
|
|
||||||
logger.error(
|
|
||||||
"Unregistered STT task cleanup timed out. "
|
|
||||||
"session_id=%s",
|
|
||||||
session.session_id if session else None,
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to clean up unregistered STT task. "
|
"Failed to cancel voice turn task. session_id=%s",
|
||||||
"session_id=%s",
|
|
||||||
session.session_id if session else None,
|
session.session_id if session else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if task.done():
|
# Handle tasks created before pipeline registration.
|
||||||
stt_events_task = None
|
if stt_events_task is not None:
|
||||||
# Abort STT without waiting for a final transcript.
|
task = stt_events_task
|
||||||
if stt is not None:
|
|
||||||
|
if (
|
||||||
|
pipeline is None
|
||||||
|
or pipeline.turn_task is not task
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
if not task.done():
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(
|
||||||
|
asyncio.shield(task),
|
||||||
|
timeout=5.0,
|
||||||
|
)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
if not task.cancelled():
|
||||||
|
raise
|
||||||
|
|
||||||
|
except TimeoutError:
|
||||||
|
logger.error(
|
||||||
|
"Unregistered STT task cleanup timed out. "
|
||||||
|
"session_id=%s",
|
||||||
|
session.session_id if session else None,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to clean up unregistered STT task. "
|
||||||
|
"session_id=%s",
|
||||||
|
session.session_id if session else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if task.done():
|
||||||
|
stt_events_task = None
|
||||||
|
|
||||||
|
# Abort STT without waiting for a final transcript.
|
||||||
|
if stt is not None:
|
||||||
|
try:
|
||||||
|
await stt.abort_session()
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to abort STT session. session_id=%s",
|
||||||
|
session.session_id if session else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Release session and semantic turn state.
|
||||||
try:
|
try:
|
||||||
await stt.abort_session()
|
if pipeline is not None:
|
||||||
|
pipeline.close_session()
|
||||||
|
elif session is not None:
|
||||||
|
session.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to abort STT session. session_id=%s",
|
"Failed to close voice session. session_id=%s",
|
||||||
session.session_id if session else None,
|
session.session_id if session else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Release session and semantic turn state.
|
# Release provider-owned HTTP connections.
|
||||||
try:
|
if agent is not None:
|
||||||
if pipeline is not None:
|
try:
|
||||||
pipeline.close_session()
|
await close_optional_provider(agent)
|
||||||
elif session is not None:
|
except Exception:
|
||||||
session.close()
|
logger.exception(
|
||||||
except Exception:
|
"Failed to close Agent provider. session_id=%s",
|
||||||
logger.exception(
|
session.session_id if session else None,
|
||||||
"Failed to close voice session. session_id=%s",
|
)
|
||||||
session.session_id if session else None,
|
finally:
|
||||||
)
|
agent = None
|
||||||
|
|
||||||
# Release provider-owned HTTP connections.
|
|
||||||
if agent is not None:
|
|
||||||
try:
|
|
||||||
await close_optional_provider(agent)
|
|
||||||
except Exception:
|
|
||||||
logger.exception(
|
|
||||||
"Failed to close Agent provider. session_id=%s",
|
|
||||||
session.session_id if session else None,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
agent = None
|
|
||||||
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -2924,6 +2924,7 @@ name = "voice-assistant"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
{ name = "deepagents" },
|
{ name = "deepagents" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
@@ -2946,6 +2947,7 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "anyio", specifier = ">=4.15.1,<5" },
|
||||||
{ name = "deepagents", specifier = ">=0.7.26" },
|
{ name = "deepagents", specifier = ">=0.7.26" },
|
||||||
{ name = "fastapi", specifier = ">=0.141.1" },
|
{ name = "fastapi", specifier = ">=0.141.1" },
|
||||||
{ name = "httpx", specifier = ">=0.28,<1" },
|
{ name = "httpx", specifier = ">=0.28,<1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user