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
This commit is contained in:
Quinn Xu 2026-10-01 10:01:42 -07:00
parent d9f73245be
commit e9fe2562a7
No known key found for this signature in database
GPG key ID: BE28F31078A13997
2 changed files with 48 additions and 2 deletions

View file

@ -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 = []

View file

@ -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