From e9fe2562a7c1abc5922e90e7ca139a2cfcbfd635 Mon Sep 17 00:00:00 2001 From: Quinn Xu Date: Thu, 1 Oct 2026 10:01:42 -0700 Subject: [PATCH 1/3] fix(proxy): extract spend tags from Starlette Headers on pass-through Pass-through endpoints store request.headers as a Starlette Headers Mapping, but _get_user_agent_tags / _get_extra_header_tags only accepted dict, so request_tags stayed empty. Accept Mapping in both extractors. Fixes #44032 --- litellm/litellm_core_utils/litellm_logging.py | 4 +- .../test_litellm_logging.py | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 154893b6c21..5d19ed19503 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -6270,7 +6270,7 @@ class StandardLoggingPayloadSetup: return None user_agent_tags: list[str] | None = None headers: Final = proxy_server_request.get("headers", {}) - if headers is not None and isinstance(headers, dict): + if headers is not None and isinstance(headers, Mapping): if "user-agent" in headers: user_agent: Final = headers["user-agent"] if user_agent is not None: @@ -6295,7 +6295,7 @@ class StandardLoggingPayloadSetup: return None headers: Final = proxy_server_request.get("headers", {}) - if not isinstance(headers, dict): + if not isinstance(headers, Mapping): return None header_tags: Final = [] diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 761fa38e73f..27dc9638168 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -2959,6 +2959,52 @@ def test_get_extra_header_tags(): delattr(litellm, "extra_spend_tag_headers") + +def test_header_derived_spend_tags_accept_starlette_headers_mapping(): + """Pass-through stores Starlette Headers (a Mapping, not dict); tags must still extract. + + Regression for https://github.com/BerriAI/litellm/issues/44032 + """ + import litellm + from starlette.datastructures import Headers + + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + original_extra_headers = getattr(litellm, "extra_spend_tag_headers", None) + original_disable_ua = getattr(litellm, "disable_add_user_agent_to_request_tags", False) + try: + litellm.disable_add_user_agent_to_request_tags = False + litellm.extra_spend_tag_headers = ["x-custom-header"] + proxy_server_request = { + "headers": Headers( + { + "user-agent": "curl/8.0.0", + "x-custom-header": "tenant-a", + } + ) + } + + ua_tags = StandardLoggingPayloadSetup._get_user_agent_tags(proxy_server_request) + assert ua_tags is not None + assert "User-Agent: curl" in ua_tags + assert "User-Agent: curl/8.0.0" in ua_tags + + extra_tags = StandardLoggingPayloadSetup._get_extra_header_tags(proxy_server_request) + assert extra_tags is not None + assert "x-custom-header: tenant-a" in extra_tags + + request_tags = StandardLoggingPayloadSetup._get_request_tags( + litellm_params={}, + proxy_server_request=proxy_server_request, + ) + assert "User-Agent: curl" in request_tags + assert "x-custom-header: tenant-a" in request_tags + finally: + litellm.extra_spend_tag_headers = original_extra_headers + litellm.disable_add_user_agent_to_request_tags = original_disable_ua + + + def test_response_cost_calculator_with_response_cost_in_hidden_params(logging_obj): from litellm import Router From 1ba33e36f19b8e68fbace9542bb9e73445f73c64 Mon Sep 17 00:00:00 2001 From: Quinn Xu Date: Sun, 11 Oct 2026 10:03:31 -0700 Subject: [PATCH 2/3] fix(proxy): use monkeypatch for Starlette Headers spend-tag test Avoid TQ005 module-global mutations by configuring spend-tag headers via monkeypatch instead of assigning litellm attributes in try/finally. --- .../test_litellm_logging.py | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 023dbb6189e..eef9bd6f4e9 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -3217,7 +3217,7 @@ def test_get_extra_header_tags(): -def test_header_derived_spend_tags_accept_starlette_headers_mapping(): +def test_header_derived_spend_tags_accept_starlette_headers_mapping(monkeypatch): """Pass-through stores Starlette Headers (a Mapping, not dict); tags must still extract. Regression for https://github.com/BerriAI/litellm/issues/44032 @@ -3227,38 +3227,32 @@ def test_header_derived_spend_tags_accept_starlette_headers_mapping(): from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup - original_extra_headers = getattr(litellm, "extra_spend_tag_headers", None) - original_disable_ua = getattr(litellm, "disable_add_user_agent_to_request_tags", False) - try: - litellm.disable_add_user_agent_to_request_tags = False - litellm.extra_spend_tag_headers = ["x-custom-header"] - proxy_server_request = { - "headers": Headers( - { - "user-agent": "curl/8.0.0", - "x-custom-header": "tenant-a", - } - ) - } - - ua_tags = StandardLoggingPayloadSetup._get_user_agent_tags(proxy_server_request) - assert ua_tags is not None - assert "User-Agent: curl" in ua_tags - assert "User-Agent: curl/8.0.0" in ua_tags - - extra_tags = StandardLoggingPayloadSetup._get_extra_header_tags(proxy_server_request) - assert extra_tags is not None - assert "x-custom-header: tenant-a" in extra_tags - - request_tags = StandardLoggingPayloadSetup._get_request_tags( - litellm_params={}, - proxy_server_request=proxy_server_request, + monkeypatch.setattr(litellm, "disable_add_user_agent_to_request_tags", False) + monkeypatch.setattr(litellm, "extra_spend_tag_headers", ["x-custom-header"]) + proxy_server_request = { + "headers": Headers( + { + "user-agent": "curl/8.0.0", + "x-custom-header": "tenant-a", + } ) - assert "User-Agent: curl" in request_tags - assert "x-custom-header: tenant-a" in request_tags - finally: - litellm.extra_spend_tag_headers = original_extra_headers - litellm.disable_add_user_agent_to_request_tags = original_disable_ua + } + + ua_tags = StandardLoggingPayloadSetup._get_user_agent_tags(proxy_server_request) + assert ua_tags is not None + assert "User-Agent: curl" in ua_tags + assert "User-Agent: curl/8.0.0" in ua_tags + + extra_tags = StandardLoggingPayloadSetup._get_extra_header_tags(proxy_server_request) + assert extra_tags is not None + assert "x-custom-header: tenant-a" in extra_tags + + request_tags = StandardLoggingPayloadSetup._get_request_tags( + litellm_params={}, + proxy_server_request=proxy_server_request, + ) + assert "User-Agent: curl" in request_tags + assert "x-custom-header: tenant-a" in request_tags From 8d5b06fe6ee9a1d5d5dddbb819e755c3626dfef5 Mon Sep 17 00:00:00 2001 From: Quinn Xu Date: Sun, 11 Oct 2026 10:12:09 -0700 Subject: [PATCH 3/3] fix(proxy): allow monkeypatch of optional extra_spend_tag_headers extra_spend_tag_headers is not always defined on the litellm module; setattr(..., raising=False) matches the getattr fallback used elsewhere. --- tests/unit/litellm_core_utils/test_litellm_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index eef9bd6f4e9..fa400635a66 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -3228,7 +3228,9 @@ def test_header_derived_spend_tags_accept_starlette_headers_mapping(monkeypatch) from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup monkeypatch.setattr(litellm, "disable_add_user_agent_to_request_tags", False) - monkeypatch.setattr(litellm, "extra_spend_tag_headers", ["x-custom-header"]) + monkeypatch.setattr( + litellm, "extra_spend_tag_headers", ["x-custom-header"], raising=False + ) proxy_server_request = { "headers": Headers( {