aidial_interceptors_sdk/utils/_exceptions.py (87 lines of code) (raw):
import functools
import logging
from typing import Dict
from aidial_sdk.exceptions import HTTPException as DialException
from openai import APIConnectionError, APIError, APIStatusError, APITimeoutError
_log = logging.getLogger(__name__)
def _parse_dial_exception(
status_code: int,
content: dict | str,
headers: Dict[str, str] | None = None,
):
if (
isinstance(content, dict)
and (error := content.get("error"))
and isinstance(error, dict)
):
message = error.get("message") or "Unknown error"
code = error.get("code")
type = error.get("type")
param = error.get("param")
display_message = error.get("display_message")
else:
message = str(content)
code = type = param = display_message = None
return DialException(
status_code=status_code,
message=message,
type=type,
param=param,
code=code,
display_message=display_message,
headers=headers,
)
def to_dial_exception(exc: Exception) -> DialException:
if isinstance(exc, APIStatusError):
# Non-streaming errors reported by `openai` library via this exception
r = exc.response
headers = r.headers
# The original content length may have changed
# due to the response modification in the adapter.
if "Content-Length" in headers:
del headers["Content-Length"]
# httpx library (used by openai) automatically sets
# "Accept-Encoding:gzip,deflate" header in requests to the upstream.
# Therefore, we may receive from the upstream gzip-encoded
# response along with "Content-Encoding:gzip" header.
# We either need to encode the response, or
# remove the "Content-Encoding" header.
if "Content-Encoding" in headers:
del headers["Content-Encoding"]
plain_headers = {k.decode(): v.decode() for k, v in headers.raw}
try:
content = r.json()
except Exception:
content = r.text
return _parse_dial_exception(
status_code=r.status_code,
headers=plain_headers,
content=content,
)
if isinstance(exc, APIError):
# Streaming errors reported by `openai` library via this exception
status_code: int = 500
if exc.code:
try:
status_code = int(exc.code)
except Exception:
pass
return _parse_dial_exception(
status_code=status_code,
headers={},
content={"error": exc.body or {}},
)
if isinstance(exc, APITimeoutError):
return DialException("Request timed out", 504, "timeout")
if isinstance(exc, APIConnectionError):
return DialException(
"Error communicating with OpenAI", 502, "connection"
)
if isinstance(exc, DialException):
return exc
return DialException(
status_code=500,
type="internal_server_error",
message=str(exc),
)
def dial_exception_decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except Exception as e:
_log.exception(
f"caught exception: {type(e).__module__}.{type(e).__name__}"
)
raise to_dial_exception(e) from e
return wrapper