diff --git a/litellm/proxy/common_utils/swagger_utils.py b/litellm/proxy/common_utils/swagger_utils.py index 75a64707cd4..05f5746bea3 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, Dict from pydantic import BaseModel, Field @@ -31,11 +32,25 @@ def get_status_code(exception): return 500 # Internal Server Error as default -# Create error responses +def _exception_description(exception): + """Return a normalized description for OpenAPI / JSDoc consumers. + + Uses the class's own docstring (not an inherited one) so the rendered Swagger + description matches the historical short-form (the class name) when only an + upstream library defined a docstring. ``cleandoc`` strips the source-code + indentation that would otherwise leak into the generated JSDoc comment in + ``ui/litellm-dashboard/src/lib/http/schema.d.ts``. + """ + doc = exception.__doc__ + if not doc: + return exception.__name__ + return inspect.cleandoc(doc) + + ERROR_RESPONSES = { get_status_code(exception): { "model": ErrorResponse, - "description": exception.__doc__ or exception.__name__, + "description": _exception_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..8f0a11ad15e --- /dev/null +++ b/tests/test_litellm/proxy/common_utils/test_swagger_utils.py @@ -0,0 +1,33 @@ +import inspect + +from litellm.exceptions import RateLimitError +from litellm.proxy.common_utils.swagger_utils import ( + ERROR_RESPONSES, + _exception_description, +) + + +class _ChildWithoutDoc(RateLimitError): + pass + + +def test_exception_description_dedents_multiline_docstring(): + description = _exception_description(RateLimitError) + + assert RateLimitError.__doc__ is not None + assert RateLimitError.__doc__.startswith("\n ") + assert description == inspect.cleandoc(RateLimitError.__doc__) + assert "\n " not in description + + +def test_exception_description_falls_back_to_name_when_no_own_doc(): + assert _ChildWithoutDoc.__doc__ is None + assert _exception_description(_ChildWithoutDoc) == "_ChildWithoutDoc" + + +def test_rate_limit_error_response_description_is_dedented(): + rate_limit_response = ERROR_RESPONSES[429] + + description = rate_limit_response["description"] + assert description.startswith("Unified rate-limit error.") + assert "\n " not in description