From 33ff50eb114b5d3d8a9e3cc11f2f2ca210875e5e Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 21 Sep 2026 17:51:41 +0000 Subject: [PATCH] fix(errors): only link bug reports for exceptions without a provider status Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/litellm_core_utils/bug_report.py | 4 +++ .../exception_mapping_utils.py | 4 +-- litellm/proxy/common_request_processing.py | 4 +-- litellm/proxy/proxy_server.py | 4 +-- litellm/proxy/utils.py | 4 +-- .../litellm_core_utils/test_bug_report.py | 27 ++++++++++++++++++- tests/test_litellm/proxy/test_proxy_utils.py | 17 +++++++++++- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/litellm/litellm_core_utils/bug_report.py b/litellm/litellm_core_utils/bug_report.py index 76d758c18d3..352a21ae067 100644 --- a/litellm/litellm_core_utils/bug_report.py +++ b/litellm/litellm_core_utils/bug_report.py @@ -43,6 +43,10 @@ def bug_report_enabled() -> bool: return os.getenv(DISABLE_ENV_VAR, "").lower() != "true" +def should_report_bug(exc: object) -> bool: + return bug_report_enabled() and isinstance(exc, BaseException) and getattr(exc, "status_code", None) is None + + def _format_frame(frame: traceback.FrameSummary, package_dir: Path, package_parent: Path) -> str | None: frame_path: Final = Path(frame.filename).resolve() try: diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 022873214a8..78b481a4d00 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -11,9 +11,9 @@ import httpx import litellm from litellm._logging import _ENABLE_SECRET_REDACTION, _redact_string, verbose_logger from litellm.litellm_core_utils.bug_report import ( - bug_report_enabled, bug_report_notice, build_bug_report, + should_report_bug, ) from litellm.litellm_core_utils.secret_redaction import redact_string from litellm.types.utils import LlmProviders @@ -2690,7 +2690,7 @@ def exception_type( custom_llm_provider=cast(str | None, custom_llm_provider), ) ) - if bug_report_enabled() and isinstance(original_exception, BaseException) + if should_report_bug(original_exception) else "" ) ), diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index ed867d900ea..0ef9af114e7 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -47,9 +47,9 @@ from litellm.constants import ( ) from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.litellm_core_utils.bug_report import ( - bug_report_enabled, bug_report_notice, build_bug_report, + should_report_bug, strip_bug_report_notice, ) from litellm.litellm_core_utils.core_helpers import ( @@ -3665,7 +3665,7 @@ class ProxyBaseLLMRequestProcessing: _code = _exc_status_code else: _code = status.HTTP_500_INTERNAL_SERVER_ERROR - if bug_report_enabled(): + if should_report_bug(e): proxy_server_request: Final = self.data.get("proxy_server_request") request_url: Final = ( proxy_server_request.get("url") if isinstance(proxy_server_request, Mapping) else None diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index b9becdf746c..c6f6c171615 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -75,9 +75,9 @@ from litellm.constants import ( ) from litellm.litellm_core_utils.asyncify import asyncify from litellm.litellm_core_utils.bug_report import ( - bug_report_enabled, bug_report_notice, build_bug_report, + should_report_bug, ) from litellm.litellm_core_utils.litellm_logging import ( _init_custom_logger_compatible_class, @@ -1875,7 +1875,7 @@ async def otel_unhandled_exception_handler(request: Request, exc: Exception): if isinstance(exc, (ProxyException, HTTPException, RequestValidationError)): raise exc verbose_proxy_logger.exception("Unhandled exception in request: %s", type(exc).__name__) - if bug_report_enabled(): + if should_report_bug(exc): verbose_proxy_logger.error( bug_report_notice(build_bug_report(exc, surface="proxy", call_type=request.url.path)) ) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 3216c370126..6e95463ab99 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -56,9 +56,9 @@ from litellm.constants import ( SPEND_LOG_WRITE_BATCH_MAX_ROWS, ) from litellm.litellm_core_utils.bug_report import ( - bug_report_enabled, bug_report_notice, build_bug_report, + should_report_bug, strip_bug_report_notice, ) from litellm.proxy._types import ( @@ -7842,7 +7842,7 @@ def handle_exception_on_proxy(e: Exception, litellm_call_id: str | None = None) elif isinstance(e, ProxyException): return with_litellm_call_id(e, litellm_call_id) _status_code: Final = getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR) - if _status_code == status.HTTP_500_INTERNAL_SERVER_ERROR and bug_report_enabled() and isinstance(e, BaseException): + if should_report_bug(e): verbose_proxy_logger.error(bug_report_notice(build_bug_report(e, surface="proxy"))) return ProxyException( message=strip_bug_report_notice(str(e)), 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 7746e5688d3..7bcc6aca8cc 100644 --- a/tests/test_litellm/litellm_core_utils/test_bug_report.py +++ b/tests/test_litellm/litellm_core_utils/test_bug_report.py @@ -3,10 +3,11 @@ from __future__ import annotations from typing import cast from urllib.parse import parse_qs, urlparse +import httpx import pytest from litellm._version import version -from litellm.exceptions import BadRequestError +from litellm.exceptions import APIConnectionError, BadRequestError, InternalServerError from litellm.litellm_core_utils.bug_report import ( DISABLE_ENV_VAR, ISSUE_URL_BASE, @@ -15,6 +16,7 @@ from litellm.litellm_core_utils.bug_report import ( bug_report_issue_url, bug_report_notice, build_bug_report, + should_report_bug, strip_bug_report_notice, ) from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider @@ -81,6 +83,29 @@ def test_bug_report_can_be_disabled(monkeypatch: pytest.MonkeyPatch): monkeypatch.setenv(DISABLE_ENV_VAR, "true") assert bug_report_enabled() is False + assert should_report_bug(RuntimeError("boom")) is False + + +@pytest.mark.parametrize( + "exc", + [ + InternalServerError(message="upstream 500", llm_provider="openai", model="gpt-4"), + APIConnectionError( + message="connection reset", + llm_provider="openai", + model="gpt-4", + request=httpx.Request(method="POST", url="https://api.openai.com/v1/"), + ), + BadRequestError(message="bad input", llm_provider="openai", model="gpt-4"), + "not an exception", + ], +) +def test_should_report_bug_skips_provider_and_network_errors(exc: object): + assert should_report_bug(exc) is False + + +def test_should_report_bug_accepts_plain_python_errors(): + assert should_report_bug(KeyError("missing")) is True def test_proxy_provider_uses_translation_domain(): diff --git a/tests/test_litellm/proxy/test_proxy_utils.py b/tests/test_litellm/proxy/test_proxy_utils.py index 734408d9b61..0fc7295a717 100644 --- a/tests/test_litellm/proxy/test_proxy_utils.py +++ b/tests/test_litellm/proxy/test_proxy_utils.py @@ -6,10 +6,12 @@ import pytest from fastapi import HTTPException from litellm.caching.caching import DualCache +from litellm.exceptions import InternalServerError from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.integrations.custom_logger import CustomLogger +from litellm.litellm_core_utils.bug_report import ISSUE_URL_BASE from litellm.proxy._types import ProxyErrorTypes, UserAPIKeyAuth -from litellm.proxy.utils import PrismaClient, ProxyLogging +from litellm.proxy.utils import PrismaClient, ProxyLogging, handle_exception_on_proxy from litellm.types.guardrails import GuardrailEventHooks @@ -2418,3 +2420,16 @@ def test_mcp_auth_policy_uses_original_request_model(monkeypatch, model, expecte synthetic = proxy_logging._convert_mcp_to_llm_format(proxy_logging._create_mcp_request_object_from_kwargs(kwargs), kwargs) assert ("model-rule" in synthetic["metadata"]["guardrails"]) is expected assert "request-rule" in synthetic["metadata"]["guardrails"] + + +def test_handle_exception_on_proxy_logs_bug_report_only_for_unmapped_500(caplog): + with caplog.at_level("ERROR", logger="LiteLLM Proxy"): + provider_result = handle_exception_on_proxy( + InternalServerError(message="upstream 500", llm_provider="openai", model="gpt-4") + ) + assert ISSUE_URL_BASE not in caplog.text + internal_result = handle_exception_on_proxy(KeyError("missing")) + + assert provider_result.code == internal_result.code == "500" + assert ISSUE_URL_BASE in caplog.text + assert ISSUE_URL_BASE not in internal_result.message