fix(proxy): cleandoc exception docstrings used in OpenAPI ERROR_RESPONSES

The Swagger error descriptions wired up in swagger_utils.ERROR_RESPONSES
were taken straight from exception.__doc__, which preserves the leading
whitespace from the Python source (4 spaces per body line for any
multi-line docstring). openapi-typescript renders that into JSDoc
verbatim, so when RateLimitError grew a real multi-line docstring in
#27687, every regenerated schema.d.ts now has a different indentation
than the file committed by #29885. CI's Check UI API Types Sync flips
back and forth between rebases on any PR that doesn't touch
schema.d.ts itself.

Pass __doc__ through inspect.cleandoc so the description stored in the
OpenAPI spec is dedented at the source. The committed schema.d.ts is
exactly what gen:api now produces, so no schema regen is needed in this
commit; future docstrings on litellm exceptions won't reintroduce the
drift either. Inherited docstrings from openai/Exception are still
ignored (we keep the existing __doc__-not-getdoc fallback to the class
name) so the description of every other 4xx/5xx response stays the
class name like 'AuthenticationError'.

Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-06-08 14:30:57 +00:00
parent aaf1e2444b
commit fef0f219c2
No known key found for this signature in database
2 changed files with 50 additions and 2 deletions

View file

@ -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
}

View file

@ -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