mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00: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 json
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, AsyncGenerator, Callable, Dict, Optional
|
||||
|
||||
@@ -803,6 +804,26 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
upstream_status=resp.status_code,
|
||||
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 {}
|
||||
|
||||
try:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
不再经过 Protocol 抽象层。
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, Dict, Optional, Tuple, Type
|
||||
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""OpenAI 格式响应解析器"""
|
||||
|
||||
@@ -141,6 +207,7 @@ class OpenAIResponseParser(ResponseParser):
|
||||
result.is_error = True
|
||||
result.error_type = error_info.get("type")
|
||||
result.error_message = error_info.get("message")
|
||||
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||
|
||||
return result
|
||||
|
||||
@@ -273,6 +340,7 @@ class ClaudeResponseParser(ResponseParser):
|
||||
result.is_error = True
|
||||
result.error_type = error_info.get("type")
|
||||
result.error_message = error_info.get("message")
|
||||
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||
|
||||
return result
|
||||
|
||||
@@ -417,6 +485,7 @@ class GeminiResponseParser(ResponseParser):
|
||||
result.is_error = True
|
||||
result.error_type = error_info.get("status")
|
||||
result.error_message = error_info.get("message")
|
||||
result.embedded_status_code = _extract_embedded_status_code(error_info)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -87,6 +87,8 @@ class ParsedResponse:
|
||||
is_error: bool = False
|
||||
error_type: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
# 从响应体解析出的嵌套状态码(当 HTTP 200 但响应体含错误时使用)
|
||||
embedded_status_code: Optional[int] = None
|
||||
|
||||
|
||||
class ResponseParser(ABC):
|
||||
|
||||
@@ -286,15 +286,12 @@ class StreamProcessor:
|
||||
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=(
|
||||
int(parsed.error_type)
|
||||
if parsed.error_type and parsed.error_type.isdigit()
|
||||
else None
|
||||
),
|
||||
error_code=parsed.embedded_status_code,
|
||||
error_message=parsed.error_message,
|
||||
error_status=parsed.error_type,
|
||||
)
|
||||
|
||||
@@ -186,15 +186,12 @@ def check_prefetched_response_error(
|
||||
f" [{request_id}] 检测到 JSON 错误响应: "
|
||||
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=provider_name,
|
||||
error_code=(
|
||||
int(parsed.error_type)
|
||||
if parsed.error_type and parsed.error_type.isdigit()
|
||||
else None
|
||||
),
|
||||
error_code=parsed.embedded_status_code,
|
||||
error_message=parsed.error_message,
|
||||
error_status=parsed.error_type,
|
||||
)
|
||||
|
||||
@@ -388,7 +388,7 @@ class AuthService:
|
||||
|
||||
if key_record.is_locked:
|
||||
logger.warning("API认证失败 - 密钥已被管理员锁定")
|
||||
raise ForbiddenException("该API密钥已被管理员锁定,请联系管理员")
|
||||
raise ForbiddenException("该密钥已被管理员锁定,请联系管理员")
|
||||
|
||||
# 检查过期时间
|
||||
if key_record.expires_at:
|
||||
|
||||
@@ -358,6 +358,8 @@ class FallbackOrchestrator:
|
||||
# 需要检查错误消息是否为客户端错误(如 prompt is too long),这类错误不应重试
|
||||
if isinstance(cause, EmbeddedErrorException):
|
||||
error_message = cause.error_message or ""
|
||||
# 使用嵌入式状态码(如果有),否则默认 200
|
||||
embedded_status = cause.error_code or 200
|
||||
if self._error_classifier.is_client_error(error_message):
|
||||
logger.warning(
|
||||
f" [{request_id}] 嵌入式客户端错误,停止重试: {error_message[:200]}"
|
||||
@@ -366,7 +368,7 @@ class FallbackOrchestrator:
|
||||
client_error = UpstreamClientException(
|
||||
message=error_message or "请求无效",
|
||||
provider_name=str(provider.name),
|
||||
status_code=200, # 嵌入式错误的 HTTP 状态码通常是 200
|
||||
status_code=embedded_status,
|
||||
upstream_error=error_message,
|
||||
)
|
||||
RequestCandidateService.mark_candidate_failed(
|
||||
@@ -374,7 +376,7 @@ class FallbackOrchestrator:
|
||||
candidate_id=candidate_record_id,
|
||||
error_type="UpstreamClientException",
|
||||
error_message=error_message,
|
||||
status_code=200,
|
||||
status_code=embedded_status,
|
||||
latency_ms=elapsed_ms,
|
||||
concurrent_requests=captured_key_concurrent,
|
||||
)
|
||||
@@ -396,7 +398,7 @@ class FallbackOrchestrator:
|
||||
candidate_id=candidate_record_id,
|
||||
error_type="EmbeddedErrorException",
|
||||
error_message=error_message,
|
||||
status_code=200,
|
||||
status_code=embedded_status,
|
||||
latency_ms=elapsed_ms,
|
||||
concurrent_requests=captured_key_concurrent,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user