refactor: 优化相关打断处理

This commit is contained in:
Sakura-RanChen
2026-04-09 10:46:46 +08:00
parent c5f82369e5
commit e08eb48d9a
9 changed files with 204 additions and 164 deletions
+19 -10
View File
@@ -838,20 +838,27 @@ class ConnectionHandler:
self.dialogue.update_system_message(self.prompt) self.dialogue.update_system_message(self.prompt)
def chat(self, query, depth=0): def chat(self, query, depth=0):
# 保存当前任务的sentence_id到局部变量,避免被新任务覆盖
current_sentence_id = None
if query is not None: if query is not None:
self.logger.bind(tag=TAG).info(f"大模型收到用户消息: {query}") self.logger.bind(tag=TAG).info(f"大模型收到用户消息: {query}")
# 为最顶层时新建会话ID和发送FIRST请求 # 为最顶层时新建会话ID和发送FIRST请求
if depth == 0: if depth == 0:
self.sentence_id = str(uuid.uuid4().hex) current_sentence_id = str(uuid.uuid4().hex)
self.sentence_id = current_sentence_id # 更新共享属性
self.dialogue.put(Message(role="user", content=query)) self.dialogue.put(Message(role="user", content=query))
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
sentence_id=self.sentence_id, sentence_id=current_sentence_id,
sentence_type=SentenceType.FIRST, sentence_type=SentenceType.FIRST,
content_type=ContentType.ACTION, content_type=ContentType.ACTION,
) )
) )
else:
# 递归调用时,使用当前的sentence_id
current_sentence_id = self.sentence_id
# 设置最大递归深度,避免无限循环,可根据实际需求调整 # 设置最大递归深度,避免无限循环,可根据实际需求调整
MAX_DEPTH = 5 MAX_DEPTH = 5
@@ -976,7 +983,6 @@ class ConnectionHandler:
# 支持多个并行工具调用 - 使用列表存储 # 支持多个并行工具调用 - 使用列表存储
tool_calls_list = [] # 格式: [{"id": "", "name": "", "arguments": ""}] tool_calls_list = [] # 格式: [{"id": "", "name": "", "arguments": ""}]
content_arguments = "" content_arguments = ""
self.client_abort = False
emotion_flag = True emotion_flag = True
try: try:
for response in llm_responses: for response in llm_responses:
@@ -1013,7 +1019,7 @@ class ConnectionHandler:
response_message.append(content) response_message.append(content)
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
sentence_id=self.sentence_id, sentence_id=current_sentence_id,
sentence_type=SentenceType.MIDDLE, sentence_type=SentenceType.MIDDLE,
content_type=ContentType.TEXT, content_type=ContentType.TEXT,
content_detail=content, content_detail=content,
@@ -1023,7 +1029,7 @@ class ConnectionHandler:
self.logger.bind(tag=TAG).error(f"LLM stream processing error: {e}") self.logger.bind(tag=TAG).error(f"LLM stream processing error: {e}")
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
sentence_id=self.sentence_id, sentence_id=current_sentence_id,
sentence_type=SentenceType.MIDDLE, sentence_type=SentenceType.MIDDLE,
content_type=ContentType.TEXT, content_type=ContentType.TEXT,
content_detail=get_system_error_response(self.config), content_detail=get_system_error_response(self.config),
@@ -1032,7 +1038,7 @@ class ConnectionHandler:
if depth == 0: if depth == 0:
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
sentence_id=self.sentence_id, sentence_id=current_sentence_id,
sentence_type=SentenceType.LAST, sentence_type=SentenceType.LAST,
content_type=ContentType.ACTION, content_type=ContentType.ACTION,
) )
@@ -1085,7 +1091,7 @@ class ConnectionHandler:
# 如需要大模型先处理一轮,添加相关处理后的日志情况 # 如需要大模型先处理一轮,添加相关处理后的日志情况
if len(response_message) > 0: if len(response_message) > 0:
text_buff = "".join(response_message) text_buff = "".join(response_message)
self.tts_MessageText = text_buff self.tts.store_tts_text(current_sentence_id, text_buff)
self.dialogue.put(Message(role="assistant", content=text_buff)) self.dialogue.put(Message(role="assistant", content=text_buff))
response_message.clear() response_message.clear()
@@ -1139,7 +1145,7 @@ class ConnectionHandler:
# 存储对话内容 # 存储对话内容
if len(response_message) > 0: if len(response_message) > 0:
text_buff = "".join(response_message) text_buff = "".join(response_message)
self.tts_MessageText = text_buff self.tts.store_tts_text(current_sentence_id, text_buff)
self.dialogue.put(Message(role="assistant", content=text_buff)) self.dialogue.put(Message(role="assistant", content=text_buff))
# 更新工具调用统计:如果没有调用工具,增加计数 # 更新工具调用统计:如果没有调用工具,增加计数
@@ -1149,7 +1155,7 @@ class ConnectionHandler:
if depth == 0: if depth == 0:
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
sentence_id=self.sentence_id, sentence_id=current_sentence_id,
sentence_type=SentenceType.LAST, sentence_type=SentenceType.LAST,
content_type=ContentType.ACTION, content_type=ContentType.ACTION,
) )
@@ -1282,7 +1288,10 @@ class ConnectionHandler:
# 标记任务完成 # 标记任务完成
self.report_queue.task_done() self.report_queue.task_done()
def clearSpeakStatus(self): def clearSpeakStatus(self, sentence_id=None):
# 如果sentence_id不匹配,说明是旧轮次的回调,不需要执行
if sentence_id is not None and sentence_id != self.sentence_id:
return
self.client_is_speaking = False self.client_is_speaking = False
self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态") self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态")
@@ -219,8 +219,8 @@ async def process_intent_result(
def speak_txt(conn: "ConnectionHandler", text): def speak_txt(conn: "ConnectionHandler", text):
# 记录文本 # 记录文本到 sentence_id 映射
conn.tts_MessageText = text conn.tts.store_tts_text(conn.sentence_id, text)
conn.tts.tts_text_queue.put( conn.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
@@ -26,10 +26,6 @@ async def handleAudioMessage(conn: "ConnectionHandler", audio):
if not hasattr(conn, "vad_resume_task") or conn.vad_resume_task.done(): if not hasattr(conn, "vad_resume_task") or conn.vad_resume_task.done():
conn.vad_resume_task = asyncio.create_task(resume_vad_detection(conn)) conn.vad_resume_task = asyncio.create_task(resume_vad_detection(conn))
return return
# manual 模式下不打断正在播放的内容
if have_voice:
if conn.client_is_speaking and conn.client_listen_mode != "manual":
await handleAbortMessage(conn)
# 设备长时间空闲检测,用于say goodbye # 设备长时间空闲检测,用于say goodbye
await no_voice_close_connect(conn, have_voice) await no_voice_close_connect(conn, have_voice)
# 接收音频 # 接收音频
@@ -94,6 +90,10 @@ async def startToChat(conn: "ConnectionHandler", text):
# 意图未被处理,继续常规聊天流程,使用实际文本内容 # 意图未被处理,继续常规聊天流程,使用实际文本内容
await send_stt_message(conn, actual_text) await send_stt_message(conn, actual_text)
# 准备开始新会话
conn.client_abort = False
conn.executor.submit(conn.chat, actual_text) conn.executor.submit(conn.chat, actual_text)
@@ -45,7 +45,6 @@ async def sendAudioMessage(conn: "ConnectionHandler", sentenceType, audios, text
# 发送结束消息(如果是最后一个文本) # 发送结束消息(如果是最后一个文本)
if sentenceType == SentenceType.LAST: if sentenceType == SentenceType.LAST:
await send_tts_message(conn, "stop", None) await send_tts_message(conn, "stop", None)
conn.client_is_speaking = False
if conn.close_after_chat: if conn.close_after_chat:
await conn.close() await conn.close()
@@ -270,6 +269,8 @@ async def send_tts_message(conn: "ConnectionHandler", state, text=None):
# TTS播放结束 # TTS播放结束
if state == "stop": if state == "stop":
# 保存当前的 sentence_id,用于后续判断是否是当前轮次
current_sentence_id = conn.sentence_id
# 播放提示音 # 播放提示音
tts_notify = conn.config.get("enable_stop_tts_notify", False) tts_notify = conn.config.get("enable_stop_tts_notify", False)
if tts_notify: if tts_notify:
@@ -283,8 +284,8 @@ async def send_tts_message(conn: "ConnectionHandler", state, text=None):
# 停止音频发送循环(仅在流控器已初始化时调用) # 停止音频发送循环(仅在流控器已初始化时调用)
if hasattr(conn, "audio_rate_controller") and conn.audio_rate_controller: if hasattr(conn, "audio_rate_controller") and conn.audio_rate_controller:
conn.audio_rate_controller.stop_sending() conn.audio_rate_controller.stop_sending()
# 清除服务端讲话状态 # 清除服务端讲话状态,传入 sentence_id 用于判断是否是当前轮次
conn.clearSpeakStatus() conn.clearSpeakStatus(current_sentence_id)
# 发送消息到客户端 # 发送消息到客户端
await conn.websocket.send(json.dumps(message)) await conn.websocket.send(json.dumps(message))
@@ -39,6 +39,7 @@ class TTSProvider(TTSProviderBase):
self.ws_url = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/" self.ws_url = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
self.ws = None self.ws = None
self._monitor_task = None self._monitor_task = None
self.activate_session = False
self.last_active_time = None self.last_active_time = None
# 模型和音色配置 # 模型和音色配置
@@ -75,9 +76,9 @@ class TTSProvider(TTSProviderBase):
current_time = time.time() current_time = time.time()
if self.ws and current_time - self.last_active_time < 60: if self.ws and current_time - self.last_active_time < 60:
# 一分钟内才可以复用链接进行连续对话 # 一分钟内才可以复用链接进行连续对话
logger.bind(tag=TAG).info(f"使用已有链接...") logger.bind(tag=TAG).debug(f"使用已有链接...")
return self.ws return self.ws
logger.bind(tag=TAG).info("开始建立新连接...") logger.bind(tag=TAG).debug("开始建立新连接...")
self.ws = await websockets.connect( self.ws = await websockets.connect(
self.ws_url, self.ws_url,
@@ -87,7 +88,7 @@ class TTSProvider(TTSProviderBase):
close_timeout=10, close_timeout=10,
) )
logger.bind(tag=TAG).info("WebSocket连接建立成功") logger.bind(tag=TAG).debug("WebSocket连接建立成功")
self.last_active_time = current_time self.last_active_time = current_time
return self.ws return self.ws
except Exception as e: except Exception as e:
@@ -101,16 +102,22 @@ class TTSProvider(TTSProviderBase):
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
message = self.tts_text_queue.get(timeout=1) message = self.tts_text_queue.get(timeout=1)
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {self.conn.sentence_id}"
)
if message.sentence_type == SentenceType.FIRST: # 过滤旧消息:检查sentence_id是否匹配
self.conn.client_abort = False if message.sentence_id != self.conn.sentence_id:
continue
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {message.sentence_id}"
)
if self.conn.client_abort: if self.conn.client_abort:
try: try:
logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程")
asyncio.run_coroutine_threadsafe(
self.finish_session(self.conn.sentence_id),
loop=self.conn.loop,
)
continue continue
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"取消TTS会话失败: {str(e)}") logger.bind(tag=TAG).error(f"取消TTS会话失败: {str(e)}")
@@ -121,16 +128,16 @@ class TTSProvider(TTSProviderBase):
try: try:
if not getattr(self.conn, "sentence_id", None): if not getattr(self.conn, "sentence_id", None):
self.conn.sentence_id = uuid.uuid4().hex self.conn.sentence_id = uuid.uuid4().hex
logger.bind(tag=TAG).info(f"自动生成新的 会话ID: {self.conn.sentence_id}") logger.bind(tag=TAG).debug(f"自动生成新的 会话ID: {self.conn.sentence_id}")
logger.bind(tag=TAG).info("开始启动TTS会话...") logger.bind(tag=TAG).debug("开始启动TTS会话...")
future = asyncio.run_coroutine_threadsafe( future = asyncio.run_coroutine_threadsafe(
self.start_session(self.conn.sentence_id), self.start_session(self.conn.sentence_id),
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
self.before_stop_play_files.clear() self.before_stop_play_files.clear()
logger.bind(tag=TAG).info("TTS会话启动成功") logger.bind(tag=TAG).debug("TTS会话启动成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"启动TTS会话失败: {str(e)}") logger.bind(tag=TAG).error(f"启动TTS会话失败: {str(e)}")
continue continue
@@ -146,7 +153,6 @@ class TTSProvider(TTSProviderBase):
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
logger.bind(tag=TAG).debug("TTS文本发送成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
continue continue
@@ -161,12 +167,12 @@ class TTSProvider(TTSProviderBase):
if message.sentence_type == SentenceType.LAST: if message.sentence_type == SentenceType.LAST:
try: try:
logger.bind(tag=TAG).info("开始结束TTS会话...") logger.bind(tag=TAG).debug("开始结束TTS会话...")
future = asyncio.run_coroutine_threadsafe( future = asyncio.run_coroutine_threadsafe(
self.finish_session(self.conn.sentence_id), self.finish_session(self.conn.sentence_id),
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result()
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"结束TTS会话失败: {str(e)}") logger.bind(tag=TAG).error(f"结束TTS会话失败: {str(e)}")
continue continue
@@ -202,7 +208,6 @@ class TTSProvider(TTSProviderBase):
await self.ws.send(json.dumps(continue_task_message)) await self.ws.send(json.dumps(continue_task_message))
self.last_active_time = time.time() self.last_active_time = time.time()
logger.bind(tag=TAG).debug(f"已发送文本: {filtered_text}")
return return
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
@@ -216,22 +221,22 @@ class TTSProvider(TTSProviderBase):
async def start_session(self, session_id): async def start_session(self, session_id):
"""启动TTS会话""" """启动TTS会话"""
logger.bind(tag=TAG).info(f"开始会话~~{session_id}") logger.bind(tag=TAG).debug(f"开始会话~~{session_id}")
try: try:
# 检查并清理上一个会话的监听任务 # 上个会话处于激活状态时关闭上个连接新建链接
if ( if self.activate_session:
self._monitor_task is not None
and isinstance(self._monitor_task, Task)
and not self._monitor_task.done()
):
logger.bind(tag=TAG).info("检测到未完成的上个会话,关闭监听任务...")
await self.close() await self.close()
# 设置会话激活标志
self.activate_session = True
# 确保连接可用 # 确保连接可用
await self._ensure_connection() await self._ensure_connection()
# 启动监听任务 # 启动监听任务
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response()) if self._monitor_task is None or self._monitor_task.done():
logger.bind(tag=TAG).debug("启动监听任务...")
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
# 发送run-task消息启动会话 # 发送run-task消息启动会话
run_task_message = { run_task_message = {
@@ -260,7 +265,7 @@ class TTSProvider(TTSProviderBase):
await self.ws.send(json.dumps(run_task_message)) await self.ws.send(json.dumps(run_task_message))
self.last_active_time = time.time() self.last_active_time = time.time()
logger.bind(tag=TAG).info("会话启动请求已发送") logger.bind(tag=TAG).debug("会话启动请求已发送")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}") logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}")
await self.close() await self.close()
@@ -268,7 +273,7 @@ class TTSProvider(TTSProviderBase):
async def finish_session(self, session_id): async def finish_session(self, session_id):
"""结束TTS会话""" """结束TTS会话"""
logger.bind(tag=TAG).info(f"关闭会话~~{session_id}") logger.bind(tag=TAG).debug(f"关闭会话~~{session_id}")
try: try:
if self.ws and session_id: if self.ws and session_id:
# 发送finish-task消息 # 发送finish-task消息
@@ -285,17 +290,6 @@ class TTSProvider(TTSProviderBase):
await self.ws.send(json.dumps(finish_task_message)) await self.ws.send(json.dumps(finish_task_message))
self.last_active_time = time.time() self.last_active_time = time.time()
logger.bind(tag=TAG).info("会话结束请求已发送")
# 等待监听任务完成
if self._monitor_task:
try:
await self._monitor_task
except Exception as e:
logger.bind(tag=TAG).error(
f"等待监听任务完成时发生错误: {str(e)}"
)
finally:
self._monitor_task = None
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}") logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}")
@@ -304,6 +298,8 @@ class TTSProvider(TTSProviderBase):
async def close(self): async def close(self):
"""清理资源""" """清理资源"""
await super().close()
self.activate_session = False
# 取消监听任务 # 取消监听任务
if self._monitor_task: if self._monitor_task:
try: try:
@@ -325,19 +321,13 @@ class TTSProvider(TTSProviderBase):
self.last_active_time = None self.last_active_time = None
async def _start_monitor_tts_response(self): async def _start_monitor_tts_response(self):
"""监听TTS响应""" """监听TTS响应 - 长期运行"""
try: try:
session_finished = False
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
msg = await self.ws.recv() msg = await self.ws.recv()
self.last_active_time = time.time() self.last_active_time = time.time()
# 检查客户端是否中止
if self.conn.client_abort:
logger.bind(tag=TAG).info("收到打断信息,终止监听TTS响应")
break
if isinstance(msg, str): # JSON控制消息 if isinstance(msg, str): # JSON控制消息
try: try:
data = json.loads(msg) data = json.loads(msg)
@@ -348,19 +338,19 @@ class TTSProvider(TTSProviderBase):
self.tts_audio_queue.put((SentenceType.FIRST, [], None)) self.tts_audio_queue.put((SentenceType.FIRST, [], None))
elif event == "result-generated": elif event == "result-generated":
# 发送缓存的数据 # 发送缓存的数据
if self.conn.tts_MessageText: tts_text = self.get_tts_text(self.conn.sentence_id)
if tts_text:
logger.bind(tag=TAG).info( logger.bind(tag=TAG).info(
f"句子语音生成成功: {self.conn.tts_MessageText}" f"句子语音生成成功: {tts_text}"
) )
self.tts_audio_queue.put( self.tts_audio_queue.put(
(SentenceType.FIRST, [], self.conn.tts_MessageText) (SentenceType.FIRST, [], tts_text)
) )
self.conn.tts_MessageText = None self.clear_tts_text(self.conn.sentence_id)
elif event == "task-finished": elif event == "task-finished":
logger.bind(tag=TAG).debug("TTS任务完成~") logger.bind(tag=TAG).debug("TTS任务完成~")
self.activate_session = False
self._process_before_stop_play_files() self._process_before_stop_play_files()
session_finished = True
break
elif event == "task-failed": elif event == "task-failed":
error_code = data["header"].get("error_code", "unknown") error_code = data["header"].get("error_code", "unknown")
error_message = data["header"].get("error_message", "未知错误") error_message = data["header"].get("error_message", "未知错误")
@@ -383,8 +373,8 @@ class TTSProvider(TTSProviderBase):
) )
break break
# 仅在连接异常且非正常结束时才关闭连接 # 连接异常时关闭WebSocket
if not session_finished and self.ws: if self.ws:
try: try:
await self.ws.close() await self.ws.close()
except: except:
@@ -392,6 +382,7 @@ class TTSProvider(TTSProviderBase):
self.ws = None self.ws = None
# 监听任务退出时清理引用 # 监听任务退出时清理引用
finally: finally:
self.activate_session = False
self._monitor_task = None self._monitor_task = None
def audio_to_opus_data_stream( def audio_to_opus_data_stream(
@@ -135,6 +135,7 @@ class TTSProvider(TTSProviderBase):
self.ws_url = f"wss://{self.host}/ws/v1" self.ws_url = f"wss://{self.host}/ws/v1"
self.ws = None self.ws = None
self._monitor_task = None self._monitor_task = None
self.activate_session = False
self.last_active_time = None self.last_active_time = None
# 专属tts设置 # 专属tts设置
@@ -188,7 +189,7 @@ class TTSProvider(TTSProviderBase):
if self.ws and current_time - self.last_active_time < 10: if self.ws and current_time - self.last_active_time < 10:
# 10秒内才可以复用链接进行连续对话 # 10秒内才可以复用链接进行连续对话
self.task_id = uuid.uuid4().hex self.task_id = uuid.uuid4().hex
logger.bind(tag=TAG).info(f"使用已有链接..., task_id: {self.task_id}") logger.bind(tag=TAG).debug(f"使用已有链接..., task_id: {self.task_id}")
return self.ws return self.ws
logger.bind(tag=TAG).debug("开始建立新连接...") logger.bind(tag=TAG).debug("开始建立新连接...")
@@ -214,15 +215,21 @@ class TTSProvider(TTSProviderBase):
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
message = self.tts_text_queue.get(timeout=1) message = self.tts_text_queue.get(timeout=1)
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {self.conn.sentence_id}"
)
if message.sentence_type == SentenceType.FIRST: # 过滤旧消息:检查sentence_id是否匹配
self.conn.client_abort = False if message.sentence_id != self.conn.sentence_id:
continue
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {message.sentence_id}"
)
if self.conn.client_abort: if self.conn.client_abort:
logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程")
asyncio.run_coroutine_threadsafe(
self.finish_session(self.conn.sentence_id),
loop=self.conn.loop,
)
continue continue
if message.sentence_type == SentenceType.FIRST: if message.sentence_type == SentenceType.FIRST:
@@ -252,7 +259,6 @@ class TTSProvider(TTSProviderBase):
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
logger.bind(tag=TAG).debug("TTS文本发送成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
continue continue
@@ -317,22 +323,20 @@ class TTSProvider(TTSProviderBase):
async def start_session(self, task_id): async def start_session(self, task_id):
logger.bind(tag=TAG).debug("开始会话~~") logger.bind(tag=TAG).debug("开始会话~~")
try: try:
# 会话开始时检测上个会话的监听状态 # 上个会话处于激活状态时关闭上个连接新建链接
if ( if self.activate_session:
self._monitor_task is not None
and isinstance(self._monitor_task, Task)
and not self._monitor_task.done()
):
logger.bind(tag=TAG).info(
"检测到未完成的上个会话,关闭监听任务和连接..."
)
await self.close() await self.close()
# 设置会话激活标志
self.activate_session = True
# 建立新连接 # 建立新连接
await self._ensure_connection() await self._ensure_connection()
# 启动监听任务 # 启动监听任务
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response()) if self._monitor_task is None or self._monitor_task.done():
logger.bind(tag=TAG).debug("启动监听任务...")
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
start_request = { start_request = {
"header": { "header": {
@@ -377,15 +381,7 @@ class TTSProvider(TTSProviderBase):
await self.ws.send(json.dumps(stop_request)) await self.ws.send(json.dumps(stop_request))
logger.bind(tag=TAG).debug("会话结束请求已发送") logger.bind(tag=TAG).debug("会话结束请求已发送")
self.last_active_time = time.time() self.last_active_time = time.time()
if self._monitor_task:
try:
await self._monitor_task
except Exception as e:
logger.bind(tag=TAG).error(
f"等待监听任务完成时发生错误: {str(e)}"
)
finally:
self._monitor_task = None
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}") logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}")
# 确保清理资源 # 确保清理资源
@@ -394,6 +390,8 @@ class TTSProvider(TTSProviderBase):
async def close(self): async def close(self):
"""资源清理""" """资源清理"""
await super().close()
self.activate_session = False
if self._monitor_task: if self._monitor_task:
try: try:
self._monitor_task.cancel() self._monitor_task.cancel()
@@ -413,17 +411,13 @@ class TTSProvider(TTSProviderBase):
self.last_active_time = None self.last_active_time = None
async def _start_monitor_tts_response(self): async def _start_monitor_tts_response(self):
"""监听TTS响应""" """监听TTS响应 - 长期运行"""
try: try:
session_finished = False # 标记会话是否正常结束
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
msg = await self.ws.recv() msg = await self.ws.recv()
self.last_active_time = time.time() self.last_active_time = time.time()
# 检查客户端是否中止
if self.conn.client_abort:
logger.bind(tag=TAG).info("收到打断信息,终止监听TTS响应")
break
if isinstance(msg, str): # 文本控制消息 if isinstance(msg, str): # 文本控制消息
try: try:
data = json.loads(msg) data = json.loads(msg)
@@ -436,19 +430,19 @@ class TTSProvider(TTSProviderBase):
) )
elif event_name == "SentenceEnd": elif event_name == "SentenceEnd":
# 发送缓存的数据 # 发送缓存的数据
if self.conn.tts_MessageText: tts_text = self.get_tts_text(self.conn.sentence_id)
if tts_text:
logger.bind(tag=TAG).info( logger.bind(tag=TAG).info(
f"句子语音生成成功: {self.conn.tts_MessageText}" f"句子语音生成成功: {tts_text}"
) )
self.tts_audio_queue.put( self.tts_audio_queue.put(
(SentenceType.FIRST, [], self.conn.tts_MessageText) (SentenceType.FIRST, [], tts_text)
) )
self.conn.tts_MessageText = None self.clear_tts_text(self.conn.sentence_id)
elif event_name == "SynthesisCompleted": elif event_name == "SynthesisCompleted":
logger.bind(tag=TAG).debug(f"会话结束~~") logger.bind(tag=TAG).debug(f"会话结束~~")
self.activate_session = False
self._process_before_stop_play_files() self._process_before_stop_play_files()
session_finished = True
break
except json.JSONDecodeError: except json.JSONDecodeError:
logger.bind(tag=TAG).warning("收到无效的JSON消息") logger.bind(tag=TAG).warning("收到无效的JSON消息")
# 二进制消息(音频数据) # 二进制消息(音频数据)
@@ -462,8 +456,8 @@ class TTSProvider(TTSProviderBase):
f"处理TTS响应时出错: {e}\n{traceback.format_exc()}" f"处理TTS响应时出错: {e}\n{traceback.format_exc()}"
) )
break break
# 仅在连接异常时关闭 # 连接异常时关闭WebSocket
if not session_finished and self.ws: if self.ws:
try: try:
await self.ws.close() await self.ws.close()
except: except:
@@ -471,6 +465,7 @@ class TTSProvider(TTSProviderBase):
self.ws = None self.ws = None
# 监听任务退出时清理引用 # 监听任务退出时清理引用
finally: finally:
self.activate_session = False
self._monitor_task = None self._monitor_task = None
def audio_to_opus_data_stream( def audio_to_opus_data_stream(
+41 -2
View File
@@ -5,6 +5,7 @@ import queue
import asyncio import asyncio
import threading import threading
import traceback import traceback
import concurrent.futures
from core.utils import p3 from core.utils import p3
from datetime import datetime from datetime import datetime
@@ -42,6 +43,8 @@ class TTSProviderBase(ABC):
self.tts_audio_first_sentence = True self.tts_audio_first_sentence = True
self.before_stop_play_files = [] self.before_stop_play_files = []
self.report_on_last = False self.report_on_last = False
# sentence_id 到文本的映射,用于流式TTS获取正确的字幕文本
self._sentence_text_map = {}
self.tts_text_buff = [] self.tts_text_buff = []
self.punctuations = ( self.punctuations = (
@@ -277,14 +280,49 @@ class TTSProviderBase(ABC):
) )
self.audio_play_priority_thread.start() self.audio_play_priority_thread.start()
def store_tts_text(self, sentence_id, text):
"""存储指定 sentence_id 对应的文本,用于流式TTS获取正确的字幕文本
Args:
sentence_id: 会话ID
text: 要存储的文本
"""
if sentence_id and text:
self._sentence_text_map[sentence_id] = text
# 只保留最近 5 个,防止内存泄漏
if len(self._sentence_text_map) > 5:
oldest = next(iter(self._sentence_text_map))
del self._sentence_text_map[oldest]
def get_tts_text(self, sentence_id):
"""获取指定 sentence_id 对应的文本
Args:
sentence_id: 会话ID
Returns:
str: 对应的文本,如果不存在返回 None
"""
return self._sentence_text_map.get(sentence_id)
def clear_tts_text(self, sentence_id):
"""清除指定 sentence_id 的文本
Args:
sentence_id: 会话ID
"""
if sentence_id in self._sentence_text_map:
del self._sentence_text_map[sentence_id]
# 这里默认是非流式的处理方式 # 这里默认是非流式的处理方式
# 流式处理方式请在子类中重写 # 流式处理方式请在子类中重写
def tts_text_priority_thread(self): def tts_text_priority_thread(self):
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
message = self.tts_text_queue.get(timeout=1) message = self.tts_text_queue.get(timeout=1)
if message.sentence_type == SentenceType.FIRST: # 过滤旧消息:检查sentence_id是否匹配
self.conn.client_abort = False if message.sentence_id != self.conn.sentence_id:
continue
if self.conn.client_abort: if self.conn.client_abort:
logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程")
continue continue
@@ -386,6 +424,7 @@ class TTSProviderBase(ABC):
async def close(self): async def close(self):
"""资源清理方法""" """资源清理方法"""
self._sentence_text_map.clear()
if hasattr(self, "ws") and self.ws: if hasattr(self, "ws") and self.ws:
await self.ws.close() await self.ws.close()
@@ -221,7 +221,7 @@ class TTSProvider(TTSProviderBase):
try: try:
if self.ws: if self.ws:
if self.enable_ws_reuse: if self.enable_ws_reuse:
logger.bind(tag=TAG).info(f"使用已有链接...") logger.bind(tag=TAG).debug(f"使用已有链接...")
return self.ws return self.ws
else: else:
try: try:
@@ -272,12 +272,14 @@ class TTSProvider(TTSProviderBase):
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
message = self.tts_text_queue.get(timeout=1) message = self.tts_text_queue.get(timeout=1)
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {self.conn.sentence_id}"
)
if message.sentence_type == SentenceType.FIRST: # 过滤旧消息:检查sentence_id是否匹配
self.conn.client_abort = False if message.sentence_id != self.conn.sentence_id:
continue
logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {message.sentence_id}"
)
if self.conn.client_abort: if self.conn.client_abort:
try: try:
@@ -327,7 +329,6 @@ class TTSProvider(TTSProviderBase):
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
logger.bind(tag=TAG).debug("TTS文本发送成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
continue continue
@@ -387,15 +388,8 @@ class TTSProvider(TTSProviderBase):
async def start_session(self, session_id): async def start_session(self, session_id):
logger.bind(tag=TAG).debug(f"开始会话~~{session_id}") logger.bind(tag=TAG).debug(f"开始会话~~{session_id}")
try: try:
# 等待上一个会话结束,最多等待3次 # 上个会话处于激活状态时关闭上个连接新建链接
for _ in range(3): if self.activate_session:
if not self.activate_session:
break
logger.bind(tag=TAG).debug(f"等待上一个会话结束...")
await asyncio.sleep(0.1)
else:
# 等待超时,强制清除连接状态
logger.bind(tag=TAG).debug("等待上一个会话超时,清除连接状态...")
await self.close() await self.close()
# 设置会话激活标志 # 设置会话激活标志
@@ -468,6 +462,7 @@ class TTSProvider(TTSProviderBase):
async def close(self): async def close(self):
"""资源清理方法""" """资源清理方法"""
await super().close()
self.activate_session = False self.activate_session = False
# 取消监听任务 # 取消监听任务
if self._monitor_task: if self._monitor_task:
@@ -525,14 +520,16 @@ class TTSProvider(TTSProviderBase):
and res.header.message_type == AUDIO_ONLY_RESPONSE and res.header.message_type == AUDIO_ONLY_RESPONSE
): ):
# 处理seed-tts-2.0文本字幕 # 处理seed-tts-2.0文本字幕
if self.resource_type and self.conn.tts_MessageText: if self.resource_type:
logger.bind(tag=TAG).info( tts_text = self.get_tts_text(self.conn.sentence_id)
f"句子语音生成成功: {self.conn.tts_MessageText}" if tts_text:
) logger.bind(tag=TAG).info(
self.tts_audio_queue.put( f"句子语音生成成功: {tts_text}"
(SentenceType.FIRST, [], self.conn.tts_MessageText) )
) self.tts_audio_queue.put(
self.conn.tts_MessageText = None (SentenceType.FIRST, [], tts_text)
)
self.clear_tts_text(self.conn.sentence_id)
self.wav_to_opus_data_audio_raw_stream(res.payload, callback=self.handle_opus) self.wav_to_opus_data_audio_raw_stream(res.payload, callback=self.handle_opus)
elif not self.resource_type and res.optional.event == EVENT_TTSSentenceEnd: elif not self.resource_type and res.optional.event == EVENT_TTSSentenceEnd:
logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}") logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}")
@@ -117,6 +117,7 @@ class TTSProvider(TTSProviderBase):
# WebSocket配置 # WebSocket配置
self.ws = None self.ws = None
self._monitor_task = None self._monitor_task = None
self.activate_session = False
# 序列号管理 # 序列号管理
self.text_seq = 0 self.text_seq = 0
@@ -128,7 +129,7 @@ class TTSProvider(TTSProviderBase):
async def _ensure_connection(self): async def _ensure_connection(self):
"""确保WebSocket连接可用""" """确保WebSocket连接可用"""
try: try:
logger.bind(tag=TAG).info("开始建立新连接...") logger.bind(tag=TAG).debug("开始建立新连接...")
# 生成认证URL # 生成认证URL
auth_url = XunfeiWSAuth.create_auth_url( auth_url = XunfeiWSAuth.create_auth_url(
@@ -141,7 +142,7 @@ class TTSProvider(TTSProviderBase):
ping_timeout=10, ping_timeout=10,
close_timeout=10, close_timeout=10,
) )
logger.bind(tag=TAG).info("WebSocket连接建立成功") logger.bind(tag=TAG).debug("WebSocket连接建立成功")
return self.ws return self.ws
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"建立连接失败: {str(e)}") logger.bind(tag=TAG).error(f"建立连接失败: {str(e)}")
@@ -153,16 +154,21 @@ class TTSProvider(TTSProviderBase):
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
message = self.tts_text_queue.get(timeout=1) message = self.tts_text_queue.get(timeout=1)
# 过滤旧消息:检查sentence_id是否匹配
if message.sentence_id != self.conn.sentence_id:
continue
logger.bind(tag=TAG).debug( logger.bind(tag=TAG).debug(
f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {self.conn.sentence_id}" f"收到TTS任务|{message.sentence_type.name} {message.content_type.name} | 会话ID: {message.sentence_id}"
) )
if message.sentence_type == SentenceType.FIRST: if message.sentence_type == SentenceType.FIRST:
# 重置序列号 # 重置序列号
self.text_seq = 0 self.text_seq = 0
self.conn.client_abort = False
# 增加序列号 # 增加序列号
self.text_seq += 1 self.text_seq += 1
if self.conn.client_abort: if self.conn.client_abort:
logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程")
continue continue
@@ -172,16 +178,16 @@ class TTSProvider(TTSProviderBase):
try: try:
if not getattr(self.conn, "sentence_id", None): if not getattr(self.conn, "sentence_id", None):
self.conn.sentence_id = uuid.uuid4().hex self.conn.sentence_id = uuid.uuid4().hex
logger.bind(tag=TAG).info(f"自动生成新的 会话ID: {self.conn.sentence_id}") logger.bind(tag=TAG).debug(f"自动生成新的 会话ID: {self.conn.sentence_id}")
logger.bind(tag=TAG).info("开始启动TTS会话...") logger.bind(tag=TAG).debug("开始启动TTS会话...")
future = asyncio.run_coroutine_threadsafe( future = asyncio.run_coroutine_threadsafe(
self.start_session(self.conn.sentence_id), self.start_session(self.conn.sentence_id),
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
self.before_stop_play_files.clear() self.before_stop_play_files.clear()
logger.bind(tag=TAG).info("TTS会话启动成功") logger.bind(tag=TAG).debug("TTS会话启动成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"启动TTS会话失败: {str(e)}") logger.bind(tag=TAG).error(f"启动TTS会话失败: {str(e)}")
@@ -199,7 +205,6 @@ class TTSProvider(TTSProviderBase):
loop=self.conn.loop, loop=self.conn.loop,
) )
future.result(timeout=self.tts_timeout) future.result(timeout=self.tts_timeout)
logger.bind(tag=TAG).debug("TTS文本发送成功")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
# 不使用continue,确保后续处理不被中断 # 不使用continue,确保后续处理不被中断
@@ -216,7 +221,7 @@ class TTSProvider(TTSProviderBase):
# 处理会话结束 # 处理会话结束
if message.sentence_type == SentenceType.LAST: if message.sentence_type == SentenceType.LAST:
try: try:
logger.bind(tag=TAG).info("开始结束TTS会话...") logger.bind(tag=TAG).debug("开始结束TTS会话...")
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.finish_session(self.conn.sentence_id), self.finish_session(self.conn.sentence_id),
loop=self.conn.loop, loop=self.conn.loop,
@@ -257,30 +262,28 @@ class TTSProvider(TTSProviderBase):
raise raise
async def start_session(self, session_id): async def start_session(self, session_id):
logger.bind(tag=TAG).info(f"开始会话~~{session_id}") logger.bind(tag=TAG).debug(f"开始会话~~{session_id}")
try: try:
# 会话开始时检测上个会话的监听状态 # 上个会话处于激活状态时关闭上个连接新建链接
if ( if self.activate_session:
self._monitor_task is not None
and isinstance(self._monitor_task, Task)
and not self._monitor_task.done()
):
logger.bind(tag=TAG).info(
"检测到未完成的上个会话,关闭监听任务和连接..."
)
await self.close() await self.close()
# 设置会话激活标志
self.activate_session = True
# 建立新连接 # 建立新连接
await self._ensure_connection() await self._ensure_connection()
# 启动监听任务 # 启动监听任务
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response()) if self._monitor_task is None or self._monitor_task.done():
logger.bind(tag=TAG).debug("启动监听任务...")
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
# 发送会话启动请求 # 发送会话启动请求
start_request = self._build_base_request(status=0) start_request = self._build_base_request(status=0)
await self.ws.send(json.dumps(start_request)) await self.ws.send(json.dumps(start_request))
logger.bind(tag=TAG).info("会话启动请求已发送") logger.bind(tag=TAG).debug("会话启动请求已发送")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}") logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}")
# 确保清理资源 # 确保清理资源
@@ -288,13 +291,13 @@ class TTSProvider(TTSProviderBase):
raise raise
async def finish_session(self, session_id): async def finish_session(self, session_id):
logger.bind(tag=TAG).info(f"关闭会话~~{session_id}") logger.bind(tag=TAG).debug(f"关闭会话~~{session_id}")
try: try:
if self.ws: if self.ws:
# 发送会话结束请求 # 发送会话结束请求
stop_request = self._build_base_request(status=2) stop_request = self._build_base_request(status=2)
await self.ws.send(json.dumps(stop_request)) await self.ws.send(json.dumps(stop_request))
logger.bind(tag=TAG).info("会话结束请求已发送") logger.bind(tag=TAG).debug("会话结束请求已发送")
if self._monitor_task: if self._monitor_task:
try: try:
@@ -310,6 +313,8 @@ class TTSProvider(TTSProviderBase):
async def close(self): async def close(self):
"""资源清理""" """资源清理"""
await super().close()
self.activate_session = False
if self._monitor_task: if self._monitor_task:
try: try:
self._monitor_task.cancel() self._monitor_task.cancel()
@@ -358,17 +363,19 @@ class TTSProvider(TTSProviderBase):
) )
elif status == 2: elif status == 2:
logger.bind(tag=TAG).debug("收到结束状态的音频数据,TTS合成完成") logger.bind(tag=TAG).debug("收到结束状态的音频数据,TTS合成完成")
self.activate_session = False
self._process_before_stop_play_files() self._process_before_stop_play_files()
break break
else: else:
if self.conn.tts_MessageText: tts_text = self.get_tts_text(self.conn.sentence_id)
if tts_text:
logger.bind(tag=TAG).info( logger.bind(tag=TAG).info(
f"句子语音生成成功: {self.conn.tts_MessageText}" f"句子语音生成成功: {tts_text}"
) )
self.tts_audio_queue.put( self.tts_audio_queue.put(
(SentenceType.FIRST, [], self.conn.tts_MessageText) (SentenceType.FIRST, [], tts_text)
) )
self.conn.tts_MessageText = None self.clear_tts_text(self.conn.sentence_id)
try: try:
audio_bytes = base64.b64decode(audio_data) audio_bytes = base64.b64decode(audio_data)
self.opus_encoder.encode_pcm_to_opus_stream( self.opus_encoder.encode_pcm_to_opus_stream(
@@ -405,6 +412,7 @@ class TTSProvider(TTSProviderBase):
self.ws = None self.ws = None
# 监听任务退出时清理引用 # 监听任务退出时清理引用
finally: finally:
self.activate_session = False
self._monitor_task = None self._monitor_task = None
def to_tts(self, text: str) -> list: def to_tts(self, text: str) -> list: