fix(llm): 增强API错误处理以包含原始响应信息

改进所有LLM适配器的错误处理逻辑,现在会捕获并传递API原始响应信息
添加对账户余额不足等特定错误类型的识别和处理
统一各适配器的错误响应格式,包含错误代码和消息
This commit is contained in:
lintsinghua 2025-12-19 13:33:49 +08:00
parent 333be11edf
commit 9fe15f0d0b
4 changed files with 58 additions and 22 deletions

View File

@ -75,7 +75,8 @@ class BaiduAdapter(BaseLLMAdapter):
await self.validate_config() await self.validate_config()
return await self.retry(lambda: self._send_request(request)) return await self.retry(lambda: self._send_request(request))
except Exception as error: except Exception as error:
self.handle_error(error, "百度文心一言 API调用失败") api_response = getattr(error, 'api_response', None)
self.handle_error(error, "百度文心一言 API调用失败", api_response=api_response)
async def _send_request(self, request: LLMRequest) -> LLMResponse: async def _send_request(self, request: LLMRequest) -> LLMResponse:
"""发送请求""" """发送请求"""
@ -107,12 +108,19 @@ class BaiduAdapter(BaseLLMAdapter):
if response.status_code != 200: if response.status_code != 200:
error_data = response.json() if response.text else {} error_data = response.json() if response.text else {}
error_msg = error_data.get("error_msg", f"HTTP {response.status_code}") error_msg = error_data.get("error_msg", f"HTTP {response.status_code}")
raise Exception(f"{error_msg}") error_code = error_data.get("error_code", "")
api_response = f"[{error_code}] {error_msg}" if error_code else error_msg
err = LLMError(error_msg, self.config.provider, response.status_code, api_response=api_response)
raise err
data = response.json() data = response.json()
if "error_code" in data: if "error_code" in data:
raise Exception(f"百度API错误: {data.get('error_msg', '未知错误')}") error_msg = data.get('error_msg', '未知错误')
error_code = data.get('error_code', '')
api_response = f"[{error_code}] {error_msg}"
err = LLMError(f"百度API错误: {error_msg}", self.config.provider, api_response=api_response)
raise err
usage = None usage = None
if "usage" in data: if "usage" in data:

View File

@ -22,7 +22,8 @@ class DoubaoAdapter(BaseLLMAdapter):
await self.validate_config() await self.validate_config()
return await self.retry(lambda: self._send_request(request)) return await self.retry(lambda: self._send_request(request))
except Exception as error: except Exception as error:
self.handle_error(error, "豆包 API调用失败") api_response = getattr(error, 'api_response', None)
self.handle_error(error, "豆包 API调用失败", api_response=api_response)
async def _send_request(self, request: LLMRequest) -> LLMResponse: async def _send_request(self, request: LLMRequest) -> LLMResponse:
"""发送请求""" """发送请求"""
@ -50,8 +51,12 @@ class DoubaoAdapter(BaseLLMAdapter):
if response.status_code != 200: if response.status_code != 200:
error_data = response.json() if response.text else {} error_data = response.json() if response.text else {}
error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") error_obj = error_data.get("error", {})
raise Exception(f"{error_msg}") error_msg = error_obj.get("message", f"HTTP {response.status_code}")
error_code = error_obj.get("code", "")
api_response = f"[{error_code}] {error_msg}" if error_code else error_msg
err = LLMError(error_msg, self.config.provider, response.status_code, api_response=api_response)
raise err
data = response.json() data = response.json()
choice = data.get("choices", [{}])[0] choice = data.get("choices", [{}])[0]

View File

@ -19,7 +19,8 @@ class MinimaxAdapter(BaseLLMAdapter):
await self.validate_config() await self.validate_config()
return await self.retry(lambda: self._send_request(request)) return await self.retry(lambda: self._send_request(request))
except Exception as error: except Exception as error:
self.handle_error(error, "MiniMax API调用失败") api_response = getattr(error, 'api_response', None)
self.handle_error(error, "MiniMax API调用失败", api_response=api_response)
async def _send_request(self, request: LLMRequest) -> LLMResponse: async def _send_request(self, request: LLMRequest) -> LLMResponse:
"""发送请求""" """发送请求"""
@ -47,15 +48,23 @@ class MinimaxAdapter(BaseLLMAdapter):
if response.status_code != 200: if response.status_code != 200:
error_data = response.json() if response.text else {} error_data = response.json() if response.text else {}
error_msg = error_data.get("base_resp", {}).get("status_msg", f"HTTP {response.status_code}") base_resp = error_data.get("base_resp", {})
raise Exception(f"{error_msg}") error_msg = base_resp.get("status_msg", f"HTTP {response.status_code}")
error_code = base_resp.get("status_code", "")
api_response = f"[{error_code}] {error_msg}" if error_code else error_msg
err = LLMError(error_msg, self.config.provider, response.status_code, api_response=api_response)
raise err
data = response.json() data = response.json()
# MiniMax 特殊的错误处理 # MiniMax 特殊的错误处理
if data.get("base_resp", {}).get("status_code") != 0: base_resp = data.get("base_resp", {})
error_msg = data.get("base_resp", {}).get("status_msg", "未知错误") if base_resp.get("status_code") != 0:
raise Exception(f"MiniMax API错误: {error_msg}") error_msg = base_resp.get("status_msg", "未知错误")
error_code = base_resp.get("status_code", "")
api_response = f"[{error_code}] {error_msg}"
err = LLMError(f"MiniMax API错误: {error_msg}", self.config.provider, api_response=api_response)
raise err
choice = data.get("choices", [{}])[0] choice = data.get("choices", [{}])[0]

View File

@ -57,17 +57,30 @@ class BaseLLMAdapter(ABC):
self.config.provider self.config.provider
) )
def handle_error(self, error: Any, context: str = "") -> None: def handle_error(self, error: Any, context: str = "", api_response: str = None) -> None:
"""处理API错误""" """处理API错误
Args:
error: 原始异常
context: 错误上下文描述
api_response: API 服务器返回的原始响应信息
"""
message = str(error) message = str(error)
status_code = getattr(error, 'status_code', None) status_code = getattr(error, 'status_code', None)
# 如果错误本身已经有 api_response优先使用
if api_response is None:
api_response = getattr(error, 'api_response', None)
# 针对不同错误类型提供更详细的信息 # 针对不同错误类型提供更详细的信息
if "超时" in message or "timeout" in message.lower(): if "超时" in message or "timeout" in message.lower():
message = f"请求超时 ({self.config.timeout}s)。建议:\n" \ message = f"请求超时 ({self.config.timeout}s)。建议:\n" \
f"1. 检查网络连接是否正常\n" \ f"1. 检查网络连接是否正常\n" \
f"2. 尝试增加超时时间\n" \ f"2. 尝试增加超时时间\n" \
f"3. 验证API端点是否正确" f"3. 验证API端点是否正确"
elif any(keyword in message for keyword in ["余额不足", "资源包", "充值", "quota", "insufficient", "balance"]):
message = f"账户余额不足或配额已用尽,请充值后重试"
status_code = status_code or 402
elif status_code == 401 or status_code == 403: elif status_code == 401 or status_code == 403:
message = f"API认证失败。建议\n" \ message = f"API认证失败。建议\n" \
f"1. 检查API Key是否正确配置\n" \ f"1. 检查API Key是否正确配置\n" \
@ -83,14 +96,15 @@ class BaseLLMAdapter(ABC):
f"1. 稍后重试\n" \ f"1. 稍后重试\n" \
f"2. 检查服务商状态页面\n" \ f"2. 检查服务商状态页面\n" \
f"3. 尝试切换其他LLM提供商" f"3. 尝试切换其他LLM提供商"
full_message = f"{context}: {message}" if context else message full_message = f"{context}: {message}" if context else message
raise LLMError( raise LLMError(
full_message, full_message,
self.config.provider, self.config.provider,
status_code, status_code,
error error,
api_response=api_response
) )
async def retry(self, fn, max_attempts: int = 3, delay: float = 1.0) -> Any: async def retry(self, fn, max_attempts: int = 3, delay: float = 1.0) -> Any: