mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-10 11:42:27 +08:00
feat: 所有 Provider 失败时透传上游错误信息
- FallbackOrchestrator 在所有候选组合失败后保留最后的错误信息 - 从 httpx.HTTPStatusError 提取上游状态码和响应内容 - ProviderNotAvailableException 携带上游错误信息 - ErrorResponse 在返回错误时透传上游状态码和响应
This commit is contained in:
@@ -547,11 +547,19 @@ class ErrorResponse:
|
|||||||
- 所有错误都记录到日志,通过错误 ID 关联
|
- 所有错误都记录到日志,通过错误 ID 关联
|
||||||
"""
|
"""
|
||||||
if isinstance(e, ProxyException):
|
if isinstance(e, ProxyException):
|
||||||
|
details = e.details.copy() if e.details else {}
|
||||||
|
status_code = e.status_code
|
||||||
|
message = e.message
|
||||||
|
# 如果是 ProviderNotAvailableException 且有上游错误,直接透传上游信息
|
||||||
|
if isinstance(e, ProviderNotAvailableException) and e.upstream_response:
|
||||||
|
if e.upstream_status:
|
||||||
|
status_code = e.upstream_status
|
||||||
|
message = e.upstream_response
|
||||||
return ErrorResponse.create(
|
return ErrorResponse.create(
|
||||||
error_type=e.error_type,
|
error_type=e.error_type,
|
||||||
message=e.message,
|
message=message,
|
||||||
status_code=e.status_code,
|
status_code=status_code,
|
||||||
details=e.details,
|
details=details if details else None,
|
||||||
)
|
)
|
||||||
elif isinstance(e, HTTPException):
|
elif isinstance(e, HTTPException):
|
||||||
return ErrorResponse.create(
|
return ErrorResponse.create(
|
||||||
|
|||||||
@@ -543,7 +543,9 @@ class FallbackOrchestrator:
|
|||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
# 所有组合都已尝试完毕,全部失败
|
# 所有组合都已尝试完毕,全部失败
|
||||||
self._raise_all_failed_exception(request_id, max_attempts, last_candidate, model_name, api_format_enum)
|
self._raise_all_failed_exception(
|
||||||
|
request_id, max_attempts, last_candidate, model_name, api_format_enum, last_error
|
||||||
|
)
|
||||||
|
|
||||||
async def _try_candidate_with_retries(
|
async def _try_candidate_with_retries(
|
||||||
self,
|
self,
|
||||||
@@ -565,6 +567,7 @@ class FallbackOrchestrator:
|
|||||||
provider = candidate.provider
|
provider = candidate.provider
|
||||||
endpoint = candidate.endpoint
|
endpoint = candidate.endpoint
|
||||||
max_retries_for_candidate = int(endpoint.max_retries) if candidate.is_cached else 1
|
max_retries_for_candidate = int(endpoint.max_retries) if candidate.is_cached else 1
|
||||||
|
last_error: Optional[Exception] = None
|
||||||
|
|
||||||
for retry_index in range(max_retries_for_candidate):
|
for retry_index in range(max_retries_for_candidate):
|
||||||
attempt_counter += 1
|
attempt_counter += 1
|
||||||
@@ -599,6 +602,7 @@ class FallbackOrchestrator:
|
|||||||
return {"success": True, "response": response}
|
return {"success": True, "response": response}
|
||||||
|
|
||||||
except ExecutionError as exec_err:
|
except ExecutionError as exec_err:
|
||||||
|
last_error = exec_err.cause
|
||||||
action = await self._handle_candidate_error(
|
action = await self._handle_candidate_error(
|
||||||
exec_err=exec_err,
|
exec_err=exec_err,
|
||||||
candidate=candidate,
|
candidate=candidate,
|
||||||
@@ -630,6 +634,7 @@ class FallbackOrchestrator:
|
|||||||
"success": False,
|
"success": False,
|
||||||
"attempt_counter": attempt_counter,
|
"attempt_counter": attempt_counter,
|
||||||
"max_attempts": max_attempts,
|
"max_attempts": max_attempts,
|
||||||
|
"error": last_error,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _attach_metadata_to_error(
|
def _attach_metadata_to_error(
|
||||||
@@ -678,6 +683,7 @@ class FallbackOrchestrator:
|
|||||||
last_candidate: Optional[ProviderCandidate],
|
last_candidate: Optional[ProviderCandidate],
|
||||||
model_name: str,
|
model_name: str,
|
||||||
api_format_enum: APIFormat,
|
api_format_enum: APIFormat,
|
||||||
|
last_error: Optional[Exception] = None,
|
||||||
) -> NoReturn:
|
) -> NoReturn:
|
||||||
"""所有组合都失败时抛出异常"""
|
"""所有组合都失败时抛出异常"""
|
||||||
logger.error(f" [{request_id}] 所有 {max_attempts} 个组合均失败")
|
logger.error(f" [{request_id}] 所有 {max_attempts} 个组合均失败")
|
||||||
@@ -693,9 +699,28 @@ class FallbackOrchestrator:
|
|||||||
"api_format": api_format_enum.value,
|
"api_format": api_format_enum.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 提取上游错误响应
|
||||||
|
upstream_status: Optional[int] = None
|
||||||
|
upstream_response: Optional[str] = None
|
||||||
|
if last_error:
|
||||||
|
# 从 httpx.HTTPStatusError 提取
|
||||||
|
if isinstance(last_error, httpx.HTTPStatusError):
|
||||||
|
upstream_status = last_error.response.status_code
|
||||||
|
try:
|
||||||
|
upstream_response = last_error.response.text
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# 从异常属性提取
|
||||||
|
elif hasattr(last_error, "upstream_status"):
|
||||||
|
upstream_status = getattr(last_error, "upstream_status", None)
|
||||||
|
if hasattr(last_error, "upstream_response"):
|
||||||
|
upstream_response = getattr(last_error, "upstream_response", None)
|
||||||
|
|
||||||
raise ProviderNotAvailableException(
|
raise ProviderNotAvailableException(
|
||||||
f"所有Provider均不可用,已尝试{max_attempts}个组合",
|
f"所有Provider均不可用,已尝试{max_attempts}个组合",
|
||||||
request_metadata=request_metadata,
|
request_metadata=request_metadata,
|
||||||
|
upstream_status=upstream_status,
|
||||||
|
upstream_response=upstream_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def execute_with_fallback(
|
async def execute_with_fallback(
|
||||||
|
|||||||
Reference in New Issue
Block a user