From 3b1792d728ecf736365e26ca5a4b92920e20c5b2 Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Fri, 2 Jan 2026 13:51:28 -0800 Subject: [PATCH] feat: lazy load heavy imports to reduce memory usage at import time (#18592) - Lazy load remove_index_from_tool_calls via __getattr__ - Lazy load _service_logger module via __getattr__ in __init__.py - Lazy load audio_utils.utils with module-level caching in utils.py - Remove direct imports that trigger heavy module loading These changes reduce import-time memory usage by deferring imports until they are actually needed, while maintaining performance through caching for subsequent accesses. --- litellm/__init__.py | 14 +++++++++++++- litellm/_lazy_imports_registry.py | 2 ++ litellm/utils.py | 26 +++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 23eff758e7..207ece224f 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1058,7 +1058,7 @@ openai_video_generation_models = ["sora-2"] # timeout is lazy-loaded via __getattr__ # get_llm_provider is lazy-loaded via __getattr__ -from litellm.litellm_core_utils.core_helpers import remove_index_from_tool_calls +# remove_index_from_tool_calls is lazy-loaded via __getattr__ # Import KeyManagementSettings here (before utils import) because _key_management_settings # is accessed during import time in secret_managers/main.py (via dd_tracing -> datadog -> _service_logger -> utils) @@ -1507,6 +1507,7 @@ if TYPE_CHECKING: get_first_chars_messages: Callable[..., str] get_provider_fields: Callable[..., List] get_valid_models: Callable[..., list] + remove_index_from_tool_calls: Callable[..., None] # Response types - truly lazy loaded only (not in main.py or elsewhere) ModelResponseListIterator: Type[Any] @@ -1658,6 +1659,17 @@ def __getattr__(name: str) -> Any: LoggingCallbackManager = __getattr__("LoggingCallbackManager") _globals["logging_callback_manager"] = LoggingCallbackManager() return _globals["logging_callback_manager"] + + # Lazy load _service_logger module + if name == "_service_logger": + from ._lazy_imports import _get_litellm_globals + _globals = _get_litellm_globals() + # Check if already cached + if "_service_logger" not in _globals: + # Import the module lazily + import litellm._service_logger + _globals["_service_logger"] = litellm._service_logger + return _globals["_service_logger"] raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 01be9c095d..1a54e77998 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -290,6 +290,7 @@ TYPES_NAMES = ( # LLM provider logic names that support lazy loading via _lazy_import_llm_provider_logic LLM_PROVIDER_LOGIC_NAMES = ( "get_llm_provider", + "remove_index_from_tool_calls", ) # Import maps for registry pattern - reduces repetition @@ -393,6 +394,7 @@ _TYPES_IMPORT_MAP = { _LLM_PROVIDER_LOGIC_IMPORT_MAP = { "get_llm_provider": ("litellm.litellm_core_utils.get_llm_provider_logic", "get_llm_provider"), + "remove_index_from_tool_calls": ("litellm.litellm_core_utils.core_helpers", "remove_index_from_tool_calls"), } _LLM_CONFIGS_IMPORT_MAP = { diff --git a/litellm/utils.py b/litellm/utils.py index 32f54b652e..10f8e8b055 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -48,9 +48,9 @@ from tiktoken import Encoding from tokenizers import Tokenizer import litellm -import litellm._service_logger # for storing API inputs, outputs, and metadata + import litellm.litellm_core_utils -import litellm.litellm_core_utils.audio_utils.utils +# audio_utils.utils is lazy-loaded - only imported when needed for transcription calls import litellm.litellm_core_utils.json_validation_rule import litellm.llms import litellm.llms.gemini @@ -157,6 +157,24 @@ from litellm.router_utils.get_retry_from_policy import ( reset_retry_policy, ) from litellm.secret_managers.main import get_secret + +# Cached lazy import for audio_utils.utils +# Module-level cache to avoid repeated imports while preserving memory benefits +_audio_utils_module = None + + +def _get_cached_audio_utils(): + """ + Get cached audio_utils.utils module. + Lazy imports on first call to avoid loading audio_utils.utils at import time. + Subsequent calls use cached module for better performance. + """ + global _audio_utils_module + if _audio_utils_module is None: + import litellm.litellm_core_utils.audio_utils.utils + _audio_utils_module = litellm.litellm_core_utils.audio_utils.utils + return _audio_utils_module + from litellm.types.llms.anthropic import ( ANTHROPIC_API_ONLY_HEADERS, AnthropicThinkingParam, @@ -965,7 +983,9 @@ def function_setup( # noqa: PLR0915 or call_type == CallTypes.transcription.value ): _file_obj: FileTypes = args[1] if len(args) > 1 else kwargs["file"] - file_checksum = litellm.litellm_core_utils.audio_utils.utils.get_audio_file_content_hash( + # Lazy import audio_utils.utils only when needed for transcription calls + audio_utils = _get_cached_audio_utils() + file_checksum = audio_utils.get_audio_file_content_hash( file_obj=_file_obj ) if "metadata" in kwargs: