CHATSAM / services /protocol /error_response.py
xbang
fix(api): expose compatible error messages
26bac1d
Raw
History Blame Contribute Delete
3.86 kB
from __future__ import annotations
from typing import Any
from fastapi.responses import JSONResponse
def _message_from_value(value: object) -> str:
if isinstance(value, str):
return value
if not isinstance(value, dict):
return ""
message = value.get("message")
if isinstance(message, str) and message:
return message
return _message_from_value(value.get("error"))
def error_message_from_detail(detail: object) -> str:
if isinstance(detail, list):
messages = []
for item in detail:
if not isinstance(item, dict):
continue
location = ".".join(str(part) for part in item.get("loc", []) if part != "body")
message = str(item.get("msg") or "").strip()
if location and message:
messages.append(f"{location}: {message}")
elif message:
messages.append(message)
return "; ".join(messages)
if isinstance(detail, dict):
message = _message_from_value(detail.get("error")) or _message_from_value(detail)
if message:
return message
return str(detail or "").strip()
def _default_error_type(status_code: int) -> str:
if status_code == 401:
return "authentication_error"
if status_code == 403:
return "permission_error"
if status_code == 429:
return "rate_limit_error"
if 400 <= status_code < 500:
return "invalid_request_error"
return "server_error"
def _default_error_code(status_code: int) -> str:
if status_code == 401:
return "invalid_api_key"
if status_code == 403:
return "permission_denied"
if status_code == 429:
return "rate_limit_exceeded"
if 400 <= status_code < 500:
return "bad_request"
return "upstream_error"
def openai_error_payload(
detail: object,
status_code: int,
*,
error_type: str | None = None,
code: object | None = None,
param: object | None = None,
) -> dict[str, Any]:
error_detail = detail.get("error") if isinstance(detail, dict) else None
if isinstance(error_detail, dict):
return {
"error": {
"message": error_message_from_detail(error_detail) or "request failed",
"type": str(error_detail.get("type") or error_type or _default_error_type(status_code)),
"param": error_detail.get("param", param),
"code": error_detail.get("code", code if code is not None else _default_error_code(status_code)),
}
}
return {
"error": {
"message": error_message_from_detail(detail) or "request failed",
"type": error_type or _default_error_type(status_code),
"param": param,
"code": code if code is not None else _default_error_code(status_code),
}
}
def openai_error_response(
detail: object,
status_code: int,
*,
headers: dict[str, str] | None = None,
error_type: str | None = None,
code: object | None = None,
param: object | None = None,
) -> JSONResponse:
return JSONResponse(
status_code=status_code,
content=openai_error_payload(detail, status_code, error_type=error_type, code=code, param=param),
headers=headers,
)
def anthropic_error_response(
detail: object,
status_code: int,
*,
headers: dict[str, str] | None = None,
) -> JSONResponse:
error_type = "api_error" if status_code >= 500 else _default_error_type(status_code)
return JSONResponse(
status_code=status_code,
content={
"type": "error",
"error": {
"type": error_type,
"message": error_message_from_detail(detail) or "request failed",
},
},
headers=headers,
)