diff --git a/litellm/proxy/hooks/global_tag_rate_limits_hook.py b/litellm/proxy/hooks/global_tag_rate_limits_hook.py index f5d6857a18f..43d2a1462f2 100644 --- a/litellm/proxy/hooks/global_tag_rate_limits_hook.py +++ b/litellm/proxy/hooks/global_tag_rate_limits_hook.py @@ -725,7 +725,7 @@ class _PROXY_GlobalTagRateLimitsHook( # pyright: ignore[reportUnusedClass] # o key_alias: Final = _extract_key_alias(litellm_params_for_metadata, metadata_variable_name) tags: Final = _order_tags_for_identity_resolution( - _get_tags_from_request_kwargs(kwargs, metadata_variable_name=metadata_variable_name), + _get_tags_from_request_kwargs(litellm_params_for_metadata, metadata_variable_name=metadata_variable_name), litellm_params_for_metadata, metadata_variable_name, ) diff --git a/tests/test_litellm/proxy/hooks/test_global_tag_rate_limits_hook.py b/tests/test_litellm/proxy/hooks/test_global_tag_rate_limits_hook.py index 1a43bebb3b8..aea37ecf149 100644 --- a/tests/test_litellm/proxy/hooks/test_global_tag_rate_limits_hook.py +++ b/tests/test_litellm/proxy/hooks/test_global_tag_rate_limits_hook.py @@ -1181,6 +1181,50 @@ async def test_log_success_event_accounts_when_litellm_params_carries_a_null_lit ) +@pytest.mark.asyncio +async def test_log_success_event_reads_tags_when_top_level_kwargs_carries_a_null_metadata_key( + time_controller, monkeypatch +): + """ + Bugbot finding: some call paths populate a top-level "metadata" (or + "litellm_metadata") key on kwargs (Logging.model_call_details) set to + None, alongside the real, populated dict nested under + kwargs["litellm_params"]. _get_tags_from_request_kwargs checks the + top-level key first; passing it raw kwargs made a present-but-None + top-level key win, reading no tags at all even though + metadata_variable_name correctly resolved to "metadata". + """ + monkeypatch.setattr( + litellm, + "global_tag_rate_limits", + { + "dollar_limits": { + "limits": [{"name": "daily_spend", "tag_id": "end_user_id", "limit": 10.0, "period_seconds": 86400}] + } + }, + ) + hook = _make_hook(time_controller) + + kwargs = { + "litellm_call_id": "call-1", + "metadata": None, + "litellm_params": { + "metadata": {"tags": ["end_user_id:u1"], "user_api_key": "hash"}, + }, + "standard_logging_object": {"total_tokens": 0, "response_cost": 12.0}, + } + await hook.async_log_success_event(kwargs=kwargs, response_obj=None, start_time=0, end_time=0) + await asyncio.sleep(0) + + with pytest.raises(ProxyRateLimitError): + await hook.async_pre_call_hook( + user_api_key_dict=_key(), + cache=DualCache(), + data=_data(["end_user_id:u1"], call_id="call-2"), + call_type="completion", + ) + + @pytest.mark.asyncio async def test_dollar_limit_respects_apply_to_key_alias_at_accounting_time(time_controller, monkeypatch): """The entry only applies to `premium-key`; a non-listed key's spend must