mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
Merge pull request #41585 from BerriAI/litellm_lazy_fastapi_bpe_imports
perf: defer fastapi and tiktoken BPE imports out of import litellm
This commit is contained in:
commit
d42f448e41
8 changed files with 71 additions and 11 deletions
|
|
@ -35,11 +35,6 @@ from litellm.types.utils import (
|
|||
StandardLoggingGuardrailInformation,
|
||||
)
|
||||
|
||||
try:
|
||||
from fastapi.exceptions import HTTPException
|
||||
except ImportError:
|
||||
HTTPException = None
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation
|
||||
|
|
@ -107,9 +102,9 @@ def is_guardrail_intervention(e: Exception) -> bool:
|
|||
),
|
||||
):
|
||||
return True
|
||||
if HTTPException is not None and isinstance(e, HTTPException) and e.status_code in _GUARDRAIL_BLOCK_STATUS_CODES:
|
||||
return True
|
||||
return False
|
||||
from litellm.proxy.guardrails.exception_utils import is_fastapi_http_exception
|
||||
|
||||
return is_fastapi_http_exception(e, _GUARDRAIL_BLOCK_STATUS_CODES)
|
||||
|
||||
|
||||
def _strict_guardrail_modes_enabled() -> bool:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from litellm.integrations.gcs_bucket.gcs_bucket_base import GCSBucketBase
|
|||
from litellm.litellm_core_utils.cloud_storage_security import (
|
||||
sanitize_cloud_object_component,
|
||||
)
|
||||
from litellm.proxy._types import CommonProxyErrors
|
||||
from litellm.types.integrations.base_health_check import IntegrationHealthCheckStatus
|
||||
from litellm.types.integrations.gcs_bucket import *
|
||||
from litellm.types.utils import StandardLoggingPayload
|
||||
|
|
@ -27,6 +26,7 @@ else:
|
|||
|
||||
class GCSBucketLogger(GCSBucketBase, AdditionalLoggingUtils):
|
||||
def __init__(self, bucket_name: str | None = None) -> None:
|
||||
from litellm.proxy._types import CommonProxyErrors
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
|
||||
self.batch_size = int(os.getenv("GCS_BATCH_SIZE", GCS_DEFAULT_BATCH_SIZE))
|
||||
|
|
@ -52,6 +52,7 @@ class GCSBucketLogger(GCSBucketBase, AdditionalLoggingUtils):
|
|||
|
||||
#### ASYNC ####
|
||||
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
from litellm.proxy._types import CommonProxyErrors
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
|
||||
if premium_user is not True:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from typing_extensions import ParamSpec, TypeVar
|
|||
|
||||
import litellm
|
||||
from litellm import verbose_logger
|
||||
from litellm._lazy_imports import _get_default_encoding
|
||||
from litellm.constants import (
|
||||
DEFAULT_IMAGE_HEIGHT,
|
||||
DEFAULT_IMAGE_TOKEN_COUNT,
|
||||
|
|
@ -29,7 +30,6 @@ from litellm.constants import (
|
|||
TOKEN_COUNTER_MAX_EXACT_CHARS,
|
||||
)
|
||||
from litellm.litellm_core_utils.asyncify import asyncify
|
||||
from litellm.litellm_core_utils.default_encoding import encoding as default_encoding
|
||||
from litellm.litellm_core_utils.url_utils import safe_get
|
||||
from litellm.llms.custom_httpx.http_handler import _get_httpx_client
|
||||
from litellm.types.llms.anthropic import (
|
||||
|
|
@ -638,7 +638,7 @@ def _get_exact_count_function(
|
|||
else:
|
||||
|
||||
def encode_length(text: str) -> int:
|
||||
return len(default_encoding.encode(text, disallowed_special=()))
|
||||
return len(_get_default_encoding().encode(text, disallowed_special=()))
|
||||
|
||||
return _get_tiktoken_count_function(encode_length)
|
||||
|
||||
|
|
|
|||
9
litellm/proxy/guardrails/exception_utils.py
Normal file
9
litellm/proxy/guardrails/exception_utils.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from collections.abc import Collection
|
||||
|
||||
|
||||
def is_fastapi_http_exception(e: Exception, block_status_codes: Collection[int]) -> bool:
|
||||
try:
|
||||
from fastapi.exceptions import HTTPException
|
||||
except ImportError:
|
||||
return False
|
||||
return isinstance(e, HTTPException) and e.status_code in block_status_codes
|
||||
|
|
@ -131,6 +131,13 @@ class TestGCSBucketBase:
|
|||
|
||||
|
||||
class TestGCSBucketLoggerBucketName:
|
||||
@pytest.mark.asyncio
|
||||
async def test_constructor_rejects_non_premium_user(self, monkeypatch):
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", False)
|
||||
|
||||
with pytest.raises(ValueError, match="GCS Bucket logging is a premium feature"):
|
||||
GCSBucketLogger(bucket_name="config-bucket")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_bucket_name_it_is_constructed_with_survives(self, monkeypatch):
|
||||
"""Reading config.yaml out of a GCS bucket asks for that bucket, not the logging one (LIT-6982)."""
|
||||
|
|
@ -145,3 +152,11 @@ class TestGCSBucketLoggerBucketName:
|
|||
monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True)
|
||||
|
||||
assert GCSBucketLogger().BUCKET_NAME == "logging-bucket"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_logging_rejects_non_premium_user(self, monkeypatch):
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", False)
|
||||
logger = object.__new__(GCSBucketLogger)
|
||||
|
||||
with pytest.raises(ValueError, match="GCS Bucket logging is a premium feature"):
|
||||
await logger.async_log_success_event({}, None, None, None)
|
||||
|
|
|
|||
|
|
@ -1754,6 +1754,20 @@ class TestCustomGuardrailSpendLogMatchRedaction:
|
|||
class TestGuardrailInterventionClassification:
|
||||
"""A routing decision is a deliberate guardrail intervention, not a failure."""
|
||||
|
||||
def test_http_exception_classification_returns_false_without_fastapi(self, monkeypatch):
|
||||
import builtins
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def import_without_fastapi(name, *args, **kwargs):
|
||||
if name == "fastapi.exceptions":
|
||||
raise ImportError("fastapi is unavailable")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", import_without_fastapi)
|
||||
|
||||
assert CustomGuardrail._is_guardrail_intervention(Exception("not an intervention")) is False
|
||||
|
||||
def test_sensitive_data_route_exception_is_intervention(self):
|
||||
from litellm.exceptions import SensitiveDataRouteException
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,13 @@ def test_token_counter_short_text_matches_tiktoken(text):
|
|||
assert token_counter_new(model="us.anthropic.claude-sonnet-4-6", text=text) == expected
|
||||
|
||||
|
||||
def test_token_counter_default_encoding_matches_cl100k():
|
||||
encoding: Final = tiktoken.get_encoding("cl100k_base")
|
||||
expected: Final = len(encoding.encode("hello world", disallowed_special=()))
|
||||
|
||||
assert token_counter_new(model=None, text="hello world") == expected
|
||||
|
||||
|
||||
def test_token_counter_text_over_chunk_boundary_stays_close_to_tiktoken():
|
||||
text = ("The quick brown fox jumps over the lazy dog. " * 30)[:1025]
|
||||
encoding = tiktoken.get_encoding("cl100k_base")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"""Simple tests for lazy import functionality."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -38,6 +41,22 @@ from litellm._lazy_imports import (
|
|||
)
|
||||
|
||||
|
||||
def test_import_litellm_does_not_load_fastapi_or_bpe_table():
|
||||
result: Final = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
"import sys, litellm; print(','.join(m for m in ('fastapi','starlette','litellm.litellm_core_utils.default_encoding') if m in sys.modules))",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "LITELLM_LOCAL_MODEL_COST_MAP": "True"},
|
||||
)
|
||||
|
||||
assert result.stdout.strip() == ""
|
||||
|
||||
|
||||
def _clear_names_from_globals(names: tuple):
|
||||
"""Clear all names from litellm globals."""
|
||||
# Get the actual globals dict, not a copy
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue