mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(proxy): keep bug report notice out of client error bodies
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
919d8a59c3
commit
fd0398d837
5 changed files with 64 additions and 6 deletions
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
import platform
|
||||
import traceback
|
||||
|
|
@ -15,8 +16,10 @@ from litellm._version import version as litellm_version
|
|||
ISSUE_URL_BASE: Final = "https://github.com/BerriAI/litellm/issues/new"
|
||||
MAX_URL_LENGTH: Final = 6000
|
||||
MAX_MESSAGE_CHARS: Final = 600
|
||||
MAX_CONTEXT_CHARS: Final = 200
|
||||
MAX_FRAMES: Final = 12
|
||||
DISABLE_ENV_VAR: Final = "LITELLM_DISABLE_BUG_REPORT_LINK"
|
||||
NOTICE_PREFIX: Final = "This looks like a bug in LiteLLM rather than in your request."
|
||||
_SHORTENED_MESSAGE_LENGTHS: Final = (480, 360, 240, 120, 0)
|
||||
|
||||
Surface = Literal["sdk", "proxy"]
|
||||
|
|
@ -66,7 +69,7 @@ def _get_litellm_frames(exc: BaseException) -> tuple[str, ...]:
|
|||
def _redact_context(value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
return redact_secrets(value)[:MAX_MESSAGE_CHARS]
|
||||
return redact_secrets(value)[:MAX_CONTEXT_CHARS]
|
||||
|
||||
|
||||
def build_bug_report(
|
||||
|
|
@ -142,11 +145,19 @@ def bug_report_issue_url(report: BugReport) -> str:
|
|||
*(_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)),
|
||||
)
|
||||
return next((candidate for candidate in candidates if len(candidate) <= MAX_URL_LENGTH), _issue_url(report, "", ()))
|
||||
return next(
|
||||
(candidate for candidate in candidates if len(candidate) <= MAX_URL_LENGTH),
|
||||
_issue_url(dataclasses.replace(report, call_type=None, model=None, custom_llm_provider=None), "", ()),
|
||||
)
|
||||
|
||||
|
||||
def strip_bug_report_notice(message: str) -> str:
|
||||
index: Final = message.find(NOTICE_PREFIX)
|
||||
return message if index == -1 else message[:index].rstrip()
|
||||
|
||||
|
||||
def bug_report_notice(report: BugReport) -> str:
|
||||
return (
|
||||
"This looks like a bug in LiteLLM rather than in your request. File it with one click "
|
||||
f"{NOTICE_PREFIX} File it with one click "
|
||||
f"(prefilled and redacted, review before submitting): {bug_report_issue_url(report)}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ from litellm.litellm_core_utils.bug_report import (
|
|||
bug_report_enabled,
|
||||
bug_report_notice,
|
||||
build_bug_report,
|
||||
strip_bug_report_notice,
|
||||
)
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
get_or_create_metadata_bucket,
|
||||
|
|
@ -3681,8 +3682,11 @@ class ProxyBaseLLMRequestProcessing:
|
|||
)
|
||||
)
|
||||
)
|
||||
client_message: Final = getattr(e, "message", error_msg)
|
||||
raise ProxyException(
|
||||
message=redact_internal_details_from_client_message(getattr(e, "message", error_msg)),
|
||||
message=redact_internal_details_from_client_message(
|
||||
strip_bug_report_notice(client_message) if isinstance(client_message, str) else error_msg
|
||||
),
|
||||
type=openai_error_type(e, _code),
|
||||
param=openai_error_param(e),
|
||||
openai_code=getattr(e, "code", None),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ from litellm.litellm_core_utils.bug_report import (
|
|||
bug_report_enabled,
|
||||
bug_report_notice,
|
||||
build_bug_report,
|
||||
strip_bug_report_notice,
|
||||
)
|
||||
from litellm.proxy._types import (
|
||||
CommonProxyErrors,
|
||||
|
|
@ -7844,7 +7845,7 @@ def handle_exception_on_proxy(e: Exception, litellm_call_id: str | None = None)
|
|||
if _status_code == status.HTTP_500_INTERNAL_SERVER_ERROR and bug_report_enabled() and isinstance(e, BaseException):
|
||||
verbose_proxy_logger.error(bug_report_notice(build_bug_report(e, surface="proxy")))
|
||||
return ProxyException(
|
||||
message=str(e),
|
||||
message=strip_bug_report_notice(str(e)),
|
||||
type=ProxyErrorTypes.internal_server_error,
|
||||
param=openai_error_param(e),
|
||||
headers=headers,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ from litellm.litellm_core_utils.bug_report import (
|
|||
MAX_URL_LENGTH,
|
||||
bug_report_enabled,
|
||||
bug_report_issue_url,
|
||||
bug_report_notice,
|
||||
build_bug_report,
|
||||
strip_bug_report_notice,
|
||||
)
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
|
|
@ -54,6 +56,18 @@ def test_issue_url_is_bounded_for_long_messages():
|
|||
assert "RuntimeError" in query["description"][0]
|
||||
|
||||
|
||||
def test_issue_url_is_bounded_for_long_non_ascii_context():
|
||||
report = build_bug_report(
|
||||
ValueError("故障" * 10_000),
|
||||
surface="sdk",
|
||||
model="模型" * 300,
|
||||
custom_llm_provider="供給" * 300,
|
||||
call_type="呼出" * 300,
|
||||
)
|
||||
|
||||
assert len(bug_report_issue_url(report)) <= MAX_URL_LENGTH
|
||||
|
||||
|
||||
def test_issue_url_builds_without_a_traceback():
|
||||
exc = RuntimeError("no traceback")
|
||||
assert exc.__traceback__ is None
|
||||
|
|
@ -80,3 +94,11 @@ def test_proxy_provider_uses_translation_domain():
|
|||
query = parse_qs(urlparse(bug_report_issue_url(report)).query)
|
||||
|
||||
assert query["domain"] == ["LLM translation: a specific provider's request or response"]
|
||||
|
||||
|
||||
def test_strip_bug_report_notice():
|
||||
report = build_bug_report(RuntimeError("boom"), surface="sdk")
|
||||
notice = bug_report_notice(report)
|
||||
|
||||
assert strip_bug_report_notice(f"boom\n{notice}") == "boom"
|
||||
assert strip_bug_report_notice("boom") == "boom"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ from fastapi.responses import JSONResponse, StreamingResponse
|
|||
|
||||
import litellm
|
||||
from litellm._uuid import uuid
|
||||
from litellm.litellm_core_utils.bug_report import DISABLE_ENV_VAR, ISSUE_URL_BASE
|
||||
from litellm.litellm_core_utils.bug_report import (
|
||||
DISABLE_ENV_VAR,
|
||||
ISSUE_URL_BASE,
|
||||
bug_report_notice,
|
||||
build_bug_report,
|
||||
)
|
||||
from litellm.constants import (
|
||||
CLIENT_REQUESTED_MODEL_SCOPE_KEY,
|
||||
MAX_LITELLM_CALL_ID_LENGTH,
|
||||
|
|
@ -4259,6 +4264,21 @@ class TestHandleLLMApiExceptionRetryAfter:
|
|||
proxy_exc = await self._invoke(ValueError("some other failure"))
|
||||
assert "retry-after" not in proxy_exc.headers
|
||||
|
||||
async def test_handle_llm_api_exception_strips_bug_report_notice_from_client_message(self, caplog):
|
||||
report = build_bug_report(RuntimeError("boom"), surface="sdk")
|
||||
notice = bug_report_notice(report)
|
||||
exc = litellm.APIConnectionError(
|
||||
message=f"boom\n{notice}",
|
||||
model="gpt-4o",
|
||||
llm_provider="openai",
|
||||
)
|
||||
|
||||
with caplog.at_level("ERROR"):
|
||||
proxy_exc = await self._invoke(exc)
|
||||
|
||||
assert ISSUE_URL_BASE not in proxy_exc.message
|
||||
assert ISSUE_URL_BASE in caplog.text
|
||||
|
||||
async def test_handle_llm_api_exception_retry_after_survives_callback_headers(self):
|
||||
from litellm.types.router import RouterRateLimitError
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue