From 569ccaece9328646535e5ab6ac163e530665c771 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:21:20 -0700 Subject: [PATCH] fix(proxy): make the lazy OpenAPI snapshot byte-identical on every Python version (#42519) Python 3.13+ strips the common indentation of docstrings at compile time and 3.12 keeps it, and the 429 error description in ERROR_RESPONSES came straight from RateLimitError.__doc__, so regenerating litellm/proxy/_lazy_openapi_snapshot.json on a 3.13+ venv produced a one-line diff that the check-ui-api-types job (Python 3.12) rejected. Run the docstring through inspect.cleandoc before it lands in the spec, regenerate the snapshot once, and pin the behavior with a test Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com> --- litellm/proxy/_lazy_openapi_snapshot.json | 2 +- litellm/proxy/common_utils/swagger_utils.py | 7 ++++++- .../proxy/common_utils/test_swagger_utils.py | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/test_litellm/proxy/common_utils/test_swagger_utils.py diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 4dbca14b917..87f28624d58 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -19919,7 +19919,7 @@ } } }, - "description": "\n Unified rate-limit error.\n\n Every rate-limit condition surfaced by litellm \u2014 whether it originated from\n an upstream LLM provider, a vendor batch endpoint, or one of litellm's own\n proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\n max-iterations, etc.) \u2014 is raised as an instance of this class.\n\n The :attr:`category` attribute lets callers distinguish the source. See\n :class:`RateLimitErrorCategory` for the available values.\n " + "description": "Unified rate-limit error.\n\nEvery rate-limit condition surfaced by litellm \u2014 whether it originated from\nan upstream LLM provider, a vendor batch endpoint, or one of litellm's own\nproxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\nmax-iterations, etc.) \u2014 is raised as an instance of this class.\n\nThe :attr:`category` attribute lets callers distinguish the source. See\n:class:`RateLimitErrorCategory` for the available values." }, "500": { "content": { diff --git a/litellm/proxy/common_utils/swagger_utils.py b/litellm/proxy/common_utils/swagger_utils.py index 2609a98a997..83480bf161d 100644 --- a/litellm/proxy/common_utils/swagger_utils.py +++ b/litellm/proxy/common_utils/swagger_utils.py @@ -1,3 +1,4 @@ +import inspect from typing import Any, Final from pydantic import BaseModel, Field @@ -31,11 +32,15 @@ def get_status_code(exception): return 500 # Internal Server Error as default +def _error_description(exception: type[Exception]) -> str: + return inspect.cleandoc(exception.__doc__) if exception.__doc__ else exception.__name__ + + # Create error responses ERROR_RESPONSES: Final = { get_status_code(exception): { "model": ErrorResponse, - "description": exception.__doc__ or exception.__name__, + "description": _error_description(exception), } for exception in LITELLM_EXCEPTION_TYPES } diff --git a/tests/test_litellm/proxy/common_utils/test_swagger_utils.py b/tests/test_litellm/proxy/common_utils/test_swagger_utils.py new file mode 100644 index 00000000000..659d2ad2941 --- /dev/null +++ b/tests/test_litellm/proxy/common_utils/test_swagger_utils.py @@ -0,0 +1,18 @@ +import inspect + +from litellm.exceptions import RateLimitError +from litellm.proxy.common_utils.swagger_utils import ERROR_RESPONSES, _error_description + + +class _ChildWithoutDoc(RateLimitError): + pass + + +def test_error_response_descriptions_carry_no_docstring_indentation(): + assert ERROR_RESPONSES[429]["description"] == inspect.cleandoc(RateLimitError.__doc__ or "") + for response in ERROR_RESPONSES.values(): + assert response["description"] == inspect.cleandoc(response["description"]) + + +def test_error_description_falls_back_to_the_class_name_without_an_own_docstring(): + assert _error_description(_ChildWithoutDoc) == "_ChildWithoutDoc"