perf: defer fastapi and tiktoken BPE imports out of import litellm

This defers FastAPI, Starlette, and the cl100k BPE table until the paths that use them run

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-09-17 08:59:48 +00:00
parent 4b368bf066
commit 293a96332c
5 changed files with 35 additions and 11 deletions

View file

@ -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,11 @@ 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
try:
from fastapi.exceptions import HTTPException
except ImportError:
return False
return isinstance(e, HTTPException) and e.status_code in _GUARDRAIL_BLOCK_STATUS_CODES
def _strict_guardrail_modes_enabled() -> bool:

View file

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

View file

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

View file

@ -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="", 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")

View file

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