mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-21 08:26:34 +00:00
[Fix] handle metadata=None in SDK path retry/error logic (utils.py) (#20873)
* [Fix] handle metadata=None in SDK path retry/error logic (utils.py)
Fixes #20871
Same class of bug as #9717 (fixed by #9764 for the proxy path).
The SDK path in utils.py has the same fragile pattern at 7 locations.
Replace `kwargs.get("metadata", {})` with `(kwargs.get("metadata") or {})`
to handle the case where metadata key exists with value None (e.g. from
Azure OpenAI streaming responses).
This is consistent with the existing correct pattern at line 602:
`metadata = kwargs.get("metadata") or {}`
Adds TestMetadataNoneHandling with 6 unit tests in test_utils.py.
* fix: remove duplicate PerplexityResponsesConfig key in lazy imports registry
Removes duplicate dictionary key added in commit be0ebb15 (PR #20860).
The entry at line 1042 is identical to the existing entry at line 906.
This causes ruff F601 lint failure on all PRs targeting main.
This commit is contained in:
+11
-11
@@ -1406,7 +1406,7 @@ def client(original_function): # noqa: PLR0915
|
||||
# [OPTIONAL] CHECK MAX RETRIES / REQUEST
|
||||
if litellm.num_retries_per_request is not None:
|
||||
# check if previous_models passed in as ['litellm_params']['metadata]['previous_models']
|
||||
previous_models = kwargs.get("metadata", {}).get(
|
||||
previous_models = (kwargs.get("metadata") or {}).get(
|
||||
"previous_models", None
|
||||
)
|
||||
if previous_models is not None:
|
||||
@@ -1483,7 +1483,7 @@ def client(original_function): # noqa: PLR0915
|
||||
# [OPTIONAL] CHECK MAX RETRIES / REQUEST
|
||||
if litellm.num_retries_per_request is not None:
|
||||
# check if previous_models passed in as ['litellm_params']['metadata]['previous_models']
|
||||
previous_models = kwargs.get("metadata", {}).get(
|
||||
previous_models = (kwargs.get("metadata") or {}).get(
|
||||
"previous_models", None
|
||||
)
|
||||
if previous_models is not None:
|
||||
@@ -1678,8 +1678,8 @@ def client(original_function): # noqa: PLR0915
|
||||
"context_window_fallback_dict", {}
|
||||
)
|
||||
|
||||
_is_litellm_router_call = "model_group" in kwargs.get(
|
||||
"metadata", {}
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
) # check if call from litellm.router/proxy
|
||||
if (
|
||||
num_retries and not _is_litellm_router_call
|
||||
@@ -1724,8 +1724,8 @@ def client(original_function): # noqa: PLR0915
|
||||
None # set retries to None to prevent infinite loops
|
||||
)
|
||||
|
||||
_is_litellm_router_call = "model_group" in kwargs.get(
|
||||
"metadata", {}
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
) # check if call from litellm.router/proxy
|
||||
if (
|
||||
num_retries and not _is_litellm_router_call
|
||||
@@ -1974,8 +1974,8 @@ def client(original_function): # noqa: PLR0915
|
||||
"context_window_fallback_dict", {}
|
||||
)
|
||||
|
||||
_is_litellm_router_call = "model_group" in kwargs.get(
|
||||
"metadata", {}
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
) # check if call from litellm.router/proxy
|
||||
|
||||
if (
|
||||
@@ -2008,8 +2008,8 @@ def client(original_function): # noqa: PLR0915
|
||||
kwargs["model"] = context_window_fallback_dict[model]
|
||||
return await original_function(*args, **kwargs)
|
||||
elif call_type == CallTypes.aresponses.value:
|
||||
_is_litellm_router_call = "model_group" in kwargs.get(
|
||||
"metadata", {}
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
) # check if call from litellm.router/proxy
|
||||
|
||||
if (
|
||||
@@ -7320,7 +7320,7 @@ def _get_base_model_from_metadata(model_call_details=None):
|
||||
_base_model = litellm_params.get("base_model", None)
|
||||
if _base_model is not None:
|
||||
return _base_model
|
||||
metadata = litellm_params.get("metadata", {})
|
||||
metadata = litellm_params.get("metadata") or {}
|
||||
|
||||
_get_base_model_from_litellm_call_metadata = getattr(
|
||||
sys.modules[__name__], "_get_base_model_from_litellm_call_metadata"
|
||||
|
||||
@@ -3305,3 +3305,73 @@ class TestIsStreamingRequest:
|
||||
|
||||
def test_stream_true_overrides_non_streaming_call_type(self):
|
||||
assert _is_streaming_request(kwargs={"stream": True}, call_type=CallTypes.acompletion) is True
|
||||
|
||||
|
||||
class TestMetadataNoneHandling:
|
||||
"""
|
||||
Test that metadata=None in kwargs doesn't cause TypeError.
|
||||
|
||||
When metadata key exists with value None (e.g., from Azure OpenAI streaming),
|
||||
dict.get("metadata", {}) returns None (key exists, so default is ignored).
|
||||
The fix uses (kwargs.get("metadata") or {}) which handles both missing key
|
||||
and explicit None value.
|
||||
|
||||
Related: #20871
|
||||
"""
|
||||
|
||||
def test_metadata_none_get_previous_models(self):
|
||||
"""kwargs.get("metadata") or {} should return {} when metadata is None."""
|
||||
kwargs = {"metadata": None}
|
||||
previous_models = (kwargs.get("metadata") or {}).get(
|
||||
"previous_models", None
|
||||
)
|
||||
assert previous_models is None
|
||||
|
||||
def test_metadata_none_model_group_check(self):
|
||||
"""'model_group' in (kwargs.get("metadata") or {}) should not raise TypeError."""
|
||||
kwargs = {"metadata": None}
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
)
|
||||
assert _is_litellm_router_call is False
|
||||
|
||||
def test_metadata_missing_key(self):
|
||||
"""Should work when metadata key is completely absent."""
|
||||
kwargs = {}
|
||||
previous_models = (kwargs.get("metadata") or {}).get(
|
||||
"previous_models", None
|
||||
)
|
||||
assert previous_models is None
|
||||
|
||||
def test_metadata_present_with_values(self):
|
||||
"""Should work when metadata has actual values."""
|
||||
kwargs = {"metadata": {"previous_models": ["model1"], "model_group": "test"}}
|
||||
previous_models = (kwargs.get("metadata") or {}).get(
|
||||
"previous_models", None
|
||||
)
|
||||
assert previous_models == ["model1"]
|
||||
_is_litellm_router_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
)
|
||||
assert _is_litellm_router_call is True
|
||||
|
||||
def test_metadata_none_causes_error_with_old_pattern(self):
|
||||
"""Demonstrate the bug: dict.get('metadata', {}) returns None when key exists with None value."""
|
||||
kwargs = {"metadata": None}
|
||||
# Old pattern: kwargs.get("metadata", {}) returns None because key exists
|
||||
result = kwargs.get("metadata", {})
|
||||
assert result is None # This is the root cause of the bug
|
||||
|
||||
# Attempting to use .get() on None raises AttributeError or TypeError
|
||||
with pytest.raises((TypeError, AttributeError)):
|
||||
kwargs.get("metadata", {}).get("previous_models", None)
|
||||
|
||||
# Attempting 'in' on None raises TypeError
|
||||
with pytest.raises(TypeError):
|
||||
"model_group" in kwargs.get("metadata", {})
|
||||
|
||||
def test_litellm_params_metadata_none(self):
|
||||
"""litellm_params.get("metadata") or {} should handle None value."""
|
||||
litellm_params = {"metadata": None}
|
||||
metadata = litellm_params.get("metadata") or {}
|
||||
assert metadata == {}
|
||||
|
||||
Reference in New Issue
Block a user