fix(rate_limiter): skip non-Latin-1 x-litellm-priority header on /v1/messages (#40636)

A team or key priority that is not Latin-1 encodable (for example CJK text) was
attached as a response header by the dynamic rate limiter v3 post-call hook, and
Starlette then raised UnicodeEncodeError while writing headers, turning a
successful /v1/messages call into HTTP 500. The header is now omitted for such
values while x-litellm-rate-limiter-version and the v3 rate limit headers are
still attached.

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-10 17:48:46 -07:00 committed by GitHub
parent c7a41c35d5
commit 985ac6b6a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 1 deletions

View file

@ -59,6 +59,10 @@ def _get_priority_settings() -> "PriorityReservationSettings":
return settings
def _is_latin1_encodable(value: object) -> bool:
return all(ord(char) < 256 for char in str(value))
class _PROXY_DynamicRateLimitHandlerV3(CustomLogger):
"""
Saturation-aware priority-based rate limiter using v3 infrastructure.
@ -666,7 +670,13 @@ class _PROXY_DynamicRateLimitHandlerV3(CustomLogger):
if response_has_hidden_params(response):
priority: Final = self._get_priority_from_user_api_key_dict(user_api_key_dict=user_api_key_dict)
additional_headers: Final = ensure_response_additional_headers(response)
additional_headers["x-litellm-priority"] = priority or "default"
priority_header: Final = priority or "default"
if _is_latin1_encodable(priority_header):
additional_headers["x-litellm-priority"] = priority_header
else:
verbose_proxy_logger.debug(
"Skipping x-litellm-priority header: priority %r is not Latin-1 encodable", priority
)
additional_headers["x-litellm-rate-limiter-version"] = "v3"
return response

View file

@ -1918,3 +1918,30 @@ async def test_post_call_success_hook_leaves_raw_provider_dict_untouched():
)
assert response == {"id": "msg_123", "type": "message", "role": "assistant", "content": []}
@pytest.mark.asyncio
@pytest.mark.parametrize(
("team_metadata", "expected_priority_header"),
[
({"priority": "优先"}, None),
({"priority": "high"}, "high"),
({}, "default"),
],
)
async def test_post_call_success_hook_priority_header_is_always_http_encodable(team_metadata, expected_priority_header):
from starlette.responses import Response
handler = DynamicRateLimitHandler(internal_usage_cache=DualCache())
response = {"id": "msg_123", "type": "message", "role": "assistant", "content": [], "_hidden_params": {}}
await handler.async_post_call_success_hook(
data={"model": "anthropic-haiku"},
user_api_key_dict=UserAPIKeyAuth(team_id="team-1", team_metadata=team_metadata),
response=response,
)
additional_headers = response["_hidden_params"]["additional_headers"]
http_response = Response(headers={key: str(value) for key, value in additional_headers.items()})
assert http_response.headers.get("x-litellm-priority") == expected_priority_header
assert http_response.headers["x-litellm-rate-limiter-version"] == "v3"