From 659127bd0da2de6dc582d6c6cdab949cbaf5e943 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 2 Jul 2026 22:00:24 -0700 Subject: [PATCH 1/5] feat(guardrails): add unreachable_fallback fail-open option to headroom guardrail Reuses the existing unreachable_fallback flag (already implemented by generic_guardrail_api, akto, vigil_guard, repelloai) so headroom compression failures can forward the request uncompressed instead of blocking it with a 502. --- .../guardrail_hooks/headroom/__init__.py | 1 + .../guardrail_hooks/headroom/headroom.py | 89 ++++++++++--------- litellm/types/guardrails.py | 2 +- .../guardrails/guardrail_hooks/headroom.py | 10 ++- .../guardrail_hooks/test_headroom.py | 63 ++++++++++++- 5 files changed, 121 insertions(+), 44 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/__init__.py index 9b7934b7705..d2b8a979261 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/__init__.py @@ -34,6 +34,7 @@ def initialize_guardrail(litellm_params: LitellmParams, guardrail: Guardrail) -> guardrail_name=guardrail["guardrail_name"], event_hook=_coerce_event_hook(litellm_params.mode), default_on=litellm_params.default_on or False, + unreachable_fallback=litellm_params.unreachable_fallback, ) litellm.logging_callback_manager.add_litellm_callback( # pyright: ignore[reportUnknownMemberType] _callback diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index 4badb48e2eb..575b4ca55dd 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -214,6 +214,7 @@ class HeadroomGuardrail(CustomGuardrail): guardrail_name: str | None = None, event_hook: GuardrailEventHooks | list[GuardrailEventHooks] | Mode | None = None, default_on: bool = False, + unreachable_fallback: str | None = None, ): self.headroom_api_base = (api_base or get_secret_str("HEADROOM_API_BASE") or "").rstrip("/") if not self.headroom_api_base: @@ -223,6 +224,9 @@ class HeadroomGuardrail(CustomGuardrail): ) self.headroom_api_key = api_key or get_secret_str("HEADROOM_API_KEY") self.headroom_model = model + self.unreachable_fallback: Literal["fail_closed", "fail_open"] = ( + "fail_open" if unreachable_fallback == "fail_open" else "fail_closed" + ) self.async_handler = get_async_httpx_client( llm_provider=httpxSpecialProvider.GuardrailCallback, ) @@ -257,6 +261,21 @@ class HeadroomGuardrail(CustomGuardrail): if expiry > now } + def _handle_compress_failure( + self, + messages: list[dict[str, object]], + error: str, + detail: dict[str, object], + ) -> list[dict[str, object]]: + if self.unreachable_fallback == "fail_open": + verbose_proxy_logger.critical( + "Headroom: %s; fail_open configured, forwarding request uncompressed. detail=%s", + error, + detail, + ) + return messages + raise HTTPException(status_code=502, detail={"error": error, **detail}) + async def _call_compress( self, messages: list[dict[str, object]], @@ -273,67 +292,55 @@ class HeadroomGuardrail(CustomGuardrail): headers=self._request_headers(), ) except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError) as e: - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service unreachable", - "detail": str(e), - }, - ) from e + return self._handle_compress_failure( + messages, + "Headroom compression service unreachable", + {"detail": str(e)}, + ) if raw_response is None: - raise HTTPException( - status_code=502, - detail={"error": "Headroom compression service returned no response"}, + return self._handle_compress_failure( + messages, + "Headroom compression service returned no response", + {}, ) response: HttpxResponse = raw_response if response.status_code != 200: - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service returned an error", - "status_code": response.status_code, - "body": response.text, - }, + return self._handle_compress_failure( + messages, + "Headroom compression service returned an error", + {"status_code": response.status_code, "body": response.text}, ) try: body: object = response.json() except ValueError: - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service returned non-JSON response", - "body": response.text[:500], - }, + return self._handle_compress_failure( + messages, + "Headroom compression service returned non-JSON response", + {"body": response.text[:500]}, ) if not _is_str_object_dict(body): - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service returned unexpected response shape", - "body": response.text[:500], - }, + return self._handle_compress_failure( + messages, + "Headroom compression service returned unexpected response shape", + {"body": response.text[:500]}, ) compressed_messages = body.get("messages") if not _is_object_list(compressed_messages): - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service response missing 'messages'", - "body": response.text, - }, + return self._handle_compress_failure( + messages, + "Headroom compression service response missing 'messages'", + {"body": response.text}, ) filtered = [item for item in compressed_messages if _is_str_object_dict(item)] if not filtered: - raise HTTPException( - status_code=502, - detail={ - "error": "Headroom compression service returned empty message list", - "body": response.text, - }, + return self._handle_compress_failure( + messages, + "Headroom compression service returned empty message list", + {"body": response.text}, ) verbose_proxy_logger.debug( diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index 889e029b902..8d7d7311fad 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -697,7 +697,7 @@ class BaseLitellmParams(ContentFilterConfigModel): # works for new and patch up default="fail_closed", description=( "Behavior when a guardrail endpoint is unreachable due to network errors. " - "Implemented by guardrail='generic_guardrail_api', 'akto', 'vigil_guard', and 'repelloai'. " + "Implemented by guardrail='generic_guardrail_api', 'akto', 'vigil_guard', 'repelloai', and 'headroom'. " "'fail_closed' raises an error (default). 'fail_open' logs a critical error and allows the request to proceed." ), ) diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py b/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py index 3186c9fc612..fd962b8ffa1 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Literal, Optional from pydantic import BaseModel, Field @@ -18,6 +18,14 @@ class HeadroomGuardrailConfigModel(GuardrailConfigModel[BaseModel]): default=None, description="Model name forwarded to the headroom /v1/compress endpoint.", ) + unreachable_fallback: Optional[Literal["fail_closed", "fail_open"]] = Field( + default="fail_closed", + description=( + "Behavior when the headroom compression service is unreachable or errors. " + "'fail_closed' raises an error (default). 'fail_open' logs a critical error and " + "forwards the request uncompressed instead of blocking it." + ), + ) @staticmethod def ui_friendly_name() -> str: diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index f5ce6cedf64..954485d6b90 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -6,8 +6,9 @@ Tests cover: - x-headroom-bypass: true header causes guardrail to skip compression - missing or empty messages are passed through unchanged - response-type input is passed through unchanged -- /v1/compress HTTP error raises HTTPException +- /v1/compress HTTP error raises HTTPException (fail_closed, the default) - /v1/compress returning malformed JSON raises HTTPException +- unreachable_fallback="fail_open" forwards the request uncompressed instead of raising - CCR: headroom_retrieve tool injected when compressed messages contain hashes - CCR: async_should_run_agentic_loop returns True when response has headroom_retrieve tool calls - CCR: async_build_agentic_loop_plan calls retrieve endpoint and builds follow-up messages @@ -806,6 +807,56 @@ async def test_apply_guardrail_transport_error_raises(): assert "unreachable" in str(exc_info.value.detail) +@pytest.mark.asyncio +async def test_apply_guardrail_transport_error_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=httpx.ConnectError("Connection refused"), + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + +@pytest.mark.asyncio +async def test_apply_guardrail_http_error_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + mock_response = _make_compress_response([], status=500) + mock_response.text = "Internal Server Error" + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + @pytest.mark.asyncio async def test_apply_guardrail_missing_messages_key_raises(): guardrail = _make_guardrail() @@ -875,6 +926,16 @@ def test_init_raises_without_api_base(): HeadroomGuardrail(api_base=None) +def test_init_defaults_to_fail_closed(): + guardrail = _make_guardrail() + assert guardrail.unreachable_fallback == "fail_closed" + + +def test_init_rejects_invalid_unreachable_fallback_value(): + guardrail = _make_guardrail(unreachable_fallback="not-a-real-mode") + assert guardrail.unreachable_fallback == "fail_closed" + + def test_bypass_header_case_insensitive(): guardrail = _make_guardrail() From 00dffcd075292b816356c9e0efd83c7f4f02baa6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 2 Jul 2026 22:17:52 -0700 Subject: [PATCH 2/5] fix(guardrails): catch httpx.HTTPStatusError in headroom compress call litellm's async httpx client already calls raise_for_status() internally, so a non-2xx /v1/compress response surfaced as an uncaught httpx.HTTPStatusError instead of going through the guardrail's status_code check. Caught live by running the guardrail against a mock headroom endpoint that returns 500: unreachable_fallback=fail_open silently failed to forward the request until this fix. --- .../guardrail_hooks/headroom/headroom.py | 6 ++ .../guardrail_hooks/test_headroom.py | 67 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index 575b4ca55dd..fe6d6bf7051 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -291,6 +291,12 @@ class HeadroomGuardrail(CustomGuardrail): json=payload, headers=self._request_headers(), ) + except httpx.HTTPStatusError as e: + return self._handle_compress_failure( + messages, + "Headroom compression service returned an error", + {"status_code": e.response.status_code, "body": e.response.text}, + ) except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError) as e: return self._handle_compress_failure( messages, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index 954485d6b90..fdf96c44f47 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -8,6 +8,8 @@ Tests cover: - response-type input is passed through unchanged - /v1/compress HTTP error raises HTTPException (fail_closed, the default) - /v1/compress returning malformed JSON raises HTTPException +- /v1/compress non-2xx surfaces as httpx.HTTPStatusError (raise_for_status), + not a status_code check on the returned response -- both are handled - unreachable_fallback="fail_open" forwards the request uncompressed instead of raising - CCR: headroom_retrieve tool injected when compressed messages contain hashes - CCR: async_should_run_agentic_loop returns True when response has headroom_retrieve tool calls @@ -807,6 +809,71 @@ async def test_apply_guardrail_transport_error_raises(): assert "unreachable" in str(exc_info.value.detail) +def _make_http_status_error(status: int, body: str) -> httpx.HTTPStatusError: + request = httpx.Request("POST", f"{FAKE_API_BASE}/v1/compress") + response = httpx.Response(status, request=request, text=body) + return httpx.HTTPStatusError( + f"Server error '{status}' for url", + request=request, + response=response, + ) + + +@pytest.mark.asyncio +async def test_apply_guardrail_http_status_error_raises(): + """Regression test: litellm's async httpx client calls raise_for_status() + internally, so a non-2xx /v1/compress response surfaces as + httpx.HTTPStatusError, not as a returned MagicMock with status_code set. + A prior version of _call_compress only checked response.status_code and + never caught this exception, so it went unhandled instead of blocking + the request per fail_closed policy.""" + guardrail = _make_guardrail() + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=_make_http_status_error(500, "headroom internal error"), + ): + with pytest.raises(HTTPException) as exc_info: + await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert exc_info.value.status_code == 502 + + +@pytest.mark.asyncio +async def test_apply_guardrail_http_status_error_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=_make_http_status_error(500, "headroom internal error"), + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + @pytest.mark.asyncio async def test_apply_guardrail_transport_error_fail_open_forwards_uncompressed(): guardrail = _make_guardrail(unreachable_fallback="fail_open") From 53d2331c700ce1772c45806b6e56a49534498778 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Jul 2026 19:13:30 +0000 Subject: [PATCH 3/5] fix: catch litellm.Timeout in HeadroomGuardrail to support fail_open on timeouts async_handler.post catches httpx.TimeoutException and re-raises it as litellm.Timeout (a subclass of openai.APITimeoutError). The except blocks in _call_compress and _call_retrieve only listed httpx exception types, so litellm.Timeout propagated uncaught and bypassed the unreachable_fallback=fail_open path. Add litellm.Timeout to both except clauses and add regression tests for the fail_closed and fail_open timeout paths. --- .../guardrail_hooks/headroom/headroom.py | 6 +- .../guardrail_hooks/test_headroom.py | 60 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index fe6d6bf7051..c218cac7aa2 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Any, Literal, Optional import httpx from fastapi import HTTPException + +import litellm from httpx import Response as HttpxResponse from typing_extensions import TypeGuard @@ -297,7 +299,7 @@ class HeadroomGuardrail(CustomGuardrail): "Headroom compression service returned an error", {"status_code": e.response.status_code, "body": e.response.text}, ) - except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError) as e: + except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError, litellm.Timeout) as e: return self._handle_compress_failure( messages, "Headroom compression service unreachable", @@ -368,7 +370,7 @@ class HeadroomGuardrail(CustomGuardrail): params=params, headers=self._request_headers(), ) - except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError) as e: + except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError, litellm.Timeout) as e: verbose_proxy_logger.warning("Headroom: retrieve failed for hash=%s: %s", hash_value, e) return f"[Headroom: retrieval failed for hash={hash_value}]" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index fdf96c44f47..09422be04ab 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -24,6 +24,8 @@ import httpx import pytest from fastapi import HTTPException +import litellm + from litellm.proxy.guardrails.guardrail_hooks.headroom.headroom import ( HeadroomGuardrail, extract_hashes_from_messages, @@ -1187,3 +1189,61 @@ async def test_async_should_run_agentic_loop_detects_responses_api_output_format assert should_run is True assert len(ctx["tool_calls"]) == 1 assert ctx["tool_calls"][0]["arguments"]["hash"] == "b573993006976af767214fac" + + +@pytest.mark.asyncio +async def test_apply_guardrail_litellm_timeout_raises_when_fail_closed(): + guardrail = _make_guardrail() + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=litellm.Timeout( + message="Connection timed out after 10 seconds.", + model="default-model-name", + llm_provider="litellm-httpx-handler", + ), + ): + with pytest.raises(HTTPException) as exc_info: + await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert exc_info.value.status_code == 502 + assert "unreachable" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_apply_guardrail_litellm_timeout_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=litellm.Timeout( + message="Connection timed out after 10 seconds.", + model="default-model-name", + llm_provider="litellm-httpx-handler", + ), + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES From 01dfbf7ebb5a037e4de3ec3698aa1ba783a3f389 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 3 Jul 2026 19:40:33 +0000 Subject: [PATCH 4/5] fix(guardrails): address review comments on headroom fail_open - Prevent fail-open from registering user-supplied hashes as valid for CCR retrieval; _call_compress now returns (messages, compressed_ok) so apply_guardrail skips hash extraction and tool injection when compression did not succeed - Remove Optional wrapper from HeadroomGuardrailConfigModel.unreachable_fallback to match BaseLitellmParams typing - Add fail_open tests for non-JSON response, missing messages key, and empty message list paths - Add regression test verifying fail_open does not authorize attacker-planted hashes - Regenerate dashboard API types Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../guardrail_hooks/headroom/headroom.py | 25 +- .../guardrails/guardrail_hooks/headroom.py | 2 +- .../guardrail_hooks/test_headroom.py | 123 ++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 1067 ----------------- 4 files changed, 138 insertions(+), 1079 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index c218cac7aa2..37863e0e356 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -282,7 +282,7 @@ class HeadroomGuardrail(CustomGuardrail): self, messages: list[dict[str, object]], model: str | None, - ) -> list[dict[str, object]]: + ) -> tuple[list[dict[str, object]], bool]: payload: dict[str, object] = {"messages": messages} if model: payload["model"] = model @@ -298,19 +298,19 @@ class HeadroomGuardrail(CustomGuardrail): messages, "Headroom compression service returned an error", {"status_code": e.response.status_code, "body": e.response.text}, - ) + ), False except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError, litellm.Timeout) as e: return self._handle_compress_failure( messages, "Headroom compression service unreachable", {"detail": str(e)}, - ) + ), False if raw_response is None: return self._handle_compress_failure( messages, "Headroom compression service returned no response", {}, - ) + ), False response: HttpxResponse = raw_response if response.status_code != 200: @@ -318,7 +318,7 @@ class HeadroomGuardrail(CustomGuardrail): messages, "Headroom compression service returned an error", {"status_code": response.status_code, "body": response.text}, - ) + ), False try: body: object = response.json() @@ -327,13 +327,13 @@ class HeadroomGuardrail(CustomGuardrail): messages, "Headroom compression service returned non-JSON response", {"body": response.text[:500]}, - ) + ), False if not _is_str_object_dict(body): return self._handle_compress_failure( messages, "Headroom compression service returned unexpected response shape", {"body": response.text[:500]}, - ) + ), False compressed_messages = body.get("messages") if not _is_object_list(compressed_messages): @@ -341,7 +341,7 @@ class HeadroomGuardrail(CustomGuardrail): messages, "Headroom compression service response missing 'messages'", {"body": response.text}, - ) + ), False filtered = [item for item in compressed_messages if _is_str_object_dict(item)] if not filtered: @@ -349,7 +349,7 @@ class HeadroomGuardrail(CustomGuardrail): messages, "Headroom compression service returned empty message list", {"body": response.text}, - ) + ), False verbose_proxy_logger.debug( "Headroom: compressed %s tokens -> %s tokens (ratio %.2f)", @@ -357,7 +357,7 @@ class HeadroomGuardrail(CustomGuardrail): body.get("tokens_after", "?"), body.get("compression_ratio", 0), ) - return filtered + return filtered, True async def _call_retrieve(self, hash_value: str, query: str | None = None) -> str: params: dict[str, str] = {} @@ -421,11 +421,14 @@ class HeadroomGuardrail(CustomGuardrail): return inputs model = self.headroom_model or request_data.get("model") - compressed = await self._call_compress( + compressed, compression_succeeded = await self._call_compress( messages=messages, model=model if isinstance(model, str) else None, ) + if not compression_succeeded: + return {**inputs, "structured_messages": compressed} # pyright: ignore[reportReturnType] + hashes = extract_hashes_from_messages(compressed) if not hashes: return {**inputs, "structured_messages": compressed} # pyright: ignore[reportReturnType] diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py b/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py index fd962b8ffa1..71aa243069a 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/headroom.py @@ -18,7 +18,7 @@ class HeadroomGuardrailConfigModel(GuardrailConfigModel[BaseModel]): default=None, description="Model name forwarded to the headroom /v1/compress endpoint.", ) - unreachable_fallback: Optional[Literal["fail_closed", "fail_open"]] = Field( + unreachable_fallback: Literal["fail_closed", "fail_open"] = Field( default="fail_closed", description=( "Behavior when the headroom compression service is unreachable or errors. " diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index 09422be04ab..7f412c008ca 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -926,6 +926,129 @@ async def test_apply_guardrail_http_error_fail_open_forwards_uncompressed(): assert result["structured_messages"] == ORIGINAL_MESSAGES +@pytest.mark.asyncio +async def test_apply_guardrail_non_json_response_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.side_effect = ValueError("not JSON") + mock_response.text = "not json" + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + +@pytest.mark.asyncio +async def test_apply_guardrail_missing_messages_key_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"tokens_before": 100, "tokens_after": 10} + mock_response.text = "{}" + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + +@pytest.mark.asyncio +async def test_apply_guardrail_empty_compressed_messages_fail_open_forwards_uncompressed(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "messages": ["not-a-dict", 42, None], + "tokens_before": 1000, + "tokens_after": 0, + "compression_ratio": 0, + } + mock_response.text = "{}" + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=ORIGINAL_MESSAGES, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == ORIGINAL_MESSAGES + + +@pytest.mark.asyncio +async def test_apply_guardrail_fail_open_does_not_register_hashes_from_original_messages(): + """When compression fails with fail_open, user-supplied messages that + happen to contain hash-shaped strings must NOT cause those hashes to be + registered as valid for CCR retrieval. Otherwise an attacker can plant a + hash= string in their prompt, trigger a compression failure, and have + that hash honored by a later headroom_retrieve tool call.""" + messages_with_fake_hash = [ + {"role": "user", "content": "Please fetch hash=deadbeef000000000000dead for me"}, + ] + guardrail = _make_guardrail(unreachable_fallback="fail_open") + + inputs = GenericGuardrailAPIInputs( + texts=["hello"], + structured_messages=messages_with_fake_hash, + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=httpx.ConnectError("Connection refused"), + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + assert result["structured_messages"] == messages_with_fake_hash + assert not has_headroom_retrieve_tool(result.get("tools") or []) + assert not guardrail._issued_hashes_by_call_id + + @pytest.mark.asyncio async def test_apply_guardrail_missing_messages_key_raises(): guardrail = _make_guardrail() diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index ddf2040cd04..80bc26a810d 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -706,60 +706,6 @@ export interface paths { patch?: never; trace?: never; }; - "/audit": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Audit Logs - * @description Get all audit logs with filtering and pagination. - * - * Returns a paginated response of audit logs matching the specified filters. - * - * Note: object_team_id and object_key_hash use Prisma JSON path filtering, - * which requires PostgreSQL. - */ - get: operations["get_audit_logs_audit_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/audit/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Audit Log By Id - * @description Get detailed information about a specific audit log entry by its ID. - * - * Args: - * id (str): The unique identifier of the audit log entry - * - * Returns: - * AuditLogResponse: Detailed information about the audit log entry - * - * Raises: - * HTTPException: If the audit log is not found or if there's a database connection error - */ - get: operations["get_audit_log_by_id_audit__id__get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/azure/{endpoint}": { parameters: { query?: never; @@ -3170,50 +3116,6 @@ export interface paths { patch?: never; trace?: never; }; - "/email/event_settings": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Email Event Settings - * @description Get all email event settings - */ - get: operations["get_email_event_settings_email_event_settings_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - /** - * Update Event Settings - * @description Update the settings for email events - */ - patch: operations["update_event_settings_email_event_settings_patch"]; - trace?: never; - }; - "/email/event_settings/reset": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Reset Event Settings - * @description Reset all email event settings to default (new user invitations on, virtual key creation off) - */ - post: operations["reset_event_settings_email_event_settings_reset_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/embeddings": { parameters: { query?: never; @@ -9964,240 +9866,6 @@ export interface paths { patch?: never; trace?: never; }; - "/project/delete": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - post?: never; - /** - * Delete Project - * @description Delete projects - * - * Parameters: - * - project_ids: *List[str]* - List of project ids to delete - * - * Example: - * ```bash - * curl --location --request DELETE 'http://0.0.0.0:4000/project/delete' \ - * --header 'Authorization: Bearer sk-1234' \ - * --header 'Content-Type: application/json' \ - * --data '{ - * "project_ids": ["project-123", "project-456"] - * }' - * ``` - */ - delete: operations["delete_project_project_delete_delete"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/project/info": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Project Info - * @description Get information about a specific project - * - * Parameters: - * - project_id: *str* - The project id to fetch info for - * - * Example: - * ```bash - * curl --location 'http://0.0.0.0:4000/project/info?project_id=project-123' \ - * --header 'Authorization: Bearer sk-1234' - * ``` - */ - get: operations["project_info_project_info_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/project/list": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List Projects - * @description List all projects that the user has access to - * - * Example: - * ```bash - * curl --location 'http://0.0.0.0:4000/project/list' \ - * --header 'Authorization: Bearer sk-1234' - * ``` - */ - get: operations["list_projects_project_list_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/project/new": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * New Project - * @description Create a new project. Projects sit between teams and keys in the hierarchy. - * - * Only admins or team admins can create projects. - * - * # Parameters - * - * - project_alias: *Optional[str]* - The name of the project. - * - description: *Optional[str]* - Description of the project's purpose and use case. - * - team_id: *str* - The team id that this project belongs to. Required. - * - models: *List* - The models the project has access to. - * - budget_id: *Optional[str]* - The id for a budget (tpm/rpm/max budget) for the project. - * ### IF NO BUDGET ID - CREATE ONE WITH THESE PARAMS ### - * - max_budget: *Optional[float]* - Max budget for project - * - tpm_limit: *Optional[int]* - Max tpm limit for project - * - rpm_limit: *Optional[int]* - Max rpm limit for project - * - max_parallel_requests: *Optional[int]* - Max parallel requests for project - * - soft_budget: *Optional[float]* - Get a slack alert when this soft budget is reached. Don't block requests. - * - model_max_budget: *Optional[dict]* - Max budget for a specific model. Example: {"gpt-4": 100.0, "gpt-3.5-turbo": 50.0} - * - model_rpm_limit: *Optional[dict]* - RPM limits per model. Example: {"gpt-4": 1000, "gpt-3.5-turbo": 5000} - * - model_tpm_limit: *Optional[dict]* - TPM limits per model. Example: {"gpt-4": 50000, "gpt-3.5-turbo": 100000} - * - budget_duration: *Optional[str]* - Frequency of reseting project budget - * - metadata: *Optional[dict]* - Metadata for project, store information for project. Example metadata - {"use_case_id": "SNOW-12345", "responsible_ai_id": "RAI-67890"} - * - tags: *Optional[list]* - Tags for the project. Example: ["production", "api"] - * - blocked: *bool* - Flag indicating if the project is blocked or not - will stop all calls from keys with this project_id. - * - object_permission: Optional[LiteLLM_ObjectPermissionBase] - project-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"]}. IF null or {} then no object permission. - * - * Example 1: Create new project **without** a budget_id, with model-specific limits - * - * ```bash - * curl --location 'http://0.0.0.0:4000/project/new' \ - * --header 'Authorization: Bearer sk-1234' \ - * --header 'Content-Type: application/json' \ - * --data '{ - * "project_alias": "flight-search-assistant", - * "description": "AI-powered flight search and booking assistant", - * "team_id": "team-123", - * "models": ["gpt-4", "gpt-3.5-turbo"], - * "max_budget": 100, - * "model_rpm_limit": { - * "gpt-4": 1000, - * "gpt-3.5-turbo": 5000 - * }, - * "model_tpm_limit": { - * "gpt-4": 50000, - * "gpt-3.5-turbo": 100000 - * }, - * "metadata": { - * "use_case_id": "SNOW-12345", - * "responsible_ai_id": "RAI-67890" - * } - * }' - * ``` - * - * Example 2: Create new project **with** a budget_id - * - * ```bash - * curl --location 'http://0.0.0.0:4000/project/new' \ - * --header 'Authorization: Bearer sk-1234' \ - * --header 'Content-Type: application/json' \ - * --data '{ - * "project_alias": "hotel-recommendations", - * "description": "Personalized hotel recommendation engine", - * "team_id": "team-123", - * "models": ["claude-3-sonnet"], - * "budget_id": "428eeaa8-f3ac-4e85-a8fb-7dc8d7aa8689", - * "metadata": { - * "use_case_id": "SNOW-54321" - * } - * }' - * ``` - */ - post: operations["new_project_project_new_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/project/update": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Update Project - * @description Update a project - * - * Parameters: - * - project_id: *str* - The project id to update. Required. - * - project_alias: *Optional[str]* - Updated name for the project - * - description: *Optional[str]* - Updated description for the project - * - team_id: *Optional[str]* - Updated team_id for the project - * - metadata: *Optional[dict]* - Updated metadata for project - * - models: *Optional[list]* - Updated list of models for the project - * - blocked: *Optional[bool]* - Updated blocked status - * - max_budget: *Optional[float]* - Updated max budget - * - tpm_limit: *Optional[int]* - Updated tpm limit - * - rpm_limit: *Optional[int]* - Updated rpm limit - * - model_rpm_limit: *Optional[dict]* - Updated RPM limits per model - * - model_tpm_limit: *Optional[dict]* - Updated TPM limits per model - * - budget_duration: *Optional[str]* - Updated budget duration - * - tags: *Optional[list]* - Updated list of tags for the project - * - object_permission: Optional[LiteLLM_ObjectPermissionBase] - Updated object permission - * - * Example: - * ```bash - * curl --location 'http://0.0.0.0:4000/project/update' \ - * --header 'Authorization: Bearer sk-1234' \ - * --header 'Content-Type: application/json' \ - * --data '{ - * "project_id": "project-123", - * "description": "Updated flight search system with enhanced capabilities", - * "max_budget": 200, - * "model_rpm_limit": { - * "gpt-4": 2000, - * "gpt-3.5-turbo": 10000 - * }, - * "metadata": { - * "use_case_id": "SNOW-12345", - * "status": "active" - * } - * }' - * ``` - */ - post: operations["update_project_project_update_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/prompts": { parameters: { query?: never; @@ -11240,27 +10908,6 @@ export interface paths { patch?: never; trace?: never; }; - "/robots.txt": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Robots - * @description Block all web crawlers from indexing the proxy server endpoints - * This is useful for ensuring that the API endpoints aren't indexed by search engines - */ - get: operations["get_robots_robots_txt_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/router/fields": { parameters: { query?: never; @@ -14300,26 +13947,6 @@ export interface paths { patch?: never; trace?: never; }; - "/user/available_users": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Available Enterprise Users - * @description For keys with `max_users` set, return the list of users that are allowed to use the key. - */ - get: operations["available_enterprise_users_user_available_users_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/user/bulk_update": { parameters: { query?: never; @@ -20600,37 +20227,6 @@ export interface components { */ unnamed_teams_count: number; }; - /** - * AuditLogResponse - * @description Response model for a single audit log entry - */ - AuditLogResponse: { - /** Action */ - action: string; - /** Before Value */ - before_value?: { - [key: string]: unknown; - } | null; - /** Changed By */ - changed_by: string; - /** Changed By Api Key */ - changed_by_api_key: string; - /** Id */ - id: string; - /** Object Id */ - object_id: string; - /** Table Name */ - table_name: string; - /** - * Updated At - * Format: date-time - */ - updated_at: string; - /** Updated Values */ - updated_values?: { - [key: string]: unknown; - } | null; - }; /** BaseLitellmParams */ "BaseLitellmParams-Input": { /** @@ -23120,14 +22716,6 @@ export interface components { /** Organization Ids */ organization_ids: string[]; }; - /** - * DeleteProjectRequest - * @description Request model for DELETE /project/delete - */ - DeleteProjectRequest: { - /** Project Ids */ - project_ids: string[]; - }; /** * DeleteSkillResponse * @description Response from deleting a skill @@ -23242,27 +22830,6 @@ export interface components { /** Write Capacity Units */ write_capacity_units?: number | null; }; - /** - * EmailEvent - * @enum {string} - */ - EmailEvent: "Virtual Key Created" | "New User Invitation" | "Virtual Key Rotated" | "Soft Budget Crossed" | "Max Budget Alert"; - /** EmailEventSettings */ - EmailEventSettings: { - /** Enabled */ - enabled: boolean; - event: components["schemas"]["EmailEvent"]; - }; - /** EmailEventSettingsResponse */ - EmailEventSettingsResponse: { - /** Settings */ - settings: components["schemas"]["EmailEventSettings"][]; - }; - /** EmailEventSettingsUpdateRequest */ - EmailEventSettingsUpdateRequest: { - /** Settings */ - settings: components["schemas"]["EmailEventSettings"][]; - }; /** EmbeddingRequest */ EmbeddingRequest: { /** @@ -25556,65 +25123,6 @@ export interface components { } & { [key: string]: unknown; }; - /** - * LiteLLM_ProjectTable - * @description Database model representation for project - */ - LiteLLM_ProjectTable: { - /** - * Blocked - * @default false - */ - blocked: boolean; - /** Budget Id */ - budget_id?: string | null; - /** Created At */ - created_at?: string | null; - /** Created By */ - created_by?: string | null; - /** Description */ - description?: string | null; - litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null; - /** Metadata */ - metadata?: { - [key: string]: unknown; - } | null; - /** Model Rpm Limit */ - model_rpm_limit?: { - [key: string]: unknown; - } | null; - /** Model Spend */ - model_spend?: { - [key: string]: unknown; - } | null; - /** Model Tpm Limit */ - model_tpm_limit?: { - [key: string]: unknown; - } | null; - /** - * Models - * @default [] - */ - models: string[]; - object_permission?: components["schemas"]["LiteLLM_ObjectPermissionTable"] | null; - /** Object Permission Id */ - object_permission_id?: string | null; - /** Project Alias */ - project_alias?: string | null; - /** Project Id */ - project_id: string; - /** - * Spend - * @default 0 - */ - spend: number; - /** Team Id */ - team_id?: string | null; - /** Updated At */ - updated_at?: string | null; - /** Updated By */ - updated_by?: string | null; - }; /** LiteLLM_ProxyModelTable */ LiteLLM_ProxyModelTable: { /** @@ -27625,134 +27133,6 @@ export interface components { /** Users */ users?: components["schemas"]["LiteLLM_UserTable"][] | null; }; - /** - * NewProjectRequest - * @description Request model for POST /project/new - */ - NewProjectRequest: { - /** Allowed Models */ - allowed_models?: string[] | null; - /** - * Blocked - * @default false - */ - blocked: boolean; - /** Budget Duration */ - budget_duration?: string | null; - /** Budget Id */ - budget_id?: string | null; - /** Description */ - description?: string | null; - /** Guardrails */ - guardrails?: string[] | null; - /** Max Budget */ - max_budget?: number | null; - /** Max Parallel Requests */ - max_parallel_requests?: number | null; - /** Metadata */ - metadata?: { - [key: string]: unknown; - } | null; - /** Model Max Budget */ - model_max_budget?: { - [key: string]: unknown; - } | null; - /** Model Rpm Limit */ - model_rpm_limit?: { - [key: string]: unknown; - } | null; - /** Model Tpm Limit */ - model_tpm_limit?: { - [key: string]: unknown; - } | null; - /** - * Models - * @default [] - */ - models: string[]; - object_permission?: components["schemas"]["LiteLLM_ObjectPermissionBase"] | null; - /** Policies */ - policies?: string[] | null; - /** Project Alias */ - project_alias?: string | null; - /** Project Id */ - project_id?: string | null; - /** Rpm Limit */ - rpm_limit?: number | null; - /** Soft Budget */ - soft_budget?: number | null; - /** Tags */ - tags?: string[] | null; - /** Team Id */ - team_id: string; - /** Tpm Limit */ - tpm_limit?: number | null; - }; - /** - * NewProjectResponse - * @description Response model for POST /project/new - */ - NewProjectResponse: { - /** - * Blocked - * @default false - */ - blocked: boolean; - /** Budget Id */ - budget_id?: string | null; - /** - * Created At - * Format: date-time - */ - created_at: string; - /** Created By */ - created_by?: string | null; - /** Description */ - description?: string | null; - litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null; - /** Metadata */ - metadata?: { - [key: string]: unknown; - } | null; - /** Model Rpm Limit */ - model_rpm_limit?: { - [key: string]: unknown; - } | null; - /** Model Spend */ - model_spend?: { - [key: string]: unknown; - } | null; - /** Model Tpm Limit */ - model_tpm_limit?: { - [key: string]: unknown; - } | null; - /** - * Models - * @default [] - */ - models: string[]; - object_permission?: components["schemas"]["LiteLLM_ObjectPermissionTable"] | null; - /** Object Permission Id */ - object_permission_id?: string | null; - /** Project Alias */ - project_alias?: string | null; - /** Project Id */ - project_id: string; - /** - * Spend - * @default 0 - */ - spend: number; - /** Team Id */ - team_id?: string | null; - /** - * Updated At - * Format: date-time - */ - updated_at: string; - /** Updated By */ - updated_by?: string | null; - }; /** NewTeamRequest */ NewTeamRequest: { /** Access Group Ids */ @@ -28262,34 +27642,6 @@ export interface components { /** Organizations */ organizations: string[]; }; - /** - * PaginatedAuditLogResponse - * @description Response model for paginated audit logs - */ - PaginatedAuditLogResponse: { - /** Audit Logs */ - audit_logs: components["schemas"]["AuditLogResponse"][]; - /** - * Page - * @description Current page number - */ - page: number; - /** - * Page Size - * @description Number of items per page - */ - page_size: number; - /** - * Total - * @description Total number of audit logs matching the filters - */ - total: number; - /** - * Total Pages - * @description Total number of pages - */ - total_pages: number; - }; /** PassThroughEndpointResponse */ PassThroughEndpointResponse: { /** Endpoints */ @@ -31800,63 +31152,6 @@ export interface components { /** Model Names */ model_names?: string[] | null; }; - /** - * UpdateProjectRequest - * @description Request model for POST /project/update - */ - UpdateProjectRequest: { - /** Allowed Models */ - allowed_models?: string[] | null; - /** Blocked */ - blocked?: boolean | null; - /** Budget Duration */ - budget_duration?: string | null; - /** Budget Id */ - budget_id?: string | null; - /** Description */ - description?: string | null; - /** Guardrails */ - guardrails?: string[] | null; - /** Max Budget */ - max_budget?: number | null; - /** Max Parallel Requests */ - max_parallel_requests?: number | null; - /** Metadata */ - metadata?: { - [key: string]: unknown; - } | null; - /** Model Max Budget */ - model_max_budget?: { - [key: string]: unknown; - } | null; - /** Model Rpm Limit */ - model_rpm_limit?: { - [key: string]: unknown; - } | null; - /** Model Tpm Limit */ - model_tpm_limit?: { - [key: string]: unknown; - } | null; - /** Models */ - models?: string[] | null; - object_permission?: components["schemas"]["LiteLLM_ObjectPermissionBase"] | null; - /** Policies */ - policies?: string[] | null; - /** Project Alias */ - project_alias?: string | null; - /** Project Id */ - project_id: string; - /** Rpm Limit */ - rpm_limit?: number | null; - /** Soft Budget */ - soft_budget?: number | null; - /** Tags */ - tags?: string[] | null; - /** Team Id */ - team_id?: string | null; - /** Tpm Limit */ - tpm_limit?: number | null; - }; /** * UpdatePublicModelGroupsRequest * @description Request model for updating public model groups @@ -34300,105 +33595,6 @@ export interface operations { }; }; }; - get_audit_logs_audit_get: { - parameters: { - query?: { - page?: number; - page_size?: number; - /** @description Filter by user or system that performed the action */ - changed_by?: string | null; - /** @description Filter by API key hash that performed the action */ - changed_by_api_key?: string | null; - /** @description Filter by action type (create, update, delete) */ - action?: string | null; - /** @description Filter by table name that was modified */ - table_name?: string | null; - /** @description Filter by ID of the object that was modified */ - object_id?: string | null; - /** @description Filter logs after this date */ - start_date?: string | null; - /** @description Filter logs before this date */ - end_date?: string | null; - /** @description Filter by team_id present in before_value or updated_values JSON (PostgreSQL only) */ - object_team_id?: string | null; - /** @description Filter by token (key hash) present in before_value or updated_values JSON (PostgreSQL only) */ - object_key_hash?: string | null; - /** @description Column to sort by (e.g. 'updated_at', 'action', 'table_name') */ - sort_by?: string | null; - /** @description Sort order ('asc' or 'desc') */ - sort_order?: string; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["PaginatedAuditLogResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_audit_log_by_id_audit__id__get: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AuditLogResponse"]; - }; - }; - /** @description Audit log not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - /** @description Database connection error */ - 500: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; azure_proxy_route_azure__endpoint__get: { parameters: { query?: never; @@ -37924,79 +37120,6 @@ export interface operations { }; }; }; - get_email_event_settings_email_event_settings_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EmailEventSettingsResponse"]; - }; - }; - }; - }; - update_event_settings_email_event_settings_patch: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["EmailEventSettingsUpdateRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - reset_event_settings_email_event_settings_reset_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - }; - }; embeddings_embeddings_post: { parameters: { query?: never; @@ -46159,156 +45282,6 @@ export interface operations { }; }; }; - delete_project_project_delete_delete: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["DeleteProjectRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["LiteLLM_ProjectTable"][]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - project_info_project_info_get: { - parameters: { - query: { - project_id: string; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["LiteLLM_ProjectTable"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_projects_project_list_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["LiteLLM_ProjectTable"][]; - }; - }; - }; - }; - new_project_project_new_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["NewProjectRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["NewProjectResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - update_project_project_update_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdateProjectRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["LiteLLM_ProjectTable"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; create_prompt_prompts_post: { parameters: { query?: never; @@ -47221,26 +46194,6 @@ export interface operations { }; }; }; - get_robots_robots_txt_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - }; - }; get_router_fields_router_fields_get: { parameters: { query?: never; @@ -50847,26 +49800,6 @@ export interface operations { }; }; }; - available_enterprise_users_user_available_users_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - }; - }; bulk_user_update_user_bulk_update_post: { parameters: { query?: never; From 87137710c5fa3f8b6ec3b55e2d55fdb570d154fa Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 3 Jul 2026 19:44:41 +0000 Subject: [PATCH 5/5] fix: restore schema.d.ts from base branch Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ui/litellm-dashboard/src/lib/http/schema.d.ts | 1127 +++++++++++++++++ 1 file changed, 1127 insertions(+) diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 80bc26a810d..bfd1efc99f3 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -706,6 +706,60 @@ export interface paths { patch?: never; trace?: never; }; + "/audit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Audit Logs + * @description Get all audit logs with filtering and pagination. + * + * Returns a paginated response of audit logs matching the specified filters. + * + * Note: object_team_id and object_key_hash use Prisma JSON path filtering, + * which requires PostgreSQL. + */ + get: operations["get_audit_logs_audit_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/audit/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Audit Log By Id + * @description Get detailed information about a specific audit log entry by its ID. + * + * Args: + * id (str): The unique identifier of the audit log entry + * + * Returns: + * AuditLogResponse: Detailed information about the audit log entry + * + * Raises: + * HTTPException: If the audit log is not found or if there's a database connection error + */ + get: operations["get_audit_log_by_id_audit__id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/azure/{endpoint}": { parameters: { query?: never; @@ -3116,6 +3170,50 @@ export interface paths { patch?: never; trace?: never; }; + "/email/event_settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Email Event Settings + * @description Get all email event settings + */ + get: operations["get_email_event_settings_email_event_settings_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Update Event Settings + * @description Update the settings for email events + */ + patch: operations["update_event_settings_email_event_settings_patch"]; + trace?: never; + }; + "/email/event_settings/reset": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Reset Event Settings + * @description Reset all email event settings to default (new user invitations on, virtual key creation off) + */ + post: operations["reset_event_settings_email_event_settings_reset_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/embeddings": { parameters: { query?: never; @@ -6406,6 +6504,7 @@ export interface paths { * - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key. * - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false} * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget. + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[dict] - key-specific model rpm limit. Example - {"text-davinci-002": 1000, "gpt-3.5-turbo": 1000}. IF null or {} then no model specific rpm limit. * - model_tpm_limit: Optional[dict] - key-specific model tpm limit. Example - {"text-davinci-002": 1000, "gpt-3.5-turbo": 1000}. IF null or {} then no model specific tpm limit. * - mcp_rpm_limit: Optional[dict] - key-specific per-MCP-server rpm limit, keyed by MCP server name (alias if set, else the configured name). Example - {"github": 100, "slack": 200}. IF null or {} then no MCP-specific rpm limit. @@ -6612,6 +6711,7 @@ export interface paths { * - spend: Optional[float] - Amount spent by key * - max_budget: Optional[float] - Max budget for key * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}} + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - budget_duration: Optional[str] - Budget reset period ("30d", "1h", etc.) * - soft_budget: Optional[float] - Soft budget limit (warning vs. hard stop). Will trigger a slack alert when this soft budget is reached. * - max_parallel_requests: Optional[int] - Rate limit for parallel requests @@ -6687,6 +6787,7 @@ export interface paths { * - guardrails: Optional[List[str]] - List of active guardrails for the key * - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false} * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget. + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[dict] - key-specific model rpm limit. Example - {"text-davinci-002": 1000, "gpt-3.5-turbo": 1000}. IF null or {} then no model specific rpm limit. * - model_tpm_limit: Optional[dict] - key-specific model tpm limit. Example - {"text-davinci-002": 1000, "gpt-3.5-turbo": 1000}. IF null or {} then no model specific tpm limit. * - mcp_rpm_limit: Optional[dict] - key-specific per-MCP-server rpm limit, keyed by MCP server name (alias if set, else the configured name). Example - {"github": 100, "slack": 200}. IF null or {} then no MCP-specific rpm limit. @@ -6785,6 +6886,7 @@ export interface paths { * - spend: Optional[float] - Amount spent by key * - max_budget: Optional[float] - Max budget for key * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}} + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - budget_duration: Optional[str] - Budget reset period ("30d", "1h", etc.) * - soft_budget: Optional[float] - [TODO] Soft budget limit (warning vs. hard stop). Will trigger a slack alert when this soft budget is reached. * - max_parallel_requests: Optional[int] - Rate limit for parallel requests @@ -6866,6 +6968,7 @@ export interface paths { * - spend: Optional[float] - Amount spent by key * - max_budget: Optional[float] - Max budget for key * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}} + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - budget_duration: Optional[str] - Budget reset period ("30d", "1h", etc.) * - soft_budget: Optional[float] - Soft budget limit (warning vs. hard stop). Will trigger a slack alert when this soft budget is reached. * - max_parallel_requests: Optional[int] - Rate limit for parallel requests @@ -9866,6 +9969,240 @@ export interface paths { patch?: never; trace?: never; }; + "/project/delete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Delete Project + * @description Delete projects + * + * Parameters: + * - project_ids: *List[str]* - List of project ids to delete + * + * Example: + * ```bash + * curl --location --request DELETE 'http://0.0.0.0:4000/project/delete' \ + * --header 'Authorization: Bearer sk-1234' \ + * --header 'Content-Type: application/json' \ + * --data '{ + * "project_ids": ["project-123", "project-456"] + * }' + * ``` + */ + delete: operations["delete_project_project_delete_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/project/info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Info + * @description Get information about a specific project + * + * Parameters: + * - project_id: *str* - The project id to fetch info for + * + * Example: + * ```bash + * curl --location 'http://0.0.0.0:4000/project/info?project_id=project-123' \ + * --header 'Authorization: Bearer sk-1234' + * ``` + */ + get: operations["project_info_project_info_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/project/list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Projects + * @description List all projects that the user has access to + * + * Example: + * ```bash + * curl --location 'http://0.0.0.0:4000/project/list' \ + * --header 'Authorization: Bearer sk-1234' + * ``` + */ + get: operations["list_projects_project_list_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/project/new": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * New Project + * @description Create a new project. Projects sit between teams and keys in the hierarchy. + * + * Only admins or team admins can create projects. + * + * # Parameters + * + * - project_alias: *Optional[str]* - The name of the project. + * - description: *Optional[str]* - Description of the project's purpose and use case. + * - team_id: *str* - The team id that this project belongs to. Required. + * - models: *List* - The models the project has access to. + * - budget_id: *Optional[str]* - The id for a budget (tpm/rpm/max budget) for the project. + * ### IF NO BUDGET ID - CREATE ONE WITH THESE PARAMS ### + * - max_budget: *Optional[float]* - Max budget for project + * - tpm_limit: *Optional[int]* - Max tpm limit for project + * - rpm_limit: *Optional[int]* - Max rpm limit for project + * - max_parallel_requests: *Optional[int]* - Max parallel requests for project + * - soft_budget: *Optional[float]* - Get a slack alert when this soft budget is reached. Don't block requests. + * - model_max_budget: *Optional[dict]* - Max budget for a specific model. Example: {"gpt-4": 100.0, "gpt-3.5-turbo": 50.0} + * - model_rpm_limit: *Optional[dict]* - RPM limits per model. Example: {"gpt-4": 1000, "gpt-3.5-turbo": 5000} + * - model_tpm_limit: *Optional[dict]* - TPM limits per model. Example: {"gpt-4": 50000, "gpt-3.5-turbo": 100000} + * - budget_duration: *Optional[str]* - Frequency of reseting project budget + * - metadata: *Optional[dict]* - Metadata for project, store information for project. Example metadata - {"use_case_id": "SNOW-12345", "responsible_ai_id": "RAI-67890"} + * - tags: *Optional[list]* - Tags for the project. Example: ["production", "api"] + * - blocked: *bool* - Flag indicating if the project is blocked or not - will stop all calls from keys with this project_id. + * - object_permission: Optional[LiteLLM_ObjectPermissionBase] - project-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"]}. IF null or {} then no object permission. + * + * Example 1: Create new project **without** a budget_id, with model-specific limits + * + * ```bash + * curl --location 'http://0.0.0.0:4000/project/new' \ + * --header 'Authorization: Bearer sk-1234' \ + * --header 'Content-Type: application/json' \ + * --data '{ + * "project_alias": "flight-search-assistant", + * "description": "AI-powered flight search and booking assistant", + * "team_id": "team-123", + * "models": ["gpt-4", "gpt-3.5-turbo"], + * "max_budget": 100, + * "model_rpm_limit": { + * "gpt-4": 1000, + * "gpt-3.5-turbo": 5000 + * }, + * "model_tpm_limit": { + * "gpt-4": 50000, + * "gpt-3.5-turbo": 100000 + * }, + * "metadata": { + * "use_case_id": "SNOW-12345", + * "responsible_ai_id": "RAI-67890" + * } + * }' + * ``` + * + * Example 2: Create new project **with** a budget_id + * + * ```bash + * curl --location 'http://0.0.0.0:4000/project/new' \ + * --header 'Authorization: Bearer sk-1234' \ + * --header 'Content-Type: application/json' \ + * --data '{ + * "project_alias": "hotel-recommendations", + * "description": "Personalized hotel recommendation engine", + * "team_id": "team-123", + * "models": ["claude-3-sonnet"], + * "budget_id": "428eeaa8-f3ac-4e85-a8fb-7dc8d7aa8689", + * "metadata": { + * "use_case_id": "SNOW-54321" + * } + * }' + * ``` + */ + post: operations["new_project_project_new_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/project/update": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update Project + * @description Update a project + * + * Parameters: + * - project_id: *str* - The project id to update. Required. + * - project_alias: *Optional[str]* - Updated name for the project + * - description: *Optional[str]* - Updated description for the project + * - team_id: *Optional[str]* - Updated team_id for the project + * - metadata: *Optional[dict]* - Updated metadata for project + * - models: *Optional[list]* - Updated list of models for the project + * - blocked: *Optional[bool]* - Updated blocked status + * - max_budget: *Optional[float]* - Updated max budget + * - tpm_limit: *Optional[int]* - Updated tpm limit + * - rpm_limit: *Optional[int]* - Updated rpm limit + * - model_rpm_limit: *Optional[dict]* - Updated RPM limits per model + * - model_tpm_limit: *Optional[dict]* - Updated TPM limits per model + * - budget_duration: *Optional[str]* - Updated budget duration + * - tags: *Optional[list]* - Updated list of tags for the project + * - object_permission: Optional[LiteLLM_ObjectPermissionBase] - Updated object permission + * + * Example: + * ```bash + * curl --location 'http://0.0.0.0:4000/project/update' \ + * --header 'Authorization: Bearer sk-1234' \ + * --header 'Content-Type: application/json' \ + * --data '{ + * "project_id": "project-123", + * "description": "Updated flight search system with enhanced capabilities", + * "max_budget": 200, + * "model_rpm_limit": { + * "gpt-4": 2000, + * "gpt-3.5-turbo": 10000 + * }, + * "metadata": { + * "use_case_id": "SNOW-12345", + * "status": "active" + * } + * }' + * ``` + */ + post: operations["update_project_project_update_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/prompts": { parameters: { query?: never; @@ -10908,6 +11245,27 @@ export interface paths { patch?: never; trace?: never; }; + "/robots.txt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Robots + * @description Block all web crawlers from indexing the proxy server endpoints + * This is useful for ensuring that the API endpoints aren't indexed by search engines + */ + get: operations["get_robots_robots_txt_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/router/fields": { parameters: { query?: never; @@ -13947,6 +14305,26 @@ export interface paths { patch?: never; trace?: never; }; + "/user/available_users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Available Enterprise Users + * @description For keys with `max_users` set, return the list of users that are allowed to use the key. + */ + get: operations["available_enterprise_users_user_available_users_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/user/bulk_update": { parameters: { query?: never; @@ -14240,6 +14618,7 @@ export interface paths { * - max_parallel_requests: Optional[int] - Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. * - soft_budget: Optional[float] - Get alerts when user crosses given budget, doesn't block requests. * - model_max_budget: Optional[dict] - Model-specific max budget for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-budgets-to-keys) + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[float] - Model-specific rpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) * - mcp_rpm_limit: Optional[dict] - Per-MCP-server rpm limit, keyed by MCP server name {"github": 100, "slack": 200}. Enforced for keys and teams only; values set on a user are stored but not enforced per user. * - model_tpm_limit: Optional[float] - Model-specific tpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) @@ -14320,6 +14699,7 @@ export interface paths { * - max_parallel_requests: Optional[int] - Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. * - soft_budget: Optional[float] - Get alerts when user crosses given budget, doesn't block requests. * - model_max_budget: Optional[dict] - Model-specific max budget for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-budgets-to-keys) + * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[float] - Model-specific rpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) * - mcp_rpm_limit: Optional[dict] - Per-MCP-server rpm limit, keyed by MCP server name {"github": 100, "slack": 200}. Enforced for keys and teams only; values set on a user are stored but not enforced per user. * - model_tpm_limit: Optional[float] - Model-specific tpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) @@ -20227,6 +20607,37 @@ export interface components { */ unnamed_teams_count: number; }; + /** + * AuditLogResponse + * @description Response model for a single audit log entry + */ + AuditLogResponse: { + /** Action */ + action: string; + /** Before Value */ + before_value?: { + [key: string]: unknown; + } | null; + /** Changed By */ + changed_by: string; + /** Changed By Api Key */ + changed_by_api_key: string; + /** Id */ + id: string; + /** Object Id */ + object_id: string; + /** Table Name */ + table_name: string; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + /** Updated Values */ + updated_values?: { + [key: string]: unknown; + } | null; + }; /** BaseLitellmParams */ "BaseLitellmParams-Input": { /** @@ -22716,6 +23127,14 @@ export interface components { /** Organization Ids */ organization_ids: string[]; }; + /** + * DeleteProjectRequest + * @description Request model for DELETE /project/delete + */ + DeleteProjectRequest: { + /** Project Ids */ + project_ids: string[]; + }; /** * DeleteSkillResponse * @description Response from deleting a skill @@ -22830,6 +23249,27 @@ export interface components { /** Write Capacity Units */ write_capacity_units?: number | null; }; + /** + * EmailEvent + * @enum {string} + */ + EmailEvent: "Virtual Key Created" | "New User Invitation" | "Virtual Key Rotated" | "Soft Budget Crossed" | "Max Budget Alert"; + /** EmailEventSettings */ + EmailEventSettings: { + /** Enabled */ + enabled: boolean; + event: components["schemas"]["EmailEvent"]; + }; + /** EmailEventSettingsResponse */ + EmailEventSettingsResponse: { + /** Settings */ + settings: components["schemas"]["EmailEventSettings"][]; + }; + /** EmailEventSettingsUpdateRequest */ + EmailEventSettingsUpdateRequest: { + /** Settings */ + settings: components["schemas"]["EmailEventSettings"][]; + }; /** EmbeddingRequest */ EmbeddingRequest: { /** @@ -23146,6 +23586,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -23286,6 +23730,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -24269,6 +24717,13 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** + * Budget Fallbacks + * @default {} + */ + budget_fallbacks: { + [key: string]: string[]; + }; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -25123,6 +25578,65 @@ export interface components { } & { [key: string]: unknown; }; + /** + * LiteLLM_ProjectTable + * @description Database model representation for project + */ + LiteLLM_ProjectTable: { + /** + * Blocked + * @default false + */ + blocked: boolean; + /** Budget Id */ + budget_id?: string | null; + /** Created At */ + created_at?: string | null; + /** Created By */ + created_by?: string | null; + /** Description */ + description?: string | null; + litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null; + /** Metadata */ + metadata?: { + [key: string]: unknown; + } | null; + /** Model Rpm Limit */ + model_rpm_limit?: { + [key: string]: unknown; + } | null; + /** Model Spend */ + model_spend?: { + [key: string]: unknown; + } | null; + /** Model Tpm Limit */ + model_tpm_limit?: { + [key: string]: unknown; + } | null; + /** + * Models + * @default [] + */ + models: string[]; + object_permission?: components["schemas"]["LiteLLM_ObjectPermissionTable"] | null; + /** Object Permission Id */ + object_permission_id?: string | null; + /** Project Alias */ + project_alias?: string | null; + /** Project Id */ + project_id: string; + /** + * Spend + * @default 0 + */ + spend: number; + /** Team Id */ + team_id?: string | null; + /** Updated At */ + updated_at?: string | null; + /** Updated By */ + updated_by?: string | null; + }; /** LiteLLM_ProxyModelTable */ LiteLLM_ProxyModelTable: { /** @@ -25595,6 +26109,13 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** + * Budget Fallbacks + * @default {} + */ + budget_fallbacks: { + [key: string]: string[]; + }; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -27133,6 +27654,134 @@ export interface components { /** Users */ users?: components["schemas"]["LiteLLM_UserTable"][] | null; }; + /** + * NewProjectRequest + * @description Request model for POST /project/new + */ + NewProjectRequest: { + /** Allowed Models */ + allowed_models?: string[] | null; + /** + * Blocked + * @default false + */ + blocked: boolean; + /** Budget Duration */ + budget_duration?: string | null; + /** Budget Id */ + budget_id?: string | null; + /** Description */ + description?: string | null; + /** Guardrails */ + guardrails?: string[] | null; + /** Max Budget */ + max_budget?: number | null; + /** Max Parallel Requests */ + max_parallel_requests?: number | null; + /** Metadata */ + metadata?: { + [key: string]: unknown; + } | null; + /** Model Max Budget */ + model_max_budget?: { + [key: string]: unknown; + } | null; + /** Model Rpm Limit */ + model_rpm_limit?: { + [key: string]: unknown; + } | null; + /** Model Tpm Limit */ + model_tpm_limit?: { + [key: string]: unknown; + } | null; + /** + * Models + * @default [] + */ + models: string[]; + object_permission?: components["schemas"]["LiteLLM_ObjectPermissionBase"] | null; + /** Policies */ + policies?: string[] | null; + /** Project Alias */ + project_alias?: string | null; + /** Project Id */ + project_id?: string | null; + /** Rpm Limit */ + rpm_limit?: number | null; + /** Soft Budget */ + soft_budget?: number | null; + /** Tags */ + tags?: string[] | null; + /** Team Id */ + team_id: string; + /** Tpm Limit */ + tpm_limit?: number | null; + }; + /** + * NewProjectResponse + * @description Response model for POST /project/new + */ + NewProjectResponse: { + /** + * Blocked + * @default false + */ + blocked: boolean; + /** Budget Id */ + budget_id?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Created By */ + created_by?: string | null; + /** Description */ + description?: string | null; + litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null; + /** Metadata */ + metadata?: { + [key: string]: unknown; + } | null; + /** Model Rpm Limit */ + model_rpm_limit?: { + [key: string]: unknown; + } | null; + /** Model Spend */ + model_spend?: { + [key: string]: unknown; + } | null; + /** Model Tpm Limit */ + model_tpm_limit?: { + [key: string]: unknown; + } | null; + /** + * Models + * @default [] + */ + models: string[]; + object_permission?: components["schemas"]["LiteLLM_ObjectPermissionTable"] | null; + /** Object Permission Id */ + object_permission_id?: string | null; + /** Project Alias */ + project_alias?: string | null; + /** Project Id */ + project_id: string; + /** + * Spend + * @default 0 + */ + spend: number; + /** Team Id */ + team_id?: string | null; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + /** Updated By */ + updated_by?: string | null; + }; /** NewTeamRequest */ NewTeamRequest: { /** Access Group Ids */ @@ -27275,6 +27924,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Limits */ budget_limits?: components["schemas"]["BudgetLimitEntry"][] | null; /** @@ -27409,6 +28062,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -27642,6 +28299,34 @@ export interface components { /** Organizations */ organizations: string[]; }; + /** + * PaginatedAuditLogResponse + * @description Response model for paginated audit logs + */ + PaginatedAuditLogResponse: { + /** Audit Logs */ + audit_logs: components["schemas"]["AuditLogResponse"][]; + /** + * Page + * @description Current page number + */ + page: number; + /** + * Page Size + * @description Number of items per page + */ + page_size: number; + /** + * Total + * @description Total number of audit logs matching the filters + */ + total: number; + /** + * Total Pages + * @description Total number of pages + */ + total_pages: number; + }; /** PassThroughEndpointResponse */ PassThroughEndpointResponse: { /** Endpoints */ @@ -28999,6 +29684,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -30958,6 +31647,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -31152,6 +31845,63 @@ export interface components { /** Model Names */ model_names?: string[] | null; }; + /** + * UpdateProjectRequest + * @description Request model for POST /project/update + */ + UpdateProjectRequest: { + /** Allowed Models */ + allowed_models?: string[] | null; + /** Blocked */ + blocked?: boolean | null; + /** Budget Duration */ + budget_duration?: string | null; + /** Budget Id */ + budget_id?: string | null; + /** Description */ + description?: string | null; + /** Guardrails */ + guardrails?: string[] | null; + /** Max Budget */ + max_budget?: number | null; + /** Max Parallel Requests */ + max_parallel_requests?: number | null; + /** Metadata */ + metadata?: { + [key: string]: unknown; + } | null; + /** Model Max Budget */ + model_max_budget?: { + [key: string]: unknown; + } | null; + /** Model Rpm Limit */ + model_rpm_limit?: { + [key: string]: unknown; + } | null; + /** Model Tpm Limit */ + model_tpm_limit?: { + [key: string]: unknown; + } | null; + /** Models */ + models?: string[] | null; + object_permission?: components["schemas"]["LiteLLM_ObjectPermissionBase"] | null; + /** Policies */ + policies?: string[] | null; + /** Project Alias */ + project_alias?: string | null; + /** Project Id */ + project_id: string; + /** Rpm Limit */ + rpm_limit?: number | null; + /** Soft Budget */ + soft_budget?: number | null; + /** Tags */ + tags?: string[] | null; + /** Team Id */ + team_id?: string | null; + /** Tpm Limit */ + tpm_limit?: number | null; + }; /** * UpdatePublicModelGroupsRequest * @description Request model for updating public model groups @@ -31364,6 +32114,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Limits */ budget_limits?: components["schemas"]["BudgetLimitEntry"][] | null; /** @@ -31462,6 +32216,10 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** Budget Fallbacks */ + budget_fallbacks?: { + [key: string]: string[]; + } | null; /** Budget Limits */ budget_limits?: components["schemas"]["BudgetLimitEntry"][] | null; /** @@ -31689,6 +32447,13 @@ export interface components { blocked?: boolean | null; /** Budget Duration */ budget_duration?: string | null; + /** + * Budget Fallbacks + * @default {} + */ + budget_fallbacks: { + [key: string]: string[]; + }; /** Budget Id */ budget_id?: string | null; /** Budget Limits */ @@ -33595,6 +34360,105 @@ export interface operations { }; }; }; + get_audit_logs_audit_get: { + parameters: { + query?: { + page?: number; + page_size?: number; + /** @description Filter by user or system that performed the action */ + changed_by?: string | null; + /** @description Filter by API key hash that performed the action */ + changed_by_api_key?: string | null; + /** @description Filter by action type (create, update, delete) */ + action?: string | null; + /** @description Filter by table name that was modified */ + table_name?: string | null; + /** @description Filter by ID of the object that was modified */ + object_id?: string | null; + /** @description Filter logs after this date */ + start_date?: string | null; + /** @description Filter logs before this date */ + end_date?: string | null; + /** @description Filter by team_id present in before_value or updated_values JSON (PostgreSQL only) */ + object_team_id?: string | null; + /** @description Filter by token (key hash) present in before_value or updated_values JSON (PostgreSQL only) */ + object_key_hash?: string | null; + /** @description Column to sort by (e.g. 'updated_at', 'action', 'table_name') */ + sort_by?: string | null; + /** @description Sort order ('asc' or 'desc') */ + sort_order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PaginatedAuditLogResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_audit_log_by_id_audit__id__get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuditLogResponse"]; + }; + }; + /** @description Audit log not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + /** @description Database connection error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; azure_proxy_route_azure__endpoint__get: { parameters: { query?: never; @@ -37120,6 +37984,79 @@ export interface operations { }; }; }; + get_email_event_settings_email_event_settings_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EmailEventSettingsResponse"]; + }; + }; + }; + }; + update_event_settings_email_event_settings_patch: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EmailEventSettingsUpdateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + reset_event_settings_email_event_settings_reset_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; embeddings_embeddings_post: { parameters: { query?: never; @@ -45282,6 +46219,156 @@ export interface operations { }; }; }; + delete_project_project_delete_delete: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DeleteProjectRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LiteLLM_ProjectTable"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_info_project_info_get: { + parameters: { + query: { + project_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LiteLLM_ProjectTable"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_projects_project_list_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LiteLLM_ProjectTable"][]; + }; + }; + }; + }; + new_project_project_new_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewProjectRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NewProjectResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_project_project_update_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateProjectRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LiteLLM_ProjectTable"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; create_prompt_prompts_post: { parameters: { query?: never; @@ -46194,6 +47281,26 @@ export interface operations { }; }; }; + get_robots_robots_txt_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; get_router_fields_router_fields_get: { parameters: { query?: never; @@ -49800,6 +50907,26 @@ export interface operations { }; }; }; + available_enterprise_users_user_available_users_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; bulk_user_update_user_bulk_update_post: { parameters: { query?: never;