This commit is contained in:
hcl 2026-09-07 10:25:58 +00:00 committed by GitHub
commit ab53c3d5fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 2 deletions

View file

@ -989,6 +989,23 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger):
self.internal_usage_cache.dual_cache.redis_cache, RedisClusterCache
)
@staticmethod
def _force_hash_tag_grouping_enabled() -> bool:
"""
Some non-OSS-Cluster Redis backends (e.g. Azure Redis Enterprise /
Azure Managed Redis with the EnterpriseCluster clustering policy)
enforce cross-slot restrictions but expose a single endpoint and
don't use the OSS Cluster protocol, so ``_is_redis_cluster()`` is
``False`` for them. Without hash-tag grouping, batched EVALSHA
pipelines fail with ``CROSSSLOT Keys in request don't hash to the
same slot``. Users on those backends can set
``litellm.force_redis_hash_tag_grouping = True`` to opt into the
slot-grouping path.
"""
import litellm
return bool(getattr(litellm, "force_redis_hash_tag_grouping", False))
async def in_memory_cache_sliding_window(
self,
keys: list[str],
@ -1154,8 +1171,11 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger):
"""
groups: Final[dict[str, list[str]]] = {}
# Use slot calculation for Redis clusters only
if self._is_redis_cluster():
# Use slot calculation for Redis clusters, or for non-OSS-Cluster
# backends that still enforce cross-slot restrictions (Azure Redis
# Enterprise with EnterpriseCluster policy), opted into via
# ``litellm.force_redis_hash_tag_grouping = True``.
if self._is_redis_cluster() or self._force_hash_tag_grouping_enabled():
for key in keys:
slot = self.keyslot_for_redis_cluster(key)
slot_key = f"slot_{slot}"

View file

@ -1874,6 +1874,66 @@ def test_group_keys_by_hash_tag_redis_cluster():
), "All keys should be present in groups"
def test_group_keys_by_hash_tag_force_grouping_flag():
"""
Regression for #30065. Non-OSS-Cluster Redis backends (e.g. Azure Redis
Enterprise with EnterpriseCluster policy) enforce cross-slot
restrictions but ``_is_redis_cluster()`` returns False for them, so
batched EVALSHA pipelines fail with CROSSSLOT. Users opt into
slot-grouping via ``litellm.force_redis_hash_tag_grouping = True``.
"""
import litellm
local_cache = DualCache()
handler = _PROXY_MaxParallelRequestsHandler(
internal_usage_cache=InternalUsageCache(local_cache)
)
keys_two_slots = [
"{REDACTED}:max_parallel_requests",
"{team:1f44b322-dead-41d9-9244-09515df45269}:tokens",
]
# Defensive: clear any prior test leakage before asserting default behaviour.
setattr(litellm, "force_redis_hash_tag_grouping", False)
# Default: no flag, non-cluster cache -> single group (current behaviour).
groups_default = handler._group_keys_by_hash_tag(keys_two_slots)
assert len(groups_default) == 1
assert "all_keys" in groups_default
# With the opt-in flag, two different hash tags must land in two
# distinct slot groups even though _is_redis_cluster() is False.
setattr(litellm, "force_redis_hash_tag_grouping", True)
try:
groups_forced = handler._group_keys_by_hash_tag(keys_two_slots)
finally:
setattr(litellm, "force_redis_hash_tag_grouping", False)
assert all(g.startswith("slot_") for g in groups_forced)
assert (
len(groups_forced) == 2
), f"Two different hash tags must be in two slot groups, got {groups_forced}"
def test_force_hash_tag_grouping_enabled_default_false():
"""
Without the opt-in flag set, ``_force_hash_tag_grouping_enabled`` is
False so the non-cluster code path is unchanged for users on plain
Redis / standalone deployments.
"""
import litellm
# Defensive: clear any prior test leakage.
setattr(litellm, "force_redis_hash_tag_grouping", False)
handler = _PROXY_MaxParallelRequestsHandler(
internal_usage_cache=InternalUsageCache(DualCache())
)
assert handler._force_hash_tag_grouping_enabled() is False
def test_keyslot_for_redis_cluster():
"""
Test the keyslot calculation for Redis cluster.