From 1dc7d197e7fa18bcaaed8bc3c7fd7ae75e22590c Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Sat, 1 Nov 2025 13:16:32 -0700 Subject: [PATCH] fix: resolve memory leak caused by Pydantic 2.11+ deprecation warnings (#16110) - Access model_fields on class instead of instance to avoid deprecation warnings - Add warning filter to suppress Pydantic instance attribute access warnings - Fix affects streaming operations where warnings accumulate over time - Updated cost_calculator.py, core_helpers.py, and model_response_utils.py The previous implementation accessed model_fields on Pydantic model instances, which triggers deprecation warnings in Pydantic 2.11+. These warnings accumulate during streaming operations, causing memory leaks. Now accessing model_fields via type(instance).model_fields to prevent warning generation. --- litellm/__init__.py | 3 +++ litellm/cost_calculator.py | 6 ++++-- litellm/litellm_core_utils/core_helpers.py | 3 ++- litellm/litellm_core_utils/model_response_utils.py | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index d141a3fb28..6135e27577 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -2,6 +2,9 @@ import warnings warnings.filterwarnings("ignore", message=".*conflict with protected namespace.*") +# Suppress Pydantic 2.11+ deprecation warning about accessing model_fields on instances +# This warning can accumulate during streaming and cause memory leaks +warnings.filterwarnings("ignore", message=".*Accessing the.*attribute on the instance is deprecated.*") ### INIT VARIABLES ###################### import threading import os diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9935eed292..42fcabcf68 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -1595,7 +1595,8 @@ class BaseTokenUsageProcessor: combined.prompt_tokens_details = PromptTokensDetailsWrapper() # Check what keys exist in the model's prompt_tokens_details - for attr in usage.prompt_tokens_details.model_fields: + # Access model_fields on the class, not the instance, to avoid Pydantic 2.11+ deprecation warnings + for attr in type(usage.prompt_tokens_details).model_fields: if ( hasattr(usage.prompt_tokens_details, attr) and not attr.startswith("_") @@ -1626,7 +1627,8 @@ class BaseTokenUsageProcessor: ) # Check what keys exist in the model's completion_tokens_details - for attr in usage.completion_tokens_details.model_fields: + # Access model_fields on the class, not the instance, to avoid Pydantic 2.11+ deprecation warnings + for attr in type(usage.completion_tokens_details).model_fields: if not attr.startswith("_") and not callable( getattr(usage.completion_tokens_details, attr) ): diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index 8b9f53cec1..47034c3a5c 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -234,7 +234,8 @@ def preserve_upstream_non_openai_attributes( """ Preserve non-OpenAI attributes from the original chunk. """ - expected_keys = set(model_response.model_fields.keys()).union({"usage"}) + # Access model_fields on the class, not the instance, to avoid Pydantic 2.11+ deprecation warnings + expected_keys = set(type(model_response).model_fields.keys()).union({"usage"}) for key, value in original_chunk.model_dump().items(): if key not in expected_keys: setattr(model_response, key, value) diff --git a/litellm/litellm_core_utils/model_response_utils.py b/litellm/litellm_core_utils/model_response_utils.py index 974d12aef6..00462221fe 100644 --- a/litellm/litellm_core_utils/model_response_utils.py +++ b/litellm/litellm_core_utils/model_response_utils.py @@ -46,7 +46,8 @@ def is_model_response_stream_empty(model_response: ModelResponseStream) -> bool: return False # Check for any non-base fields that are set - for model_response_field in model_response.model_fields.keys(): + # Access model_fields on the class, not the instance, to avoid Pydantic 2.11+ deprecation warnings + for model_response_field in type(model_response).model_fields.keys(): # Skip base fields that are always set if model_response_field in BASE_FIELDS: continue