mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
fix: 优化退出流程并添加超时保护
- 添加 is_exiting 标志防止退出流程被中断 - 工具调用添加 30 秒超时保护,避免流程卡死 - TTS 相关操作添加超时保护 - 优化 OpenAI 客户端超时配置
This commit is contained in:
@@ -116,6 +116,8 @@ class ConnectionHandler:
|
||||
self.memory = _memory
|
||||
self.intent = _intent
|
||||
|
||||
self.is_exiting = False # 标记是否正在执行退出流程
|
||||
|
||||
# 为每个连接单独管理声纹识别
|
||||
self.voiceprint_provider = None
|
||||
|
||||
@@ -289,6 +291,10 @@ class ConnectionHandler:
|
||||
|
||||
async def _route_message(self, message):
|
||||
"""消息路由"""
|
||||
# 退出状态丢弃所有消息
|
||||
if self.is_exiting:
|
||||
return
|
||||
|
||||
# 检查是否已经获取到真实的绑定状态
|
||||
if not self.bind_completed_event.is_set():
|
||||
# 还没有获取到真实状态,等待直到获取到真实状态或超时
|
||||
@@ -991,11 +997,23 @@ class ConnectionHandler:
|
||||
)
|
||||
futures_with_data.append((future, tool_call_data))
|
||||
|
||||
TOOL_CALL_TIMEOUT = 30
|
||||
# 等待协程结束(实际等待时长为最慢的那个)
|
||||
tool_results = []
|
||||
for future, tool_call_data in futures_with_data:
|
||||
result = future.result()
|
||||
tool_results.append((result, tool_call_data))
|
||||
try:
|
||||
result = future.result(timeout=TOOL_CALL_TIMEOUT)
|
||||
tool_results.append((result, tool_call_data))
|
||||
except Exception as e:
|
||||
self.logger.bind(tag=TAG).error(
|
||||
f"工具调用超时或异常: {tool_call_data['name']}, 错误: {e}"
|
||||
)
|
||||
# 超时时返回错误响应,避免整个流程卡死
|
||||
from plugins_func.register import Action, ActionResponse
|
||||
tool_results.append((
|
||||
ActionResponse(action=Action.ERROR, result="哎呀,网络遇到点问题,请稍后再试下!"),
|
||||
tool_call_data
|
||||
))
|
||||
|
||||
# 统一处理所有工具调用结果
|
||||
if tool_results:
|
||||
|
||||
@@ -7,6 +7,10 @@ TAG = __name__
|
||||
|
||||
|
||||
async def handleAbortMessage(conn: "ConnectionHandler"):
|
||||
if conn.close_after_chat or conn.is_exiting:
|
||||
conn.logger.bind(tag=TAG).info("退出流程中被打断,直接关闭连接")
|
||||
return
|
||||
|
||||
conn.logger.bind(tag=TAG).info("Abort message received")
|
||||
# 设置成打断状态,会自动打断llm、tts任务
|
||||
conn.client_abort = True
|
||||
|
||||
@@ -32,6 +32,10 @@ async def handle_user_intent(conn: "ConnectionHandler", text):
|
||||
if await check_direct_exit(conn, filtered_text):
|
||||
return True
|
||||
|
||||
# 明确再见不被打断
|
||||
if conn.is_exiting:
|
||||
return True
|
||||
|
||||
# 检查是否是唤醒词
|
||||
if await checkWakeupWords(conn, filtered_text):
|
||||
return True
|
||||
@@ -57,6 +61,7 @@ async def check_direct_exit(conn: "ConnectionHandler", text):
|
||||
if text == cmd:
|
||||
conn.logger.bind(tag=TAG).info(f"识别到明确的退出命令: {text}")
|
||||
await send_stt_message(conn, text)
|
||||
conn.is_exiting = True
|
||||
await conn.close()
|
||||
return True
|
||||
return False
|
||||
@@ -144,7 +149,9 @@ async def process_intent_result(
|
||||
# 使用executor执行函数调用和结果处理
|
||||
def process_function_call():
|
||||
conn.dialogue.put(Message(role="user", content=original_text))
|
||||
|
||||
|
||||
# 使用统一工具处理器处理所有工具调用,添加超时保护
|
||||
TOOL_CALL_TIMEOUT = 30
|
||||
# 使用统一工具处理器处理所有工具调用
|
||||
try:
|
||||
result = asyncio.run_coroutine_threadsafe(
|
||||
@@ -152,11 +159,11 @@ async def process_intent_result(
|
||||
conn, function_call_data
|
||||
),
|
||||
conn.loop,
|
||||
).result()
|
||||
).result(timeout=TOOL_CALL_TIMEOUT)
|
||||
except Exception as e:
|
||||
conn.logger.bind(tag=TAG).error(f"工具调用失败: {e}")
|
||||
result = ActionResponse(
|
||||
action=Action.ERROR, result=str(e), response=str(e)
|
||||
action=Action.ERROR, result="工具调用超时,请一会再试下哈", response="工具调用超时,请一会再试下哈"
|
||||
)
|
||||
|
||||
if result:
|
||||
|
||||
@@ -15,6 +15,8 @@ TAG = __name__
|
||||
|
||||
|
||||
async def handleAudioMessage(conn: "ConnectionHandler", audio):
|
||||
if conn.is_exiting:
|
||||
return
|
||||
# 当前片段是否有人说话
|
||||
have_voice = conn.vad.is_vad(conn, audio)
|
||||
# 如果设备刚刚被唤醒,短暂忽略VAD检测
|
||||
|
||||
@@ -17,8 +17,12 @@ class LLMProvider(LLMProviderBase):
|
||||
self.base_url = config.get("base_url")
|
||||
else:
|
||||
self.base_url = config.get("url")
|
||||
timeout = config.get("timeout", 300)
|
||||
self.timeout = int(timeout) if timeout else 300
|
||||
custom_timeout = httpx.Timeout(
|
||||
pool=2.0,
|
||||
connect=3.0,
|
||||
write=5.0,
|
||||
read=10.0
|
||||
)
|
||||
|
||||
param_defaults = {
|
||||
"max_tokens": int,
|
||||
@@ -45,7 +49,7 @@ class LLMProvider(LLMProviderBase):
|
||||
model_key_msg = check_model_key("LLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=httpx.Timeout(self.timeout))
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=custom_timeout)
|
||||
|
||||
@staticmethod
|
||||
def normalize_dialogue(dialogue):
|
||||
|
||||
@@ -357,7 +357,7 @@ class TTSProviderBase(ABC):
|
||||
sendAudioMessage(self.conn, sentence_type, audio_datas, text),
|
||||
self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
future.result(timeout=15)
|
||||
|
||||
# 记录输出和报告
|
||||
if self.conn.max_output_size > 0 and text:
|
||||
|
||||
@@ -149,6 +149,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.cluster = config.get("cluster")
|
||||
self.resource_id = config.get("resource_id")
|
||||
self.activate_session = False
|
||||
self.timeout = 30
|
||||
if config.get("private_voice"):
|
||||
self.voice = config.get("private_voice")
|
||||
else:
|
||||
@@ -307,7 +308,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.start_session(self.conn.sentence_id),
|
||||
loop=self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
future.result(self.timeout)
|
||||
self.before_stop_play_files.clear()
|
||||
logger.bind(tag=TAG).debug("TTS会话启动成功")
|
||||
except Exception as e:
|
||||
@@ -324,7 +325,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.text_to_speak(message.content_detail, None),
|
||||
loop=self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
future.result(self.timeout)
|
||||
logger.bind(tag=TAG).debug("TTS文本发送成功")
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
|
||||
@@ -344,7 +345,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.finish_session(self.conn.sentence_id),
|
||||
loop=self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
future.result(self.timeout)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"结束TTS会话失败: {str(e)}")
|
||||
continue
|
||||
|
||||
@@ -31,6 +31,7 @@ handle_exit_intent_function_desc = {
|
||||
"handle_exit_intent", handle_exit_intent_function_desc, ToolType.SYSTEM_CTL
|
||||
)
|
||||
def handle_exit_intent(conn: "ConnectionHandler", say_goodbye: str | None = None):
|
||||
conn.is_exiting = True
|
||||
# 处理退出意图
|
||||
try:
|
||||
if say_goodbye is None:
|
||||
|
||||
Reference in New Issue
Block a user