From 2ab2cdd87ac19792bb06075187b4467069d389aa Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 20 Sep 2026 01:26:04 +0000 Subject: [PATCH] refactor(errors): scope bug report link to unmapped exceptions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/litellm_core_utils/bug_report.py | 41 ++++++------------- .../exception_mapping_utils.py | 24 ++--------- .../litellm_core_utils/test_bug_report.py | 3 +- .../test_exception_mapping_utils.py | 26 ------------ 4 files changed, 18 insertions(+), 76 deletions(-) diff --git a/litellm/litellm_core_utils/bug_report.py b/litellm/litellm_core_utils/bug_report.py index a44df268339..9aedc3fa30b 100644 --- a/litellm/litellm_core_utils/bug_report.py +++ b/litellm/litellm_core_utils/bug_report.py @@ -17,6 +17,7 @@ MAX_URL_LENGTH: Final = 6000 MAX_MESSAGE_CHARS: Final = 600 MAX_FRAMES: Final = 12 DISABLE_ENV_VAR: Final = "LITELLM_DISABLE_BUG_REPORT_LINK" +_SHORTENED_MESSAGE_LENGTHS: Final = (480, 360, 240, 120, 0) Surface = Literal["sdk", "proxy"] @@ -51,7 +52,7 @@ def _format_frame(frame: traceback.FrameSummary, package_dir: Path, package_pare def _get_litellm_frames(exc: BaseException) -> tuple[str, ...]: package_file: Final = getattr(litellm, "__file__", None) - if package_file is None or exc.__traceback__ is None: + if not isinstance(package_file, str) or exc.__traceback__ is None: return () package_dir: Final = Path(package_file).resolve().parent package_parent: Final = package_dir.parent @@ -116,7 +117,7 @@ def _description(report: BugReport, message: str, frames: tuple[str, ...]) -> st def _issue_url(report: BugReport, message: str, frames: tuple[str, ...]) -> str: - deployment: tuple[tuple[str, str], ...] = ( + deployment: Final[tuple[tuple[str, str], ...]] = ( (("deployment", "pip / Python SDK"),) if report.surface == "sdk" else (("deployment", "Docker"),) @@ -135,33 +136,17 @@ def _issue_url(report: BugReport, message: str, frames: tuple[str, ...]) -> str: def bug_report_issue_url(report: BugReport) -> str: - frame_candidates: Final = tuple(report.litellm_frames[index:] for index in range(len(report.litellm_frames) + 1)) - message_lengths: Final = ( - len(report.exception_message), - 480, - 360, - 240, - 120, - 0, + frames: Final = report.litellm_frames + message: Final = report.exception_message + candidates: Final = ( + *(_issue_url(report, message, frames[index:]) for index in range(len(frames) + 1)), + *( + _issue_url(report, message[:length], ()) + for length in _SHORTENED_MESSAGE_LENGTHS + if length < len(message) + ), ) - frame_candidates_with_full_message: Final = ( - _issue_url(report, report.exception_message[:message_length], frames) - for frames in frame_candidates - for message_length in (len(report.exception_message),) - ) - shortest_candidate: Final = _issue_url(report, "", ()) - full_message_candidate: Final = next( - (candidate for candidate in frame_candidates_with_full_message if len(candidate) <= MAX_URL_LENGTH), - None, - ) - if full_message_candidate is not None: - return full_message_candidate - shortened_candidates: Final = ( - _issue_url(report, report.exception_message[:message_length], ()) - for message_length in message_lengths - if message_length <= len(report.exception_message) - ) - return next((candidate for candidate in shortened_candidates if len(candidate) <= MAX_URL_LENGTH), shortest_candidate) + return next((candidate for candidate in candidates if len(candidate) <= MAX_URL_LENGTH), _issue_url(report, "", ())) def bug_report_notice(report: BugReport) -> str: diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 6ce64f856e9..022873214a8 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -515,24 +515,8 @@ def _map_openai_exception( else: # if no status code then it is an APIConnectionError: https://github.com/openai/openai-python#handling-errors # exception_mapping_worked = True - bug_report_message: Final = ( - f"{exception_provider} - {message}" - + ( - "\n" - + bug_report_notice( - build_bug_report( - original_exception, - surface="sdk", - model=model, - custom_llm_provider=custom_llm_provider, - ) - ) - if not hasattr(original_exception, "request") and bug_report_enabled() - else "" - ) - ) raise APIConnectionError( - message=f"APIConnectionError: {bug_report_message}", + message=f"APIConnectionError: {exception_provider} - {message}", llm_provider=custom_llm_provider, model=model, litellm_debug_info=extra_information, @@ -2702,11 +2686,11 @@ def exception_type( build_bug_report( original_exception, surface="sdk", - model=model, - custom_llm_provider=custom_llm_provider, + model=cast(str | None, model), + custom_llm_provider=cast(str | None, custom_llm_provider), ) ) - if bug_report_enabled() + if bug_report_enabled() and isinstance(original_exception, BaseException) else "" ) ), diff --git a/tests/test_litellm/litellm_core_utils/test_bug_report.py b/tests/test_litellm/litellm_core_utils/test_bug_report.py index 1c693b9a4cb..18c36308473 100644 --- a/tests/test_litellm/litellm_core_utils/test_bug_report.py +++ b/tests/test_litellm/litellm_core_utils/test_bug_report.py @@ -11,10 +11,9 @@ from litellm.litellm_core_utils.bug_report import ( DISABLE_ENV_VAR, ISSUE_URL_BASE, MAX_URL_LENGTH, - BugReport, - build_bug_report, bug_report_enabled, bug_report_issue_url, + build_bug_report, ) from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider diff --git a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py index 6045e584737..9b2568e390f 100644 --- a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py @@ -3,8 +3,6 @@ import openai import pytest import litellm - -from litellm.litellm_core_utils.bug_report import DISABLE_ENV_VAR, ISSUE_URL_BASE from litellm.litellm_core_utils.exception_mapping_utils import ( ExceptionCheckers, _get_body_error_code, @@ -974,30 +972,6 @@ def test_an_unmapped_exception_with_no_model_or_provider_is_a_connection_error(q assert "boom" in raised.value.message -def test_unmapped_sdk_exception_includes_bug_report_link(quiet_exception_mapping): - with pytest.raises(litellm.APIConnectionError) as raised: - exception_type( - model="gpt-4", - original_exception=ValueError("boom"), - custom_llm_provider="openai", - ) - - assert ISSUE_URL_BASE in str(raised.value) - - -def test_unmapped_sdk_exception_bug_report_link_can_be_disabled(quiet_exception_mapping, monkeypatch): - monkeypatch.setenv(DISABLE_ENV_VAR, "true") - - with pytest.raises(litellm.APIConnectionError) as raised: - exception_type( - model="gpt-4", - original_exception=ValueError("boom"), - custom_llm_provider="openai", - ) - - assert ISSUE_URL_BASE not in str(raised.value) - - def _raise_and_map(model: str | None, original_exception: Exception, custom_llm_provider: str | None) -> None: """Calls exception_type() from inside the except block, as litellm/main.py does, so traceback.format_exc() has a real stack."""