mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix: extra_headers population
This commit is contained in:
parent
00ab4d2067
commit
fbcd969e05
3 changed files with 117 additions and 15 deletions
|
|
@ -1852,14 +1852,35 @@ class BaseLLMHTTPHandler:
|
|||
forwarded_headers = kwargs.get("headers", None)
|
||||
# Also check for extra_headers in kwargs (from config or direct calls)
|
||||
extra_headers_from_kwargs = kwargs.get("extra_headers", None)
|
||||
# Merge all header sources: forwarded < extra_headers < provider_specific
|
||||
merged_headers = {}
|
||||
if forwarded_headers:
|
||||
merged_headers.update(forwarded_headers)
|
||||
if extra_headers_from_kwargs:
|
||||
merged_headers.update(extra_headers_from_kwargs)
|
||||
if provider_specific_headers:
|
||||
merged_headers.update(provider_specific_headers)
|
||||
# Merge all header sources, accumulating anthropic-beta values from every source
|
||||
merged_headers: dict = {}
|
||||
anthropic_beta_parts: list = []
|
||||
|
||||
def _collect_headers(source: dict) -> None:
|
||||
for key, value in source.items():
|
||||
if key.lower() == "anthropic-beta":
|
||||
# Accumulate all beta values; dedup later via a set.
|
||||
if isinstance(value, list):
|
||||
anthropic_beta_parts.extend(v.strip() for v in value if v.strip())
|
||||
elif isinstance(value, str):
|
||||
anthropic_beta_parts.extend(
|
||||
v.strip() for v in value.split(",") if v.strip()
|
||||
)
|
||||
else:
|
||||
merged_headers[key] = value
|
||||
|
||||
for _src in [forwarded_headers, provider_specific_headers, extra_headers_from_kwargs]:
|
||||
if _src:
|
||||
_collect_headers(_src)
|
||||
|
||||
# extra_headers_from_kwargs (deployment config) wins for non-beta headers
|
||||
# by being applied last in the loop above.
|
||||
if anthropic_beta_parts:
|
||||
# Preserve insertion order while deduplicating.
|
||||
seen: set = set()
|
||||
merged_headers["anthropic-beta"] = ",".join(
|
||||
v for v in anthropic_beta_parts if not (v in seen or seen.add(v)) # type: ignore[func-returns-value]
|
||||
)
|
||||
(
|
||||
headers,
|
||||
api_base,
|
||||
|
|
|
|||
|
|
@ -3525,9 +3525,9 @@ class Router:
|
|||
### get custom
|
||||
response = original_generic_function(
|
||||
**{
|
||||
**data,
|
||||
"caching": self.cache_responses,
|
||||
**kwargs,
|
||||
"caching": self.cache_responses,
|
||||
**data,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ async def test_async_anthropic_messages_handler_extra_headers():
|
|||
@pytest.mark.asyncio
|
||||
async def test_async_anthropic_messages_handler_header_priority():
|
||||
"""
|
||||
Test that async_anthropic_messages_handler respects header priority:
|
||||
forwarded < extra_headers < provider_specific
|
||||
Test that async_anthropic_messages_handler respects header priority for non-beta
|
||||
headers: forwarded < provider_specific < extra_headers (deployment config wins).
|
||||
"""
|
||||
handler = BaseLLMHTTPHandler()
|
||||
|
||||
|
|
@ -209,9 +209,90 @@ async def test_async_anthropic_messages_handler_header_priority():
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Verify priority: provider_specific should win
|
||||
assert captured_headers["X-Priority"] == "provider"
|
||||
# Verify all unique headers from different sources are present
|
||||
# Deployment config (extra_headers) wins over provider_specific for non-beta headers
|
||||
assert captured_headers["X-Priority"] == "extra"
|
||||
# All unique headers from all sources must still be present
|
||||
assert captured_headers["X-Forwarded-Only"] == "keep"
|
||||
assert captured_headers["X-Extra-Only"] == "also-keep"
|
||||
assert captured_headers["X-Provider-Only"] == "keep-this-too"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_anthropic_messages_handler_anthropic_beta_accumulation():
|
||||
"""
|
||||
Test that anthropic-beta header values from all three sources (forwarded_headers,
|
||||
provider_specific_headers, extra_headers from deployment config) are accumulated
|
||||
rather than last-write-wins, so that the deployment-configured beta flag is never
|
||||
silently dropped by the client's forwarded headers.
|
||||
|
||||
Regression test for: extra_headers={'anthropic-beta': 'context-1m-2025-08-07'}
|
||||
being overwritten by provider_specific_headers carrying the client's
|
||||
anthropic-beta value.
|
||||
"""
|
||||
handler = BaseLLMHTTPHandler()
|
||||
|
||||
mock_config = Mock()
|
||||
mock_client = AsyncMock()
|
||||
mock_logging_obj = Mock()
|
||||
mock_logging_obj.update_environment_variables = Mock()
|
||||
mock_logging_obj.model_call_details = {}
|
||||
mock_logging_obj.stream = False
|
||||
|
||||
# Simulate the real-world scenario:
|
||||
# - client forwards anthropic-beta with interleaved-thinking + context-management
|
||||
# - deployment config adds context-1m-2025-08-07 via extra_headers
|
||||
# - provider_specific_header also carries the client's forwarded anthropic-beta
|
||||
kwargs = {
|
||||
"headers": {
|
||||
"anthropic-beta": "interleaved-thinking-2025-05-14,context-management-2025-06-27",
|
||||
},
|
||||
"extra_headers": {
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
},
|
||||
}
|
||||
|
||||
with patch(
|
||||
"litellm.litellm_core_utils.get_provider_specific_headers.ProviderSpecificHeaderUtils.get_provider_specific_headers"
|
||||
) as mock_provider_headers:
|
||||
# provider_specific_headers mirrors the client-forwarded anthropic-beta
|
||||
mock_provider_headers.return_value = {
|
||||
"anthropic-beta": "interleaved-thinking-2025-05-14,context-management-2025-06-27",
|
||||
}
|
||||
|
||||
captured_headers = {}
|
||||
|
||||
def capture_validate(*args, **kwargs):
|
||||
captured_headers.update(kwargs.get("headers", {}))
|
||||
return ({}, "https://api.anthropic.com")
|
||||
|
||||
mock_config.validate_anthropic_messages_environment = capture_validate
|
||||
mock_config.transform_anthropic_messages_request = Mock(
|
||||
return_value={"model": "claude-sonnet-4-5", "messages": []}
|
||||
)
|
||||
|
||||
try:
|
||||
await handler.async_anthropic_messages_handler(
|
||||
model="claude-sonnet-4-5",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_provider_config=mock_config,
|
||||
anthropic_messages_optional_request_params={},
|
||||
custom_llm_provider="bedrock",
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
logging_obj=mock_logging_obj,
|
||||
client=mock_client,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
assert "anthropic-beta" in captured_headers
|
||||
beta_values = set(captured_headers["anthropic-beta"].split(","))
|
||||
# All three values must be present – none should be silently dropped
|
||||
assert "context-1m-2025-08-07" in beta_values, (
|
||||
"Deployment-configured 'context-1m-2025-08-07' was overwritten by client headers"
|
||||
)
|
||||
assert "interleaved-thinking-2025-05-14" in beta_values
|
||||
assert "context-management-2025-06-27" in beta_values
|
||||
# No duplicates
|
||||
raw_values = captured_headers["anthropic-beta"].split(",")
|
||||
assert len(raw_values) == len(beta_values), "Duplicate beta values found"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue