diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py index b36c8a038fc..3cfd8e047de 100644 --- a/litellm/proxy/auth/auth_exception_handler.py +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -24,6 +24,7 @@ from litellm.proxy.auth.auth_utils import ( is_invalid_virtual_key_error, mark_invalid_virtual_key_error, ) +from litellm.proxy.common_utils.openai_error_payload import openai_error_param from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler from litellm.types.services import ServiceTypes @@ -54,7 +55,7 @@ def _as_proxy_exception(e: Exception) -> ProxyException: return ProxyException( message=getattr(e, "detail", f"Authentication Error({e})"), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_401_UNAUTHORIZED), ) if isinstance(e, ProxyException): @@ -63,13 +64,13 @@ def _as_proxy_exception(e: Exception) -> ProxyException: return ProxyException( message=PrismaDBExceptionHandler.database_unavailable_message(e), type=ProxyErrorTypes.no_db_connection, - param="None", + param=None, code=status.HTTP_503_SERVICE_UNAVAILABLE, ) return ProxyException( message="Authentication Error, " + str(e), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=status.HTTP_401_UNAUTHORIZED, ) diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 0795cee7409..0eaa816b223 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -142,7 +142,7 @@ def jwks_unavailable_exception(error: JWKSUnreachableError) -> ProxyException: f"unreachable, so the JWT signature could not be verified. Please retry shortly. Error: {error}" ), type=ProxyErrorTypes.auth_provider_unavailable, - param="None", + param=None, code=status.HTTP_503_SERVICE_UNAVAILABLE, ) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index 8b57bdca2fe..06e6f281048 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -40,6 +40,7 @@ from litellm.proxy.auth.auth_utils import ( _BANNED_REQUEST_BODY_PARAMS, # pyright: ignore[reportPrivateUsage] # one canonical list, shared with the request-body check ) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.proxy.common_utils.openai_error_payload import openai_error_param from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler from litellm.proxy.db.proxy_worker_heartbeat import count_live_proxy_workers from litellm.proxy.health_check import ( @@ -529,7 +530,7 @@ async def health_services_endpoint( raise ProxyException( message=getattr(e, "detail", f"Authentication Error({e})"), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR), ) elif isinstance(e, ProxyException): @@ -537,7 +538,7 @@ async def health_services_endpoint( raise ProxyException( message="Authentication Error, " + str(e), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 27132c90e05..c4942e5df1b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -112,6 +112,11 @@ from litellm.proxy.common_utils.callback_utils import ( process_callback, strip_callback_config, ) +from litellm.proxy.common_utils.openai_error_payload import ( + error_status_code, + openai_error_param, + openai_error_type, +) from litellm.proxy.common_utils.realtime_utils import _realtime_request_body from litellm.router_utils.add_retry_fallback_headers import ( get_fallback_errors_from_headers, @@ -8160,9 +8165,9 @@ async def async_assistants_data_generator(response, user_api_key_dict: UserAPIKe proxy_exception: Final = ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", 500), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), + code=error_status_code(e, 500), ) error_returned: Final = json.dumps({"error": proxy_exception.to_dict()}) yield f"data: {error_returned}\n\n" @@ -8867,9 +8872,9 @@ async def async_data_generator( proxy_exception: Final = ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", 500), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), + code=error_status_code(e, 500), ) error_returned: Final = json.dumps({"error": proxy_exception.to_dict()}) stream_completed = True @@ -10819,10 +10824,10 @@ async def completion( error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), openai_code=getattr(e, "code", None), - code=getattr(e, "status_code", 500), + code=error_status_code(e, 500), ) @@ -11052,17 +11057,17 @@ async def moderations( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", 500), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), + code=error_status_code(e, 500), ) @@ -11199,10 +11204,10 @@ async def audio_speech( raise e raise ProxyException( message=getattr(e, "message", f"{e}"), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), openai_code=getattr(e, "code", None), - code=getattr(e, "status_code", 500), + code=error_status_code(e, 500), ) @@ -11347,18 +11352,18 @@ async def audio_transcriptions( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), openai_code=getattr(e, "code", None), - code=getattr(e, "status_code", 500), + code=error_status_code(e, 500), ) @@ -11645,18 +11650,18 @@ async def get_assistants( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), openai_code=getattr(e, "code", None), - code=getattr(e, "status_code", 500), + code=error_status_code(e, 500), ) @@ -11736,16 +11741,16 @@ async def create_assistant( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -11825,16 +11830,16 @@ async def delete_assistant( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -11914,16 +11919,16 @@ async def create_threads( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -12001,16 +12006,16 @@ async def get_thread( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -12092,16 +12097,16 @@ async def add_messages( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -12179,16 +12184,16 @@ async def get_messages( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -12301,16 +12306,16 @@ async def run_thread( if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "message", str(e.detail)), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + type=openai_error_type(e, error_status_code(e, status.HTTP_400_BAD_REQUEST)), + param=openai_error_param(e), + code=error_status_code(e, status.HTTP_400_BAD_REQUEST), ) else: error_msg: Final = f"{e}" raise ProxyException( message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), + type=openai_error_type(e, error_status_code(e, 500)), + param=openai_error_param(e), code=getattr(e, "code", getattr(e, "status_code", 500)), ) @@ -13980,7 +13985,7 @@ async def model_streaming_metrics( raise ProxyException( message=CommonProxyErrors.db_not_connected_error.value, type="internal_error", - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -14114,7 +14119,7 @@ async def model_metrics( raise ProxyException( message="Prisma Client is not initialized", type="internal_error", - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) startTime = startTime or datetime.now() - timedelta(days=DAYS_IN_A_MONTH) @@ -14229,7 +14234,7 @@ async def model_metrics_slow_responses( raise ProxyException( message="Prisma Client is not initialized", type="internal_error", - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) if api_key is None or api_key == "undefined": @@ -14318,7 +14323,7 @@ async def model_metrics_exceptions( raise ProxyException( message="Prisma Client is not initialized", type="internal_error", - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -15247,7 +15252,7 @@ async def async_queue_request( raise ProxyException( message=getattr(e, "detail", f"Authentication Error({e})"), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), ) elif isinstance(e, ProxyException): @@ -15255,7 +15260,7 @@ async def async_queue_request( raise ProxyException( message="Authentication Error, " + str(e), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=status.HTTP_400_BAD_REQUEST, ) @@ -15419,7 +15424,7 @@ async def login_v2(request: Request): raise ProxyException( message=getattr(e, "detail", str(e)), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR), ) else: @@ -15427,7 +15432,7 @@ async def login_v2(request: Request): raise ProxyException( message=error_msg, type=ProxyErrorTypes.auth_error, - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -15494,7 +15499,7 @@ async def login_v3(request: Request): raise ProxyException( message=getattr(e, "detail", str(e)), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR), ) else: @@ -15502,7 +15507,7 @@ async def login_v3(request: Request): raise ProxyException( message=error_msg, type=ProxyErrorTypes.auth_error, - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -15566,7 +15571,7 @@ async def login_v3_exchange(request: Request): raise ProxyException( message=str(e), type=ProxyErrorTypes.auth_error, - param="None", + param=None, code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -16465,7 +16470,7 @@ async def update_config( raise ProxyException( message=getattr(e, "detail", f"Authentication Error({e})"), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), ) elif isinstance(e, ProxyException): @@ -16473,7 +16478,7 @@ async def update_config( raise ProxyException( message="Authentication Error, " + str(e), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=status.HTTP_400_BAD_REQUEST, ) @@ -17464,7 +17469,7 @@ async def get_config( raise ProxyException( message=getattr(e, "detail", f"Authentication Error({e})"), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), ) elif isinstance(e, ProxyException): @@ -17472,7 +17477,7 @@ async def get_config( raise ProxyException( message="Authentication Error, " + str(e), type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), + param=openai_error_param(e), code=status.HTTP_400_BAD_REQUEST, ) diff --git a/tests/test_litellm/proxy/auth/test_auth_exception_handler.py b/tests/test_litellm/proxy/auth/test_auth_exception_handler.py index 21e0b83791f..b82072086e6 100644 --- a/tests/test_litellm/proxy/auth/test_auth_exception_handler.py +++ b/tests/test_litellm/proxy/auth/test_auth_exception_handler.py @@ -30,7 +30,10 @@ from litellm._logging import verbose_proxy_logger from litellm.constants import INVALID_VIRTUAL_KEY_ERROR_MARKER from litellm.exceptions import BudgetExceededError from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth -from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler +from litellm.proxy.auth.auth_exception_handler import ( + UserAPIKeyAuthExceptionHandler, + _as_proxy_exception, +) class _EngineHttp500: @@ -871,3 +874,32 @@ async def test_handle_authentication_error_traceback_only_for_unexpected_errors( assert records[0].levelname == expect_level expected_logger_name = "LiteLLM Proxy.stdout" if expect_level == "WARNING" else "LiteLLM Proxy" assert records[0].name == expected_logger_name + + +@pytest.mark.parametrize( + "auth_failure,expected_type", + [ + pytest.param( + HTTPException(status_code=401, detail="Authentication Error, invalid key"), + ProxyErrorTypes.auth_error.value, + id="http_exception", + ), + pytest.param( + httpx.ConnectError("All connection attempts failed"), + ProxyErrorTypes.no_db_connection.value, + id="database_unavailable", + ), + pytest.param( + Exception("Invalid proxy server token passed"), + ProxyErrorTypes.auth_error.value, + id="bare_exception", + ), + ], +) +def test_auth_failure_error_body_is_openai_shaped(auth_failure, expected_type): + """Every auth rejection must serialize as an OpenAI error object: a real + `type` string and a JSON null `param`, never the literal string "None".""" + body = json.loads(json.dumps({"error": _as_proxy_exception(auth_failure).to_dict()})) + + assert body["error"]["type"] == expected_type + assert body["error"]["param"] is None diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 99a0a4c0a8b..abfa16a74df 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -1,4 +1,5 @@ import asyncio +import json import re import time from collections.abc import Mapping, Sequence @@ -30,6 +31,7 @@ from litellm.proxy.auth.handle_jwt import ( JWTAuthManager, JWTHandler, NoMatchingJWTPublicKeyError, + jwks_unavailable_exception, ) @@ -6734,3 +6736,13 @@ async def test_sync_user_role_and_teams_singular_claim_only_recognized_under_fla } assert mock_patch.call_args.kwargs["teams_ids_to_add_user_to"] == [] assert user.teams == [] + + +def test_jwks_unavailable_exception_is_openai_shaped(): + """An unreachable JWKS endpoint must answer with a real `type` string and a + JSON null `param`, never the literal string "None".""" + body = json.loads(json.dumps({"error": jwks_unavailable_exception(JWKSUnreachableError("jwks down")).to_dict()})) + + assert body["error"]["type"] == ProxyErrorTypes.auth_provider_unavailable.value + assert body["error"]["param"] is None + assert body["error"]["code"] == "503" diff --git a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py index e3f71692c78..3d14294eff4 100644 --- a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py +++ b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py @@ -1089,6 +1089,18 @@ async def test_health_services_endpoint_rejects_unknown_service(): await health_services_endpoint(service="totally_unknown_service_xyz") +@pytest.mark.asyncio +async def test_health_services_endpoint_rejection_is_openai_shaped(): + """A caller mistake here must serialize as an OpenAI error object: a real + `type` string and a JSON null `param`, never the literal string "None".""" + with pytest.raises(ProxyException) as exc_info: + await health_services_endpoint(service="totally_unknown_service_xyz") + + body = json.loads(json.dumps({"error": exc_info.value.to_dict()})) + assert body["error"]["type"] == "auth_error" + assert body["error"]["param"] is None + + @pytest.mark.asyncio @pytest.mark.parametrize( "role", diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 4ed6a468371..1972245764f 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -12507,3 +12507,14 @@ def test_disabling_docs_does_not_disable_other_routes(monkeypatch): assert client.get("/redoc").status_code == 404 assert client.get("/health/liveliness").status_code == 200 + + +def test_assistants_error_body_is_openai_shaped(client_no_auth): + """A failed /v1/assistants call must answer with an OpenAI error object: a + real `type` string and a JSON null `param`, never the literal string "None".""" + response = client_no_auth.get("/v1/assistants") + + assert response.status_code >= 400 + error = response.json()["error"] + assert error["type"] == "internal_server_error" + assert error["param"] is None