mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix: forward retry-after header from upstream providers to clients
When upstream LLM providers return 429 responses with a retry-after header, the proxy only forwarded it as llm_provider-retry-after. The bare retry-after header was missing, which prevented clients using the OpenAI SDK from respecting the provider's retry timing. Preserve the retry-after header as-is in get_response_headers(), following the same pattern used for x-ratelimit-* headers. Fixes #21553
This commit is contained in:
parent
37c98f8325
commit
a7d59070c3
3 changed files with 90 additions and 0 deletions
|
|
@ -35,6 +35,8 @@ def get_response_headers(_response_headers: Optional[dict] = None) -> dict:
|
|||
openai_headers["x-ratelimit-remaining-tokens"] = _response_headers[
|
||||
"x-ratelimit-remaining-tokens"
|
||||
]
|
||||
if "retry-after" in _response_headers:
|
||||
openai_headers["retry-after"] = _response_headers["retry-after"]
|
||||
llm_provider_headers = _get_llm_provider_headers(_response_headers)
|
||||
return {**llm_provider_headers, **openai_headers}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,3 +77,42 @@ def test_get_llm_provider_headers():
|
|||
}
|
||||
result = _get_llm_provider_headers(input_headers)
|
||||
assert result == expected_output, "Unexpected output for _get_llm_provider_headers"
|
||||
|
||||
def test_get_response_headers_with_retry_after():
|
||||
"""
|
||||
When upstream provider returns retry-after header (e.g. on 429),
|
||||
it should be forwarded as both 'retry-after' and 'llm_provider-retry-after'.
|
||||
|
||||
Ref: https://github.com/BerriAI/litellm/issues/21553
|
||||
"""
|
||||
input_headers = {
|
||||
"retry-after": "57",
|
||||
"x-ratelimit-limit-tokens": "100000",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
result = get_response_headers(input_headers)
|
||||
|
||||
# retry-after should be preserved as a bare header
|
||||
assert result.get("retry-after") == "57"
|
||||
# retry-after should also be present as llm_provider-retry-after
|
||||
assert result.get("llm_provider-retry-after") == "57"
|
||||
# OpenAI headers should still work
|
||||
assert result.get("x-ratelimit-limit-tokens") == "100000"
|
||||
# Other headers should only be prefixed
|
||||
assert result.get("llm_provider-content-type") == "application/json"
|
||||
assert "content-type" not in result
|
||||
|
||||
|
||||
def test_get_response_headers_without_retry_after():
|
||||
"""
|
||||
When upstream provider does NOT return retry-after header,
|
||||
the output should not contain a retry-after key.
|
||||
"""
|
||||
input_headers = {
|
||||
"x-ratelimit-limit-tokens": "100000",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
result = get_response_headers(input_headers)
|
||||
|
||||
assert "retry-after" not in result
|
||||
assert result.get("x-ratelimit-limit-tokens") == "100000"
|
||||
|
|
|
|||
|
|
@ -268,3 +268,52 @@ class TestProxyHeaderExtraction:
|
|||
# Verify headers are extracted and prefixed correctly
|
||||
assert headers.get("llm_provider-x-request-id") == "req-abc123"
|
||||
assert headers.get("llm_provider-x-ms-region") == "eastus"
|
||||
|
||||
def test_proxy_forwards_retry_after_from_upstream_429(self):
|
||||
"""
|
||||
When upstream provider returns 429 with retry-after header,
|
||||
proxy should forward it as a bare 'retry-after' header (not just
|
||||
'llm_provider-retry-after').
|
||||
|
||||
Ref: https://github.com/BerriAI/litellm/issues/21553
|
||||
"""
|
||||
from litellm.litellm_core_utils.llm_response_utils.get_headers import (
|
||||
get_response_headers,
|
||||
)
|
||||
from litellm.exceptions import RateLimitError
|
||||
|
||||
mock_response = httpx.Response(
|
||||
status_code=429,
|
||||
headers={
|
||||
"retry-after": "57",
|
||||
"x-ratelimit-limit-tokens": "100000",
|
||||
"x-ratelimit-remaining-tokens": "0",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
request=httpx.Request(
|
||||
"POST", "https://api.openai.com/v1/chat/completions"
|
||||
),
|
||||
)
|
||||
error = RateLimitError(
|
||||
message="Rate limit exceeded",
|
||||
model="gpt-4",
|
||||
llm_provider="azure",
|
||||
response=mock_response,
|
||||
)
|
||||
|
||||
# Simulate proxy header extraction logic from
|
||||
# common_request_processing.py _handle_llm_api_exception
|
||||
headers = getattr(error, "headers", None) or {}
|
||||
if not headers:
|
||||
_response = getattr(error, "response", None)
|
||||
if _response is not None:
|
||||
_response_headers = getattr(_response, "headers", None)
|
||||
if _response_headers:
|
||||
headers = get_response_headers(dict(_response_headers))
|
||||
|
||||
# The bare retry-after header should be present for OpenAI SDK compat
|
||||
assert headers.get("retry-after") == "57"
|
||||
# The prefixed version should also be present
|
||||
assert headers.get("llm_provider-retry-after") == "57"
|
||||
# OpenAI-compatible rate limit headers should be preserved
|
||||
assert headers.get("x-ratelimit-limit-tokens") == "100000"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue