mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
fix: 增强嵌入式错误的状态码提取与处理
- ParsedResponse 新增 embedded_status_code 字段存储嵌套状态码 - 新增 _extract_embedded_status_code 函数支持从多种格式提取状态码 - 非流式响应增加嵌套错误检测逻辑 - 统一使用 embedded_status_code 替代从 error_type 解析数字的方式 - fallback_orchestrator 使用嵌入式状态码进行错误记录
This commit is contained in:
@@ -20,6 +20,7 @@ Chat Handler Base - Chat API 格式的通用基类
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, AsyncGenerator, Callable, Dict, Optional
|
from typing import Any, AsyncGenerator, Callable, Dict, Optional
|
||||||
|
|
||||||
@@ -803,6 +804,26 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
|||||||
upstream_status=resp.status_code,
|
upstream_status=resp.status_code,
|
||||||
upstream_response=raw_content,
|
upstream_response=raw_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 检查响应体中的嵌套错误(HTTP 200 但响应体包含错误)
|
||||||
|
if isinstance(response_json, dict):
|
||||||
|
parser = get_parser_for_format(api_format)
|
||||||
|
if parser.is_error_response(response_json):
|
||||||
|
parsed = parser.parse_response(response_json, 200)
|
||||||
|
logger.warning(
|
||||||
|
f" [{self.request_id}] 非流式检测到嵌套错误: "
|
||||||
|
f"Provider={provider.name}, "
|
||||||
|
f"error_type={parsed.error_type}, "
|
||||||
|
f"embedded_status={parsed.embedded_status_code}, "
|
||||||
|
f"message={parsed.error_message}"
|
||||||
|
)
|
||||||
|
raise EmbeddedErrorException(
|
||||||
|
provider_name=str(provider.name),
|
||||||
|
error_code=parsed.embedded_status_code,
|
||||||
|
error_message=parsed.error_message,
|
||||||
|
error_status=parsed.error_type,
|
||||||
|
)
|
||||||
|
|
||||||
return response_json if isinstance(response_json, dict) else {}
|
return response_json if isinstance(response_json, dict) else {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
不再经过 Protocol 抽象层。
|
不再经过 Protocol 抽象层。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
from typing import Any, Dict, Optional, Tuple, Type
|
from typing import Any, Dict, Optional, Tuple, Type
|
||||||
|
|
||||||
from src.api.handlers.base.response_parser import (
|
from src.api.handlers.base.response_parser import (
|
||||||
@@ -58,6 +59,71 @@ def _check_nested_error(response: Dict[str, Any]) -> Tuple[bool, Optional[Dict[s
|
|||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_embedded_status_code(error_info: Optional[Dict[str, Any]]) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
从错误信息中提取嵌套的状态码
|
||||||
|
|
||||||
|
支持多种格式:
|
||||||
|
1. 直接的 code 字段: {"code": 400}
|
||||||
|
2. status 字段: {"status": 400}
|
||||||
|
3. 从 message 中正则提取: "Request failed with status code 400"
|
||||||
|
4. 从 type 字段映射: "invalid_request_error" -> 400
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error_info: 错误信息字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
提取的状态码,如果无法提取则返回 None
|
||||||
|
"""
|
||||||
|
if not error_info:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 1. 直接的 code 字段(Gemini 等)
|
||||||
|
code = error_info.get("code")
|
||||||
|
if isinstance(code, int) and 100 <= code < 600:
|
||||||
|
return code
|
||||||
|
if isinstance(code, str) and code.isdigit():
|
||||||
|
code_int = int(code)
|
||||||
|
if 100 <= code_int < 600:
|
||||||
|
return code_int
|
||||||
|
|
||||||
|
# 2. status 字段
|
||||||
|
status = error_info.get("status")
|
||||||
|
if isinstance(status, int) and 100 <= status < 600:
|
||||||
|
return status
|
||||||
|
if isinstance(status, str) and status.isdigit():
|
||||||
|
status_int = int(status)
|
||||||
|
if 100 <= status_int < 600:
|
||||||
|
return status_int
|
||||||
|
|
||||||
|
# 3. 从 message 中正则提取 (例如 "Request failed with status code 400")
|
||||||
|
message = error_info.get("message", "")
|
||||||
|
if message:
|
||||||
|
# 匹配 "status code XXX" 或 "status XXX" 或 "HTTP XXX"
|
||||||
|
match = re.search(r"(?:status\s*(?:code\s*)?|HTTP\s*)(\d{3})", message, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
code_int = int(match.group(1))
|
||||||
|
if 100 <= code_int < 600:
|
||||||
|
return code_int
|
||||||
|
|
||||||
|
# 4. 从 type 字段映射常见的错误类型
|
||||||
|
error_type = error_info.get("type", "")
|
||||||
|
type_to_status = {
|
||||||
|
"invalid_request_error": 400,
|
||||||
|
"authentication_error": 401,
|
||||||
|
"permission_error": 403,
|
||||||
|
"not_found_error": 404,
|
||||||
|
"rate_limit_error": 429,
|
||||||
|
"overloaded_error": 503,
|
||||||
|
"api_error": 500,
|
||||||
|
"internal_error": 500,
|
||||||
|
}
|
||||||
|
if error_type and error_type.lower() in type_to_status:
|
||||||
|
return type_to_status[error_type.lower()]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class OpenAIResponseParser(ResponseParser):
|
class OpenAIResponseParser(ResponseParser):
|
||||||
"""OpenAI 格式响应解析器"""
|
"""OpenAI 格式响应解析器"""
|
||||||
|
|
||||||
@@ -141,6 +207,7 @@ class OpenAIResponseParser(ResponseParser):
|
|||||||
result.is_error = True
|
result.is_error = True
|
||||||
result.error_type = error_info.get("type")
|
result.error_type = error_info.get("type")
|
||||||
result.error_message = error_info.get("message")
|
result.error_message = error_info.get("message")
|
||||||
|
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -273,6 +340,7 @@ class ClaudeResponseParser(ResponseParser):
|
|||||||
result.is_error = True
|
result.is_error = True
|
||||||
result.error_type = error_info.get("type")
|
result.error_type = error_info.get("type")
|
||||||
result.error_message = error_info.get("message")
|
result.error_message = error_info.get("message")
|
||||||
|
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -417,6 +485,7 @@ class GeminiResponseParser(ResponseParser):
|
|||||||
result.is_error = True
|
result.is_error = True
|
||||||
result.error_type = error_info.get("status")
|
result.error_type = error_info.get("status")
|
||||||
result.error_message = error_info.get("message")
|
result.error_message = error_info.get("message")
|
||||||
|
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class ParsedResponse:
|
|||||||
is_error: bool = False
|
is_error: bool = False
|
||||||
error_type: Optional[str] = None
|
error_type: Optional[str] = None
|
||||||
error_message: Optional[str] = None
|
error_message: Optional[str] = None
|
||||||
|
# 从响应体解析出的嵌套状态码(当 HTTP 200 但响应体含错误时使用)
|
||||||
|
embedded_status_code: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class ResponseParser(ABC):
|
class ResponseParser(ABC):
|
||||||
|
|||||||
@@ -286,15 +286,12 @@ class StreamProcessor:
|
|||||||
f" [{self.request_id}] 检测到嵌套错误: "
|
f" [{self.request_id}] 检测到嵌套错误: "
|
||||||
f"Provider={provider.name}, "
|
f"Provider={provider.name}, "
|
||||||
f"error_type={parsed.error_type}, "
|
f"error_type={parsed.error_type}, "
|
||||||
|
f"embedded_status={parsed.embedded_status_code}, "
|
||||||
f"message={parsed.error_message}"
|
f"message={parsed.error_message}"
|
||||||
)
|
)
|
||||||
raise EmbeddedErrorException(
|
raise EmbeddedErrorException(
|
||||||
provider_name=str(provider.name),
|
provider_name=str(provider.name),
|
||||||
error_code=(
|
error_code=parsed.embedded_status_code,
|
||||||
int(parsed.error_type)
|
|
||||||
if parsed.error_type and parsed.error_type.isdigit()
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
error_message=parsed.error_message,
|
error_message=parsed.error_message,
|
||||||
error_status=parsed.error_type,
|
error_status=parsed.error_type,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -186,15 +186,12 @@ def check_prefetched_response_error(
|
|||||||
f" [{request_id}] 检测到 JSON 错误响应: "
|
f" [{request_id}] 检测到 JSON 错误响应: "
|
||||||
f"Provider={provider_name}, "
|
f"Provider={provider_name}, "
|
||||||
f"error_type={parsed.error_type}, "
|
f"error_type={parsed.error_type}, "
|
||||||
|
f"embedded_status={parsed.embedded_status_code}, "
|
||||||
f"message={parsed.error_message}"
|
f"message={parsed.error_message}"
|
||||||
)
|
)
|
||||||
raise EmbeddedErrorException(
|
raise EmbeddedErrorException(
|
||||||
provider_name=provider_name,
|
provider_name=provider_name,
|
||||||
error_code=(
|
error_code=parsed.embedded_status_code,
|
||||||
int(parsed.error_type)
|
|
||||||
if parsed.error_type and parsed.error_type.isdigit()
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
error_message=parsed.error_message,
|
error_message=parsed.error_message,
|
||||||
error_status=parsed.error_type,
|
error_status=parsed.error_type,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ class AuthService:
|
|||||||
|
|
||||||
if key_record.is_locked:
|
if key_record.is_locked:
|
||||||
logger.warning("API认证失败 - 密钥已被管理员锁定")
|
logger.warning("API认证失败 - 密钥已被管理员锁定")
|
||||||
raise ForbiddenException("该API密钥已被管理员锁定,请联系管理员")
|
raise ForbiddenException("该密钥已被管理员锁定,请联系管理员")
|
||||||
|
|
||||||
# 检查过期时间
|
# 检查过期时间
|
||||||
if key_record.expires_at:
|
if key_record.expires_at:
|
||||||
|
|||||||
@@ -358,6 +358,8 @@ class FallbackOrchestrator:
|
|||||||
# 需要检查错误消息是否为客户端错误(如 prompt is too long),这类错误不应重试
|
# 需要检查错误消息是否为客户端错误(如 prompt is too long),这类错误不应重试
|
||||||
if isinstance(cause, EmbeddedErrorException):
|
if isinstance(cause, EmbeddedErrorException):
|
||||||
error_message = cause.error_message or ""
|
error_message = cause.error_message or ""
|
||||||
|
# 使用嵌入式状态码(如果有),否则默认 200
|
||||||
|
embedded_status = cause.error_code or 200
|
||||||
if self._error_classifier.is_client_error(error_message):
|
if self._error_classifier.is_client_error(error_message):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f" [{request_id}] 嵌入式客户端错误,停止重试: {error_message[:200]}"
|
f" [{request_id}] 嵌入式客户端错误,停止重试: {error_message[:200]}"
|
||||||
@@ -366,7 +368,7 @@ class FallbackOrchestrator:
|
|||||||
client_error = UpstreamClientException(
|
client_error = UpstreamClientException(
|
||||||
message=error_message or "请求无效",
|
message=error_message or "请求无效",
|
||||||
provider_name=str(provider.name),
|
provider_name=str(provider.name),
|
||||||
status_code=200, # 嵌入式错误的 HTTP 状态码通常是 200
|
status_code=embedded_status,
|
||||||
upstream_error=error_message,
|
upstream_error=error_message,
|
||||||
)
|
)
|
||||||
RequestCandidateService.mark_candidate_failed(
|
RequestCandidateService.mark_candidate_failed(
|
||||||
@@ -374,7 +376,7 @@ class FallbackOrchestrator:
|
|||||||
candidate_id=candidate_record_id,
|
candidate_id=candidate_record_id,
|
||||||
error_type="UpstreamClientException",
|
error_type="UpstreamClientException",
|
||||||
error_message=error_message,
|
error_message=error_message,
|
||||||
status_code=200,
|
status_code=embedded_status,
|
||||||
latency_ms=elapsed_ms,
|
latency_ms=elapsed_ms,
|
||||||
concurrent_requests=captured_key_concurrent,
|
concurrent_requests=captured_key_concurrent,
|
||||||
)
|
)
|
||||||
@@ -396,7 +398,7 @@ class FallbackOrchestrator:
|
|||||||
candidate_id=candidate_record_id,
|
candidate_id=candidate_record_id,
|
||||||
error_type="EmbeddedErrorException",
|
error_type="EmbeddedErrorException",
|
||||||
error_message=error_message,
|
error_message=error_message,
|
||||||
status_code=200,
|
status_code=embedded_status,
|
||||||
latency_ms=elapsed_ms,
|
latency_ms=elapsed_ms,
|
||||||
concurrent_requests=captured_key_concurrent,
|
concurrent_requests=captured_key_concurrent,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user