157 lines
4.4 KiB
Python
157 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
|
|
class UnexpectedModelBehavior(RuntimeError):
|
|
"""Raised when the model behaves in a way the run cannot recover from,
|
|
e.g. a run with an output_type ends without the expected output after
|
|
exhausting its retries."""
|
|
|
|
|
|
class AssignmentTimeoutError(Exception):
|
|
"""Raised when accepting a job but not receiving an assignment within the specified timeout.
|
|
The server may have chosen another worker to handle this job."""
|
|
|
|
pass
|
|
|
|
|
|
# errors used by our plugins
|
|
|
|
|
|
class APIError(Exception):
|
|
"""Raised when an API request failed.
|
|
This is used on our TTS/STT/LLM plugins."""
|
|
|
|
message: str
|
|
"""
|
|
The error message returned by the API.
|
|
"""
|
|
|
|
body: object | None
|
|
"""The API response body, if available.
|
|
|
|
|
|
If the API returned a valid json, the body will contains
|
|
the decodede result.
|
|
"""
|
|
|
|
retryable: bool = False
|
|
"""Whether the error can be retried."""
|
|
|
|
def __init__(self, message: str, *, body: object | None = None, retryable: bool = True) -> None:
|
|
super().__init__(message)
|
|
|
|
self.message = message
|
|
self.body = body
|
|
self.retryable = retryable
|
|
|
|
def __str__(self) -> str:
|
|
return self.message
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self.__class__.__name__}({self.message!r}, body={self.body!r}, retryable={self.retryable!r})"
|
|
|
|
|
|
class APIStatusError(APIError):
|
|
"""Raised when an API response has a status code of 4xx or 5xx."""
|
|
|
|
status_code: int
|
|
"""The status code of the API response."""
|
|
|
|
request_id: str | None
|
|
"""The request ID of the API response, if available."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
*,
|
|
status_code: int = -1,
|
|
request_id: str | None = None,
|
|
body: object | None = None,
|
|
retryable: bool | None = None,
|
|
) -> None:
|
|
if retryable is None:
|
|
retryable = True
|
|
|
|
# 4xx client errors are not retryable, except for transient codes:
|
|
# 408 (Request Timeout), 429 (Too Many Requests), 499 (Client Closed Request / gRPC CANCELLED).
|
|
# This overrides any caller-provided retryable=True, since a client
|
|
# error (bad URL, auth, bad request) will keep failing on retry.
|
|
if 400 <= status_code < 500 and status_code not in (408, 429, 499):
|
|
retryable = False
|
|
|
|
super().__init__(message, body=body, retryable=retryable)
|
|
|
|
self.status_code = status_code
|
|
self.request_id = request_id
|
|
|
|
def __str__(self) -> str:
|
|
parts = [
|
|
f"message={self.message!r}",
|
|
f"status_code={self.status_code}",
|
|
f"retryable={self.retryable}",
|
|
]
|
|
if self.request_id:
|
|
parts.append(f"request_id={self.request_id}")
|
|
if self.body:
|
|
parts.append(f"body={self.body}")
|
|
return ", ".join(parts)
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
f"{self.__class__.__name__}({self.message!r}, "
|
|
f"status_code={self.status_code!r}, "
|
|
f"request_id={self.request_id!r}, "
|
|
f"body={self.body!r}, "
|
|
f"retryable={self.retryable!r})"
|
|
)
|
|
|
|
|
|
class APIConnectionError(APIError):
|
|
"""Raised when an API request failed due to a connection error."""
|
|
|
|
def __init__(self, message: str = "Connection error.", *, retryable: bool = True) -> None:
|
|
super().__init__(message, body=None, retryable=retryable)
|
|
|
|
|
|
class APITimeoutError(APIConnectionError):
|
|
"""Raised when an API request timed out."""
|
|
|
|
def __init__(self, message: str = "Request timed out.", *, retryable: bool = True) -> None:
|
|
super().__init__(message, retryable=retryable)
|
|
|
|
|
|
class CLIError(Exception):
|
|
pass
|
|
|
|
|
|
def create_api_error_from_http(
|
|
message: str = "",
|
|
*,
|
|
status: int,
|
|
request_id: str | None = None,
|
|
body: object | None = None,
|
|
) -> APIStatusError:
|
|
"""Create an APIStatusError from an HTTP status code.
|
|
|
|
When the message carries extra detail beyond the standard reason phrase,
|
|
both the message and the reason are shown. Otherwise just the reason.
|
|
"""
|
|
from http import HTTPStatus
|
|
|
|
try:
|
|
reason = HTTPStatus(status).phrase
|
|
except ValueError:
|
|
reason = f"HTTP {status}"
|
|
|
|
if message and message != reason:
|
|
display = f"{message} ({status} {reason})"
|
|
else:
|
|
display = f"{reason} ({status})"
|
|
|
|
return APIStatusError(
|
|
message=display,
|
|
status_code=status,
|
|
request_id=request_id,
|
|
body=body,
|
|
)
|