feat(asr): switch to Qwen streaming and close interrupted providers

This commit is contained in:
stu2not
2026-10-12 16:48:47 +08:00
parent a9769fb9c6
commit ffdd8eaff1
17 changed files with 1250 additions and 368 deletions
@@ -72,6 +72,8 @@ async def handle_start_packet(session: AudioSession, device_id: str, session_id:
from handlers.service_connection_handler import prepare_asr_service
await prepare_asr_service(session, device_id, session_id)
if not session.asr_service.connected:
return
from handlers.service_connection_handler import (
prepare_llm_service,
@@ -122,6 +124,7 @@ async def handle_audio_packet(session: AudioSession, audio_data: bytes):
if session.send_queue.qsize() < 100: # 设置合理的上限
await session.send_queue.put(audio_data)
else:
session.asr_error = "音频队列溢出,录音不完整"
session_logger.warning("unknown", "unknown", "音频队列过大,丢弃部分数据")
@@ -130,17 +133,12 @@ async def handle_end_packet(session: AudioSession, session_key: tuple):
device_id, session_id = session_key
session.end_time = time.perf_counter()
session_logger.info(device_id, session_id, "会话结束")
if session.send_task:
session.send_task.cancel()
try:
await session.send_task
except asyncio.CancelledError:
pass
from handlers.service_connection_handler import end_asr_session
from handlers.transcription_handler import handle_transcription
await end_asr_session(session)
await handle_transcription(session, device_id, session_id)
if not interrupt_handler.is_interrupted(session_key):
await handle_transcription(session, device_id, session_id)
if session.llm_service and hasattr(session.llm_service, "close"):
await session.llm_service.close()
if session.tts_service:
@@ -4,9 +4,34 @@ from services.task_manager import task_manager
from utils.logger import session_logger
from services.interrupt_handler import interrupt_handler
import time
from config import settings
async def _connect_with_interrupt_cleanup(service, session_key):
"""Own warmup connections even when interruption races with connect()."""
if interrupt_handler.is_interrupted(session_key):
return False
await interrupt_handler.register_cleanup_handler(
session_key, service.close,
priority=interrupt_handler.PRIORITY["NETWORK_CONNECTION"],
)
try:
await service.connect()
if interrupt_handler.is_interrupted(session_key):
await service.close()
return False
return True
except asyncio.CancelledError:
await service.close()
raise
except Exception:
await service.close()
raise
async def prepare_asr_service(session, device_id, session_id):
session.asr_error = None
session.asr_service.sample_rate = session.sample_rate
if hasattr(session.asr_service, "device_id"):
session.asr_service.device_id = device_id
if hasattr(session.asr_service, "session_id"):
@@ -20,7 +45,8 @@ async def prepare_llm_service(session, device_id, session_id, selected_role):
if hasattr(session.llm_service, "session_id"):
session.llm_service.session_id = session_id
if hasattr(session.llm_service, "connect"):
await session.llm_service.connect()
if not await _connect_with_interrupt_cleanup(session.llm_service, (device_id, session_id)):
return
from services.conversation_history import conversation_history_manager
from config import settings
@@ -58,12 +84,29 @@ async def establish_asr_connection(session: AudioSession):
)
await session.asr_service.connect()
if not session.asr_service.connected:
session_logger.error("unknown", "unknown", "语音识别 WebSocket 连接未建立")
return
raise RuntimeError("语音识别 WebSocket 连接未建立")
if session.device_id and session.session_id:
if interrupt_handler.is_interrupted((session.device_id, session.session_id)):
raise RuntimeError("连接期间会话已中断")
await interrupt_handler.register_cleanup_handler(
(session.device_id, session.session_id), session.asr_service.close,
priority=interrupt_handler.PRIORITY["NETWORK_CONNECTION"],
)
await interrupt_handler.register_cleanup_handler(
(session.device_id, session.session_id), session.cancel_send_task,
priority=interrupt_handler.PRIORITY["TASK_CANCELLATION"],
)
session_logger.info(
"unknown", "unknown", f"语音识别连接已建立,采样率: {session.sample_rate}Hz"
)
except asyncio.CancelledError:
await session.cancel_send_task()
await session.asr_service.close()
raise
except Exception as e:
session.asr_error = f"{type(e).__name__}: {e}"
await session.cancel_send_task()
await session.asr_service.close()
session_logger.error(
"unknown", "unknown", f"建立语音识别 WebSocket 连接失败: {e}", exc_info=True
)
@@ -80,7 +123,10 @@ async def establish_tts_connection(session: AudioSession):
)
return
await session.tts_service.connect()
if not await _connect_with_interrupt_cleanup(
session.tts_service, (session.device_id, session.session_id)
):
return
if not session.tts_service.connected:
session_logger.error(
@@ -113,19 +159,24 @@ async def send_audio_task(session: AudioSession):
session.device_id, session.session_id, "发送音频任务被中断"
)
break
audio_data = await session.send_queue.get()
if audio_data is None: # 结束信号
break
await session.asr_service.send_audio(audio_data)
queue = session.send_queue
audio_data = await queue.get()
try:
if audio_data is None: # Drain all preceding audio before finish-task.
break
await session.asr_service.send_audio(audio_data)
finally:
queue.task_done()
except asyncio.CancelledError:
session.asr_error = session.asr_error or "发送音频任务被取消"
session_logger.info(
session.device_id or "unknown",
session.session_id or "unknown",
"发送音频数据任务被取消",
)
except Exception as e:
session.asr_error = str(e)
session_logger.error(
session.device_id or "unknown",
session.session_id or "unknown",
@@ -135,51 +186,51 @@ async def send_audio_task(session: AudioSession):
async def end_asr_session(session: AudioSession):
if session.asr_service.connected:
try:
asr_end_start_time = time.perf_counter()
await session.asr_service.send_end()
await session.asr_service.receive_results()
session.transcript = session.asr_service.transcript
asr_end_elapsed = time.perf_counter() - asr_end_start_time
session_logger.info(
session.device_id or "unknown",
session.session_id or "unknown",
f"ASR结束延迟: 从发送结束包到获取最终结果耗时 {asr_end_elapsed:.2f} 秒",
"""Drain device audio, collect the complete transcript, always release ASR."""
session.transcript = ""
try:
session_key = (session.device_id, session.session_id)
if interrupt_handler.is_interrupted(session_key):
raise RuntimeError("识别会话已中断")
if session.send_task and not session.send_task.done():
await session.send_queue.put(None)
await asyncio.wait_for(
asyncio.shield(session.send_task),
timeout=settings.asr_queue_drain_timeout_seconds,
)
except Exception as e:
session_logger.error(
session.device_id or "unknown",
session.session_id or "unknown",
f"ASR结束处理出错: {e}",
exc_info=True,
)
finally:
if session.send_task:
session.send_task.cancel()
try:
await asyncio.wait_for(session.send_task, timeout=2.0)
except (asyncio.CancelledError, asyncio.TimeoutError):
pass
except Exception as e:
session_logger.error(
session.device_id or "unknown",
session.session_id or "unknown",
f"取消发送任务时出错: {e}",
)
finally:
session.send_task = None
await session.asr_service.close()
session_logger.info(
session.device_id or "unknown",
session.session_id or "unknown",
"语音识别 WebSocket 连接和 ClientSession 已关闭",
)
else:
if session.asr_error:
raise RuntimeError(session.asr_error)
if session.send_queue and not session.send_queue.empty():
raise RuntimeError("发送任务提前退出,音频未完整发送")
if interrupt_handler.is_interrupted(session_key):
raise RuntimeError("识别会话已中断")
asr_end_start_time = time.perf_counter()
await session.asr_service.send_end()
if interrupt_handler.is_interrupted(session_key):
raise RuntimeError("识别会话已中断")
session.transcript = await session.asr_service.receive_results()
session_logger.info(
session.device_id or "unknown", session.session_id or "unknown",
f"ASR结束延迟: {time.perf_counter() - asr_end_start_time:.2f} 秒",
)
except Exception as e:
session.asr_error = f"{type(e).__name__}: {e}"
session.transcript = ""
session_logger.error(
session.device_id or "unknown",
session.session_id or "unknown",
"语音识别 WebSocket 连接未建立",
session.device_id or "unknown", session.session_id or "unknown",
f"ASR结束处理出错: {session.asr_error}",
)
finally:
if session.send_task:
session.send_task.cancel()
try:
await asyncio.wait_for(session.send_task, timeout=2.0)
except (asyncio.CancelledError, asyncio.TimeoutError):
pass
finally:
session.send_task = None
await session.asr_service.close()
session_logger.info(
session.device_id or "unknown", session.session_id or "unknown",
"语音识别 WebSocket 连接和 ClientSession 已关闭",
)