mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(proxy): label a 408 invalid_request_error again and pin the in-route status on the files and realtime tails
This commit is contained in:
parent
8b89c909a9
commit
720f2ca775
5 changed files with 95 additions and 2 deletions
|
|
@ -12,7 +12,6 @@ _OPENAI_ERROR_TYPE_BY_STATUS: Final[Mapping[int, str]] = MappingProxyType(
|
|||
{
|
||||
status.HTTP_401_UNAUTHORIZED: "authentication_error",
|
||||
status.HTTP_403_FORBIDDEN: "permission_error",
|
||||
status.HTTP_408_REQUEST_TIMEOUT: "timeout_error",
|
||||
status.HTTP_429_TOO_MANY_REQUESTS: "rate_limit_error",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from litellm.proxy.common_utils.openai_error_payload import (
|
|||
(401, "authentication_error"),
|
||||
(403, "permission_error"),
|
||||
(404, "invalid_request_error"),
|
||||
(408, "timeout_error"),
|
||||
(408, "invalid_request_error"),
|
||||
(422, "invalid_request_error"),
|
||||
(429, "rate_limit_error"),
|
||||
(499, "invalid_request_error"),
|
||||
|
|
|
|||
|
|
@ -4773,3 +4773,49 @@ def test_get_file_content_reports_a_missing_managed_file_as_a_404(
|
|||
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == _missing_managed_file_error(file_id)
|
||||
|
||||
|
||||
def _setup_managed_file_stored_in_an_unknown_storage_backend(
|
||||
mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, llm_router: Router
|
||||
) -> None:
|
||||
"""Wire the content route to a managed file whose row names a storage backend the
|
||||
factory does not know, which is the one in-route ProxyException on these routes."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
import litellm.proxy.proxy_server as ps
|
||||
from litellm.llms.base_llm.files.transformation import BaseFileEndpoints
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
||||
proxy_logging_obj = setup_proxy_logging_object(monkeypatch, llm_router)
|
||||
managed_files = mocker.MagicMock(spec=BaseFileEndpoints)
|
||||
managed_files.prisma_client = mocker.MagicMock()
|
||||
proxy_logging_obj.proxy_hook_mapping["managed_files"] = managed_files
|
||||
repository = mocker.MagicMock()
|
||||
repository.table.find_first = mocker.AsyncMock(
|
||||
return_value=SimpleNamespace(storage_backend="ftp", storage_url="ftp://bucket/file")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.openai_files_endpoints.files_endpoints.ManagedFileRepository", lambda _prisma: repository
|
||||
)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
|
||||
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
api_key="test-key",
|
||||
user_role=LitellmUserRoles.INTERNAL_USER,
|
||||
user_id="test-user",
|
||||
)
|
||||
|
||||
|
||||
def test_get_file_content_keeps_the_status_of_a_rejection_raised_inside_the_route(
|
||||
mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, llm_router: Router
|
||||
):
|
||||
"""A ProxyException raised inside the route carries its status as the string ``code``,
|
||||
and the tail used to rebuild it as a 500 because it only read ``status_code``."""
|
||||
_setup_managed_file_stored_in_an_unknown_storage_backend(mocker, monkeypatch, llm_router)
|
||||
|
||||
response = _call_managed_file_route("GET", f"/v1/files/{_unified_managed_file_id()}/content")
|
||||
|
||||
assert response.status_code == 400, response.text
|
||||
error = response.json()["error"]
|
||||
assert error["message"].startswith("Storage backend error")
|
||||
assert (error["type"], error["param"], error["code"]) == ("invalid_request_error", "file_id", "400")
|
||||
|
|
|
|||
|
|
@ -1241,3 +1241,38 @@ def test_realtime_calls_upstream_rejection_answers_an_openai_typed_error(
|
|||
|
||||
assert response.status_code == 404
|
||||
assert (response.json()["error"]["type"], response.json()["error"]["param"]) == ("invalid_request_error", None)
|
||||
|
||||
|
||||
def test_transcription_sessions_rejection_answers_an_openai_typed_error(
|
||||
proxy_app: FastAPI,
|
||||
mock_add_litellm_data: Callable[..., Awaitable[object]],
|
||||
mock_pre_call_hook: Callable[..., Awaitable[object]],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
"""A model the router cannot serve surfaces as a bare HTTPException, which this tail
|
||||
used to relabel with the literal string "None" for both type and param."""
|
||||
|
||||
async def failing_route_request(*args: object, **kwargs: object) -> None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={"error": "realtime: Invalid model name passed in model=no-such-transcribe"},
|
||||
)
|
||||
|
||||
proxy_logging = MagicMock()
|
||||
proxy_logging.pre_call_hook = AsyncMock(side_effect=mock_pre_call_hook)
|
||||
proxy_logging.post_call_failure_hook = AsyncMock()
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.route_request", failing_route_request)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.add_litellm_data_to_request", mock_add_litellm_data)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging)
|
||||
proxy_app.dependency_overrides[user_api_key_auth] = lambda: UserAPIKeyAuth(user_id="test-user")
|
||||
try:
|
||||
response = TestClient(proxy_app, raise_server_exceptions=False).post(
|
||||
"/v1/realtime/transcription_sessions",
|
||||
headers={"Authorization": "Bearer sk-test-master-key"},
|
||||
json={"input_audio_transcription": {"model": "no-such-transcribe"}},
|
||||
)
|
||||
finally:
|
||||
proxy_app.dependency_overrides.pop(user_api_key_auth, None)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert (response.json()["error"]["type"], response.json()["error"]["param"]) == ("invalid_request_error", None)
|
||||
|
|
|
|||
|
|
@ -2202,6 +2202,19 @@ class TestGuardrailBlockErrorPayloadNeverStringifiesNone:
|
|||
assert frame["error"]["param"] is None
|
||||
assert frame["error"]["code"] == "400"
|
||||
|
||||
def test_a_streaming_frame_keeps_the_status_a_proxy_exception_was_raised_with(self):
|
||||
"""ProxyException stores its status as the string ``code``, so a 429 raised before the
|
||||
first chunk used to reach the SSE frame as a 500."""
|
||||
from litellm.proxy._types import ProxyException
|
||||
from litellm.proxy.common_request_processing import sse_error_payload
|
||||
|
||||
error_status, error_obj = sse_error_payload(
|
||||
ProxyException(message="Rate limit reached", type="rate_limit_error", param=None, code=429)
|
||||
)
|
||||
|
||||
assert error_status == 429
|
||||
assert (error_obj["type"], error_obj["code"]) == ("rate_limit_error", "429")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"status_code, expected_type",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue