From 8f976df651e5af23c47d8a4a6e214cdb1bafbf1e Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Tue, 16 Dec 2025 07:45:24 -0800 Subject: [PATCH] [Refactor] litellm/init.py: lazy load default encoding from client decorator (#18059) --- litellm/_lazy_imports.py | 23 ++++++++++++++++++++++- litellm/utils.py | 19 ++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/litellm/_lazy_imports.py b/litellm/_lazy_imports.py index b023b7c696a..2c495aa8db5 100644 --- a/litellm/_lazy_imports.py +++ b/litellm/_lazy_imports.py @@ -1,10 +1,31 @@ -from typing import Any, cast +from typing import Any, Optional, cast import sys def _get_litellm_globals() -> dict: """Helper to get the globals dictionary of the litellm module.""" return sys.modules["litellm"].__dict__ +# Lazy loader for default encoding to avoid importing tiktoken at module import time +_default_encoding: Optional[Any] = None + + +def _get_default_encoding() -> Any: + """ + Lazily load and cache the default OpenAI encoding. + + This avoids importing `litellm.litellm_core_utils.default_encoding` (and thus tiktoken) + at `litellm` import time. The encoding is cached after the first import. + + This is used internally by utils.py functions that need the encoding but shouldn't + trigger its import during module load. + """ + global _default_encoding + if _default_encoding is None: + from litellm.litellm_core_utils.default_encoding import encoding + + _default_encoding = encoding + return _default_encoding + # Cost calculator names that support lazy loading via _lazy_import_cost_calculator COST_CALCULATOR_NAMES = ( "completion_cost", diff --git a/litellm/utils.py b/litellm/utils.py index 524e86cfbbe..169a2cdcb1a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1,3 +1,6 @@ +# from __future__ import annotations must be the first non-comment statement +from __future__ import annotations + # +-----------------------------------------------+ # | | # | Give Feedback / Get Help | @@ -96,11 +99,11 @@ from litellm.litellm_core_utils.core_helpers import ( process_response_headers, ) from litellm.litellm_core_utils.credential_accessor import CredentialAccessor -from litellm.litellm_core_utils.default_encoding import encoding from litellm.litellm_core_utils.dot_notation_indexing import ( delete_nested_value, is_nested_path, ) +from litellm._lazy_imports import _get_default_encoding from litellm.litellm_core_utils.exception_mapping_utils import ( _get_response_headers, exception_type, @@ -260,12 +263,16 @@ from litellm.llms.base_llm.base_utils import ( BaseLLMModelInfo, type_to_response_format_param, ) + +if TYPE_CHECKING: + # Heavy types that are only needed for type checking; avoid importing + # their modules at runtime during `litellm` import. + from litellm.llms.base_llm.files.transformation import BaseFilesConfig from litellm.llms.base_llm.batches.transformation import BaseBatchesConfig from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.base_llm.completion.transformation import BaseTextCompletionConfig from litellm.llms.base_llm.containers.transformation import BaseContainerConfig from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig -from litellm.llms.base_llm.files.transformation import BaseFilesConfig from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig from litellm.llms.base_llm.image_generation.transformation import ( BaseImageGenerationConfig, @@ -293,6 +300,7 @@ from .caching.caching import ( RedisSemanticCache, S3Cache, ) + from .exceptions import ( APIConnectionError, APIError, @@ -1752,7 +1760,7 @@ def _select_tokenizer_helper(model: str) -> SelectTokenizerResponse: def _return_openai_tokenizer(model: str) -> SelectTokenizerResponse: - return {"type": "openai_tokenizer", "tokenizer": encoding} + return {"type": "openai_tokenizer", "tokenizer": _get_default_encoding()} def _return_huggingface_tokenizer(model: str) -> Optional[SelectTokenizerResponse]: @@ -5842,7 +5850,7 @@ def prompt_token_calculator(model, messages): anthropic_obj = Anthropic() num_tokens = anthropic_obj.count_tokens(text) # type: ignore else: - num_tokens = len(encoding.encode(text)) + num_tokens = len(_get_default_encoding().encode(text)) return num_tokens @@ -8187,9 +8195,6 @@ def extract_duration_from_srt_or_vtt(srt_or_vtt_content: str) -> Optional[float] return max(durations) if durations else None -import httpx - - def _add_path_to_api_base(api_base: str, ending_path: str) -> str: """ Adds an ending path to an API base URL while preventing duplicate path segments.