mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 01:53:53 +08:00
feat: 统一LLM错误处理并添加系统错误回复配置
在多个LLM提供者中移除try-catch块,将错误处理统一到connection.py的流处理层 添加system_error_response配置项,支持自定义系统错误时的回复内容 在意图识别和流处理中捕获异常时返回配置的错误回复,避免硬编码错误信息 Fixes #2075
This commit is contained in:
@@ -202,6 +202,9 @@ prompt: |
|
|||||||
# 默认系统提示词模板文件
|
# 默认系统提示词模板文件
|
||||||
prompt_template: agent-base-prompt.txt
|
prompt_template: agent-base-prompt.txt
|
||||||
|
|
||||||
|
# 系统错误时的回复
|
||||||
|
system_error_response: "主人,小智现在有点忙,我们稍后再试吧。"
|
||||||
|
|
||||||
# 结束语prompt
|
# 结束语prompt
|
||||||
end_prompt:
|
end_prompt:
|
||||||
enable: true # 是否开启结束语
|
enable: true # 是否开启结束语
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ from config.logger import setup_logging, build_module_string, create_connection_
|
|||||||
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
|
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
|
||||||
from core.utils.prompt_manager import PromptManager
|
from core.utils.prompt_manager import PromptManager
|
||||||
from core.utils.voiceprint_provider import VoiceprintProvider
|
from core.utils.voiceprint_provider import VoiceprintProvider
|
||||||
|
from core.utils.util import get_system_error_response
|
||||||
from core.utils import textUtils
|
from core.utils import textUtils
|
||||||
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
auto_import_modules("plugins_func.functions")
|
auto_import_modules("plugins_func.functions")
|
||||||
@@ -869,6 +871,7 @@ class ConnectionHandler:
|
|||||||
content_arguments = ""
|
content_arguments = ""
|
||||||
self.client_abort = False
|
self.client_abort = False
|
||||||
emotion_flag = True
|
emotion_flag = True
|
||||||
|
try:
|
||||||
for response in llm_responses:
|
for response in llm_responses:
|
||||||
if self.client_abort:
|
if self.client_abort:
|
||||||
break
|
break
|
||||||
@@ -909,6 +912,26 @@ class ConnectionHandler:
|
|||||||
content_detail=content,
|
content_detail=content,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"LLM stream processing error: {e}")
|
||||||
|
self.tts.tts_text_queue.put(
|
||||||
|
TTSMessageDTO(
|
||||||
|
sentence_id=self.sentence_id,
|
||||||
|
sentence_type=SentenceType.LAST,
|
||||||
|
content_type=ContentType.TEXT,
|
||||||
|
content_detail=get_system_error_response(self.config),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if depth == 0:
|
||||||
|
self.tts.tts_text_queue.put(
|
||||||
|
TTSMessageDTO(
|
||||||
|
sentence_id=self.sentence_id,
|
||||||
|
sentence_type=SentenceType.LAST,
|
||||||
|
content_type=ContentType.ACTION,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.llm_finish_task = True
|
||||||
|
return
|
||||||
# 处理function call
|
# 处理function call
|
||||||
if tool_call_flag:
|
if tool_call_flag:
|
||||||
bHasError = False
|
bHasError = False
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ from typing import List, Dict
|
|||||||
from ..base import IntentProviderBase
|
from ..base import IntentProviderBase
|
||||||
from plugins_func.functions.play_music import initialize_music_handler
|
from plugins_func.functions.play_music import initialize_music_handler
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
|
from core.utils.util import get_system_error_response
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
@@ -115,12 +118,16 @@ class IntentProvider(IntentProviderBase):
|
|||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def replyResult(self, text: str, original_text: str):
|
def replyResult(self, text: str, original_text: str):
|
||||||
|
try:
|
||||||
llm_result = self.llm.response_no_stream(
|
llm_result = self.llm.response_no_stream(
|
||||||
system_prompt=text,
|
system_prompt=text,
|
||||||
user_prompt="请根据以上内容,像人类一样说话的口吻回复用户,要求简洁,请直接返回结果。用户现在说:"
|
user_prompt="请根据以上内容,像人类一样说话的口吻回复用户,要求简洁,请直接返回结果。用户现在说:"
|
||||||
+ original_text,
|
+ original_text,
|
||||||
)
|
)
|
||||||
return llm_result
|
return llm_result
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"Error in generating reply result: {e}")
|
||||||
|
return get_system_error_response(self.config)
|
||||||
|
|
||||||
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
|
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
|
||||||
if not self.llm:
|
if not self.llm:
|
||||||
@@ -194,9 +201,13 @@ class IntentProvider(IntentProviderBase):
|
|||||||
llm_start_time = time.time()
|
llm_start_time = time.time()
|
||||||
logger.bind(tag=TAG).debug(f"开始LLM意图识别调用, 模型: {model_info}")
|
logger.bind(tag=TAG).debug(f"开始LLM意图识别调用, 模型: {model_info}")
|
||||||
|
|
||||||
|
try:
|
||||||
intent = self.llm.response_no_stream(
|
intent = self.llm.response_no_stream(
|
||||||
system_prompt=prompt_music, user_prompt=user_prompt
|
system_prompt=prompt_music, user_prompt=user_prompt
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"Error in intent detection LLM call: {e}")
|
||||||
|
return '{"function_call": {"name": "continue_chat"}}'
|
||||||
|
|
||||||
# 记录LLM调用完成时间
|
# 记录LLM调用完成时间
|
||||||
llm_time = time.time() - llm_start_time
|
llm_time = time.time() - llm_start_time
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
check_model_key("AliBLLLM", self.api_key)
|
check_model_key("AliBLLLM", self.api_key)
|
||||||
|
|
||||||
def response(self, session_id, dialogue):
|
def response(self, session_id, dialogue):
|
||||||
try:
|
|
||||||
# 处理dialogue
|
# 处理dialogue
|
||||||
if self.is_No_prompt:
|
if self.is_No_prompt:
|
||||||
dialogue.pop(0)
|
dialogue.pop(0)
|
||||||
@@ -95,10 +94,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
if chunk:
|
if chunk:
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"【阿里百练API服务】响应异常: {e}")
|
|
||||||
yield "【LLM服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
# 阿里百练当前未支持原生的 function call。为保持兼容,这里回退到普通文本流式输出。
|
# 阿里百练当前未支持原生的 function call。为保持兼容,这里回退到普通文本流式输出。
|
||||||
# 上层会按 (content, tool_calls) 的形式消费,这里始终返回 (token, None)
|
# 上层会按 (content, tool_calls) 的形式消费,这里始终返回 (token, None)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class LLMProviderBase(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def response_no_stream(self, system_prompt, user_prompt, **kwargs):
|
def response_no_stream(self, system_prompt, user_prompt, **kwargs):
|
||||||
try:
|
|
||||||
# 构造对话格式
|
# 构造对话格式
|
||||||
dialogue = [
|
dialogue = [
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt},
|
||||||
@@ -22,10 +21,6 @@ class LLMProviderBase(ABC):
|
|||||||
result += part
|
result += part
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in Ollama response generation: {e}")
|
|
||||||
return "【LLM服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
"""
|
"""
|
||||||
Default implementation for function calling (streaming)
|
Default implementation for function calling (streaming)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
logger.bind(tag=TAG).error(model_key_msg)
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
# 取最后一条用户消息
|
# 取最后一条用户消息
|
||||||
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
||||||
conversation_id = self.session_conversation_map.get(session_id)
|
conversation_id = self.session_conversation_map.get(session_id)
|
||||||
@@ -87,10 +86,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
):
|
):
|
||||||
yield event["answer"]
|
yield event["answer"]
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in response generation: {e}")
|
|
||||||
yield "【服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
if len(dialogue) == 2 and functions is not None and len(functions) > 0:
|
if len(dialogue) == 2 and functions is not None and len(functions) > 0:
|
||||||
# 第一次调用llm, 取最后一条用户消息,附加tool提示词
|
# 第一次调用llm, 取最后一条用户消息,附加tool提示词
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
logger.bind(tag=TAG).error(model_key_msg)
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
# 取最后一条用户消息
|
# 取最后一条用户消息
|
||||||
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
||||||
|
|
||||||
@@ -63,10 +62,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in response generation: {e}")
|
|
||||||
yield "【服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
logger.bind(tag=TAG).error(
|
logger.bind(tag=TAG).error(
|
||||||
f"fastgpt暂未实现完整的工具调用(function call),建议使用其他意图识别"
|
f"fastgpt暂未实现完整的工具调用(function call),建议使用其他意图识别"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.api_url = f"{self.base_url}/api/conversation/process" # 拼接完整的 API URL
|
self.api_url = f"{self.base_url}/api/conversation/process" # 拼接完整的 API URL
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
# home assistant语音助手自带意图,无需使用xiaozhi ai自带的,只需要把用户说的话传递给home assistant即可
|
# home assistant语音助手自带意图,无需使用xiaozhi ai自带的,只需要把用户说的话传递给home assistant即可
|
||||||
|
|
||||||
# 提取最后一个 role 为 'user' 的 content
|
# 提取最后一个 role 为 'user' 的 content
|
||||||
@@ -60,11 +59,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
else:
|
else:
|
||||||
logger.bind(tag=TAG).warning("API 返回数据中没有 speech 内容")
|
logger.bind(tag=TAG).warning("API 返回数据中没有 speech 内容")
|
||||||
|
|
||||||
except RequestException as e:
|
|
||||||
logger.bind(tag=TAG).error(f"HTTP 请求错误: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"生成响应时出错: {e}")
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
logger.bind(tag=TAG).error(
|
logger.bind(tag=TAG).error(
|
||||||
f"homeassistant不支持(function call),建议使用其他意图识别"
|
f"homeassistant不支持(function call),建议使用其他意图识别"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.is_qwen3 = self.model_name and self.model_name.lower().startswith("qwen3")
|
self.is_qwen3 = self.model_name and self.model_name.lower().startswith("qwen3")
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
||||||
if self.is_qwen3:
|
if self.is_qwen3:
|
||||||
# 复制对话列表,避免修改原始对话
|
# 复制对话列表,避免修改原始对话
|
||||||
@@ -89,12 +88,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
|
logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in Ollama response generation: {e}")
|
|
||||||
yield "【Ollama服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
try:
|
|
||||||
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
||||||
if self.is_qwen3:
|
if self.is_qwen3:
|
||||||
# 复制对话列表,避免修改原始对话
|
# 复制对话列表,避免修改原始对话
|
||||||
@@ -169,7 +163,3 @@ class LLMProvider(LLMProviderBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error processing function chunk: {e}")
|
logger.bind(tag=TAG).error(f"Error processing function chunk: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}")
|
|
||||||
yield f"【Ollama服务响应异常: {str(e)}】", None
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
return dialogue
|
return dialogue
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
dialogue = self.normalize_dialogue(dialogue)
|
dialogue = self.normalize_dialogue(dialogue)
|
||||||
|
|
||||||
request_params = {
|
request_params = {
|
||||||
@@ -77,6 +76,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
request_params[key] = value
|
request_params[key] = value
|
||||||
|
|
||||||
|
# raise ValueError("model_name is required")
|
||||||
responses = self.client.chat.completions.create(**request_params)
|
responses = self.client.chat.completions.create(**request_params)
|
||||||
|
|
||||||
is_active = True
|
is_active = True
|
||||||
@@ -96,11 +96,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
if is_active:
|
if is_active:
|
||||||
yield content
|
yield content
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in response generation: {e}")
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None, **kwargs):
|
def response_with_functions(self, session_id, dialogue, functions=None, **kwargs):
|
||||||
try:
|
|
||||||
dialogue = self.normalize_dialogue(dialogue)
|
dialogue = self.normalize_dialogue(dialogue)
|
||||||
|
|
||||||
request_params = {
|
request_params = {
|
||||||
@@ -136,7 +132,3 @@ class LLMProvider(LLMProviderBase):
|
|||||||
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
||||||
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in function call streaming: {e}")
|
|
||||||
yield f"【OpenAI服务响应异常: {e}】", None
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
|
||||||
logger.bind(tag=TAG).debug(
|
logger.bind(tag=TAG).debug(
|
||||||
f"Sending request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}"
|
f"Sending request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}"
|
||||||
)
|
)
|
||||||
@@ -59,12 +58,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
|
logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in Xinference response generation: {e}")
|
|
||||||
yield "【Xinference服务响应异常】"
|
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
try:
|
|
||||||
logger.bind(tag=TAG).debug(
|
logger.bind(tag=TAG).debug(
|
||||||
f"Sending function call request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}"
|
f"Sending function call request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}"
|
||||||
)
|
)
|
||||||
@@ -89,10 +83,3 @@ class LLMProvider(LLMProviderBase):
|
|||||||
yield content, tool_calls
|
yield content, tool_calls
|
||||||
elif tool_calls:
|
elif tool_calls:
|
||||||
yield None, tool_calls
|
yield None, tool_calls
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error in Xinference function call: {e}")
|
|
||||||
yield {
|
|
||||||
"type": "content",
|
|
||||||
"content": f"【Xinference服务响应异常: {str(e)}】",
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
msgStr += f"当前时间:{time_str}"
|
msgStr += f"当前时间:{time_str}"
|
||||||
|
|
||||||
if self.save_to_file:
|
if self.save_to_file:
|
||||||
|
try:
|
||||||
result = self.llm.response_no_stream(
|
result = self.llm.response_no_stream(
|
||||||
short_term_memory_prompt,
|
short_term_memory_prompt,
|
||||||
msgStr,
|
msgStr,
|
||||||
@@ -169,12 +170,11 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
temperature=0.2,
|
temperature=0.2,
|
||||||
)
|
)
|
||||||
json_str = extract_json_data(result)
|
json_str = extract_json_data(result)
|
||||||
try:
|
|
||||||
json.loads(json_str) # 检查json格式是否正确
|
json.loads(json_str) # 检查json格式是否正确
|
||||||
self.short_memory = json_str
|
self.short_memory = json_str
|
||||||
self.save_memory_to_file()
|
self.save_memory_to_file()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error:", e)
|
logger.bind(tag=TAG).error(f"Error in saving memory: {e}")
|
||||||
else:
|
else:
|
||||||
# 当save_to_file为False时,调用Java端的聊天记录总结接口
|
# 当save_to_file为False时,调用Java端的聊天记录总结接口
|
||||||
summary_id = session_id if session_id else self.role_id
|
summary_id = session_id if session_id else self.role_id
|
||||||
|
|||||||
@@ -573,3 +573,14 @@ def validate_mcp_endpoint(mcp_endpoint: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_system_error_response(config: dict) -> str:
|
||||||
|
"""获取系统错误时的回复
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 配置字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 系统错误时的回复
|
||||||
|
"""
|
||||||
|
return config.get("system_error_response", "主人,小智现在有点忙,我们稍后再试吧。")
|
||||||
Reference in New Issue
Block a user