[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:
BlueT - Matthew Lien - 練喆明
2026-02-10 22:03:33 -08:00
committed by GitHub
parent a8b4b4ccba
commit c0de6c5c6c
2 changed files with 81 additions and 11 deletions
+11 -11
View File
@@ -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"
+70
View File
@@ -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 == {}