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 <noreply@anthropic.com>
This commit is contained in:
rudra717 2026-04-04 17:04:09 -07:00
parent 8ecbf757b2
commit 1477f64079
2 changed files with 59 additions and 1 deletions

View file

@ -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:

View file

@ -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