-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
349 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,76 @@ | ||
import pydantic | ||
from aidial_sdk._errors import pydantic_validation_exception_handler | ||
from aidial_sdk.exceptions import HTTPException as DialException | ||
from fastapi import Request | ||
from fastapi.responses import Response | ||
from aidial_sdk.exceptions import InternalServerError | ||
from fastapi.requests import Request as FastAPIRequest | ||
from fastapi.responses import Response as FastAPIResponse | ||
from openai import APIConnectionError, APIError, APIStatusError, APITimeoutError | ||
|
||
from aidial_adapter_openai.utils.adapter_exception import ( | ||
AdapterException, | ||
ResponseWrapper, | ||
parse_adapter_exception, | ||
) | ||
|
||
def openai_exception_handler(request: Request, e: DialException): | ||
if isinstance(e, APIStatusError): | ||
r = e.response | ||
headers = r.headers | ||
|
||
# Avoid encoding the error message when the original response was encoded. | ||
if "Content-Encoding" in headers: | ||
del headers["Content-Encoding"] | ||
def to_adapter_exception(exc: Exception) -> AdapterException: | ||
|
||
return Response( | ||
content=r.content, | ||
if isinstance(exc, (DialException, ResponseWrapper)): | ||
return exc | ||
|
||
if isinstance(exc, APIStatusError): | ||
# Non-streaming errors reported by `openai` library via this exception | ||
r = exc.response | ||
httpx_headers = r.headers | ||
|
||
# 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 httpx_headers: | ||
del httpx_headers["Content-Encoding"] | ||
|
||
return parse_adapter_exception( | ||
status_code=r.status_code, | ||
headers=headers, | ||
headers=dict(httpx_headers.items()), | ||
content=r.text, | ||
) | ||
|
||
if isinstance(e, APITimeoutError): | ||
raise DialException( | ||
if isinstance(exc, APITimeoutError): | ||
return DialException( | ||
status_code=504, | ||
type="timeout", | ||
message="Request timed out", | ||
display_message="Request timed out. Please try again later.", | ||
) | ||
|
||
if isinstance(e, APIConnectionError): | ||
raise DialException( | ||
if isinstance(exc, APIConnectionError): | ||
return DialException( | ||
status_code=502, | ||
type="connection", | ||
message="Error communicating with OpenAI", | ||
display_message="OpenAI server is not responsive. Please try again later.", | ||
) | ||
|
||
if isinstance(e, APIError): | ||
raise DialException( | ||
status_code=getattr(e, "status_code", None) or 500, | ||
message=e.message, | ||
type=e.type, | ||
code=e.code, | ||
param=e.param, | ||
display_message=None, | ||
) | ||
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_adapter_exception( | ||
status_code=status_code, | ||
headers={}, | ||
content={"error": exc.body or {}}, | ||
) | ||
|
||
def pydantic_exception_handler(request: Request, exc: pydantic.ValidationError): | ||
return pydantic_validation_exception_handler(request, exc) | ||
return InternalServerError(str(exc)) | ||
|
||
|
||
def dial_exception_handler(request: Request, exc: DialException): | ||
return exc.to_fastapi_response() | ||
def adapter_exception_handler( | ||
request: FastAPIRequest, exc: Exception | ||
) -> FastAPIResponse: | ||
return to_adapter_exception(exc).to_fastapi_response() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import json | ||
from typing import Any, Dict | ||
|
||
from aidial_sdk.exceptions import HTTPException as DialException | ||
from fastapi.responses import Response as FastAPIResponse | ||
|
||
|
||
class ResponseWrapper(Exception): | ||
content: Any | ||
status_code: int | ||
headers: Dict[str, str] | None | ||
|
||
def __init__( | ||
self, | ||
*, | ||
content: Any, | ||
status_code: int, | ||
headers: Dict[str, str] | None, | ||
) -> None: | ||
super().__init__(str(content)) | ||
self.content = content | ||
self.status_code = status_code | ||
self.headers = headers | ||
|
||
def __repr__(self): | ||
# headers field is omitted deliberately | ||
# since it may contain sensitive information | ||
return "%s(content=%r, status_code=%r)" % ( | ||
self.__class__.__name__, | ||
self.content, | ||
self.status_code, | ||
) | ||
|
||
def to_fastapi_response(self) -> FastAPIResponse: | ||
return FastAPIResponse( | ||
status_code=self.status_code, | ||
content=self.content, | ||
headers=self.headers, | ||
) | ||
|
||
def json_error(self) -> dict: | ||
return { | ||
"error": { | ||
"message": str(self.content), | ||
"code": int(self.status_code), | ||
} | ||
} | ||
|
||
|
||
AdapterException = ResponseWrapper | DialException | ||
|
||
|
||
def _parse_dial_exception( | ||
*, status_code: int, headers: Dict[str, str], content: Any | ||
) -> DialException | None: | ||
if isinstance(content, str): | ||
try: | ||
obj = json.loads(content) | ||
except Exception: | ||
return None | ||
else: | ||
obj = content | ||
|
||
if ( | ||
isinstance(obj, dict) | ||
and (error := obj.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") | ||
|
||
return DialException( | ||
status_code=status_code, | ||
message=message, | ||
type=type, | ||
param=param, | ||
code=code, | ||
display_message=display_message, | ||
headers=headers, | ||
) | ||
|
||
return None | ||
|
||
|
||
def parse_adapter_exception( | ||
*, status_code: int, headers: Dict[str, str], content: Any | ||
) -> AdapterException: | ||
return _parse_dial_exception( | ||
status_code=status_code, headers=headers, content=content | ||
) or ResponseWrapper( | ||
status_code=status_code, headers=headers, content=content | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.