From 1477f64079269c653014e56bada40ab2eb24b0f7 Mon Sep 17 00:00:00 2001 From: rudra717 <52209277+rudra717@users.noreply.github.com> Date: Sat, 4 Apr 2026 17:04:09 -0700 Subject: [PATCH] fix(scheduler): normalize Redis-deserialized queue items to tuples JSON serialization converts Python tuples to lists. When Scheduler reads the priority queue back from Redis, the deserialized list items cause TypeError in heapq.heappush when compared against new tuple items: '<' not supported between instances of 'tuple' and 'list'. Normalize queue items back to tuples in get_queue() so heapq comparisons work consistently. Includes a unit test that simulates the JSON round-trip and verifies add_request works after a cache read. Fixes BerriAI/litellm#25157 Co-Authored-By: Claude --- litellm/scheduler.py | 5 +- .../test_scheduler_redis_tuple.py | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/test_scheduler_redis_tuple.py diff --git a/litellm/scheduler.py b/litellm/scheduler.py index 5309971eeda..41eb0faace4 100644 --- a/litellm/scheduler.py +++ b/litellm/scheduler.py @@ -137,7 +137,10 @@ class Scheduler: if response is None or not isinstance(response, list): return [] elif isinstance(response, list): - return response + # JSON deserializes tuples as lists. heapq requires + # consistent types for comparison, so normalize back + # to tuples to match what heapq.heappush produces. + return [tuple(item) if isinstance(item, list) else item for item in response] return self.queue async def save_queue(self, queue: list, model_name: str) -> None: diff --git a/tests/test_litellm/test_scheduler_redis_tuple.py b/tests/test_litellm/test_scheduler_redis_tuple.py new file mode 100644 index 00000000000..43c3344e510 --- /dev/null +++ b/tests/test_litellm/test_scheduler_redis_tuple.py @@ -0,0 +1,55 @@ +""" +Test that Scheduler.get_queue normalizes JSON-deserialized lists back to tuples. + +When Redis is used as the cache backend, JSON serialization converts tuples +to lists. heapq requires consistent types for comparison, so get_queue must +normalize items back to tuples. + +Regression test for https://github.com/BerriAI/litellm/issues/25157 +""" + +import pytest + +from litellm.scheduler import FlowItem, Scheduler + + +class FakeRedisCache: + """Minimal stub that simulates Redis returning JSON-deserialized data.""" + + def __init__(self): + self.store: dict = {} + + async def async_get_cache(self, key, **kwargs): + return self.store.get(key) + + async def async_set_cache(self, key, value, **kwargs): + # Simulate JSON round-trip: tuples become lists + import json + + self.store[key] = json.loads(json.dumps(value)) + + +@pytest.mark.asyncio +async def test_scheduler_add_request_after_redis_roundtrip(): + """ + After a Redis round-trip, queue items are lists (not tuples). + Adding a new request should not raise TypeError from heapq comparison. + """ + fake_redis = FakeRedisCache() + scheduler = Scheduler(redis_cache=fake_redis) + + # First request — goes into an empty queue, no comparison needed + item1 = FlowItem(priority=0, request_id="req-1", model_name="test-model") + await scheduler.add_request(item1) + + # Second request — queue from cache has list items, heappush must compare + # Without the fix, this raises: + # TypeError: '<' not supported between instances of 'tuple' and 'list' + item2 = FlowItem(priority=1, request_id="req-2", model_name="test-model") + await scheduler.add_request(item2) + + # Verify both items are in the queue + queue = await scheduler.get_queue(model_name="test-model") + request_ids = {item[1] for item in queue} + assert "req-1" in request_ids + assert "req-2" in request_ids