Skip to content

Commit 430c461

Browse files
committed
fix(router): unify route_request to async with optional request_json
Signed-off-by: Nejc Habjan <nejc.habjan@siemens.com>
1 parent 7f013f2 commit 430c461

3 files changed

Lines changed: 30 additions & 38 deletions

File tree

src/tests/test_roundrobin_router.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import random
22
from typing import Dict, List, Tuple
33

4+
import pytest
5+
46
from vllm_router.routers.routing_logic import RoundRobinRouter
57

68

@@ -47,21 +49,24 @@ def generate_request(request_type="http") -> Request:
4749
return Request({"type": request_type})
4850

4951

50-
def test_roundrobin_logic(
52+
@pytest.mark.asyncio
53+
async def test_roundrobin_logic(
5154
dynamic_discoveries: int = 10, max_endpoints: int = 1000, max_requests: int = 10000
5255
):
5356
"""
5457
Ensure that all active urls have roughly same number of requests (difference at most 1)
5558
"""
5659
router = RoundRobinRouter()
5760

58-
def _fixed_router_check(num_endpoints: int, num_requests: int) -> bool:
61+
async def _fixed_router_check(num_endpoints: int, num_requests: int) -> bool:
5962
# Make num_requests requests to the router and check even output distribution
6063
endpoints, engine_stats, request_stats = generate_request_args(num_endpoints)
6164
output_distribution = {}
6265
for request_idx in range(num_requests):
6366
request = generate_request()
64-
url = router.route_request(endpoints, engine_stats, request_stats, request)
67+
url = await router.route_request(
68+
endpoints, engine_stats, request_stats, request
69+
)
6570
output_distribution[url] = output_distribution.get(url, 0) + 1
6671
request_counts = output_distribution.values()
6772
return max(request_counts) - min(request_counts) <= 1
@@ -70,5 +75,5 @@ def _fixed_router_check(num_endpoints: int, num_requests: int) -> bool:
7075
num_endpoints = random.randint(1, max_endpoints)
7176
num_requests = random.randint(1, max_requests)
7277
# Perform router check
73-
res = _fixed_router_check(num_endpoints, num_requests)
78+
res = await _fixed_router_check(num_endpoints, num_requests)
7479
assert res

src/vllm_router/routers/routing_logic.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,16 @@ def extract_session_id(self, request: Request, request_json: Dict) -> Optional[s
113113
if session_key is None:
114114
return None
115115
val = request.headers.get(session_key)
116-
return val if val is not None else request_json.get(session_key, None)
116+
return val if val is not None else (request_json or {}).get(session_key, None)
117117

118118
@abc.abstractmethod
119-
def route_request(
119+
async def route_request(
120120
self,
121121
endpoints: List[EndpointInfo],
122122
engine_stats: Dict[str, EngineStats],
123123
request_stats: Dict[str, RequestStats],
124124
request: Request,
125+
request_json: Optional[Dict] = None,
125126
) -> str:
126127
"""
127128
Route the request to the appropriate engine URL
@@ -149,12 +150,13 @@ def __init__(self):
149150
self.last_endpoints_hash = None
150151
self._initialized = True
151152

152-
def route_request(
153+
async def route_request(
153154
self,
154155
endpoints: List[EndpointInfo],
155156
engine_stats: Dict[str, EngineStats],
156157
request_stats: Dict[str, RequestStats],
157158
request: Request,
159+
request_json: Optional[Dict] = None,
158160
) -> str:
159161
"""
160162
Route the request to the appropriate engine URL using a simple
@@ -201,7 +203,7 @@ async def route_request(
201203
engine_stats: Dict[str, EngineStats],
202204
request_stats: Dict[str, RequestStats],
203205
request: Request,
204-
request_json: Dict,
206+
request_json: Optional[Dict] = None,
205207
) -> str:
206208
"""
207209
Route the request to the appropriate engine URL by the 'session id' in
@@ -320,7 +322,7 @@ async def route_request(
320322
engine_stats: Dict[str, EngineStats],
321323
request_stats: Dict[str, RequestStats],
322324
request: Request,
323-
request_json: Dict,
325+
request_json: Optional[Dict] = None,
324326
) -> str:
325327
"""
326328
Route the request to the appropriate engine URL by where the KV cache
@@ -346,14 +348,14 @@ async def route_request(
346348
self.tokenizer = AutoTokenizer.from_pretrained(
347349
endpoints[0].model_names[0]
348350
)
349-
token_ids = self.tokenizer.encode(request_json.get("prompt", ""))
351+
token_ids = self.tokenizer.encode((request_json or {}).get("prompt", ""))
350352
except Exception:
351353
# Remote /tokenize fallback (let errors bubble up to keep behavior simple)
352354
remote_url = endpoints[0].url + "/tokenize"
353355
headers = {"Content-Type": "application/json"}
354356
data = {
355357
"model": endpoints[0].model_names[0],
356-
"prompt": request_json.get("prompt", ""),
358+
"prompt": (request_json or {}).get("prompt", ""),
357359
}
358360
body = requests.post(
359361
remote_url, headers=headers, json=data, timeout=10
@@ -435,7 +437,7 @@ async def route_request(
435437
engine_stats: Dict[str, EngineStats],
436438
request_stats: Dict[str, RequestStats],
437439
request: Request,
438-
request_json: Dict,
440+
request_json: Optional[Dict] = None,
439441
) -> str:
440442
"""
441443
Route the request to the appropriate engine URL by where the longest
@@ -455,9 +457,10 @@ async def route_request(
455457
"""
456458

457459
# Handle chat completions
458-
if "messages" in request_json:
460+
_request_json = request_json or {}
461+
if "messages" in _request_json:
459462
# Get the last message from the messages array
460-
messages = request_json["messages"]
463+
messages = _request_json["messages"]
461464
if messages:
462465
# Concatenate all message content
463466
prompt_parts = []
@@ -478,7 +481,7 @@ async def route_request(
478481
prompt = ""
479482
else:
480483
# Handle regular completions
481-
prompt = request_json["prompt"]
484+
prompt = _request_json["prompt"]
482485

483486
available_endpoints = set(endpoint.url for endpoint in endpoints)
484487
_, matched_endpoint = await self.hashtrie.longest_prefix_match(
@@ -503,20 +506,20 @@ def __init__(self, prefill_model_labels: List[str], decode_model_labels: List[st
503506
self.decode_model_labels = decode_model_labels
504507
self.request_cache = {} # Cache to store prefill results
505508

506-
def route_request(
509+
async def route_request(
507510
self,
508511
endpoints: List[EndpointInfo],
509512
engine_stats: Dict[str, EngineStats],
510513
request_stats: Dict[str, RequestStats],
511514
request: Request,
512-
request_json: Dict,
515+
request_json: Optional[Dict] = None,
513516
) -> str:
514517
"""
515518
Route the request to appropriate endpoints for prefill and decode operations.
516519
First request goes to prefill endpoint, then second request goes to decode endpoint.
517520
"""
518521
# Find prefill and decode endpoints
519-
is_prefill = request_json.get("max_tokens", 0) == 1
522+
is_prefill = (request_json or {}).get("max_tokens", 0) == 1
520523
if is_prefill:
521524
logger.info("Prefill request")
522525
else:
@@ -631,7 +634,7 @@ async def route_request(
631634
engine_stats: Dict[str, EngineStats],
632635
request_stats: Dict[str, RequestStats],
633636
request: Request,
634-
request_json: Dict,
637+
request_json: Optional[Dict] = None,
635638
) -> str:
636639
"""
637640
This method is called by the router framework but for orchestrated routing,

src/vllm_router/services/request_service/request.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@
3030
from vllm_router.routers.routing_logic import (
3131
DisaggregatedPrefillOrchestratedRouter,
3232
DisaggregatedPrefillRouter,
33-
KvawareRouter,
34-
PrefixAwareRouter,
35-
SessionRouter,
3633
)
3734
from vllm_router.service_discovery import get_service_discovery
3835
from vllm_router.services.request_service.rewriter import (
@@ -525,16 +522,10 @@ async def route_general_request(
525522
f"Routing request {request_id} to engine with Id: {endpoints[0].Id}"
526523
)
527524

528-
elif isinstance(
529-
request.app.state.router, (KvawareRouter, PrefixAwareRouter, SessionRouter)
530-
):
525+
else:
531526
server_url = await request.app.state.router.route_request(
532527
endpoints, engine_stats, request_stats, request, request_json
533528
)
534-
else:
535-
server_url = request.app.state.router.route_request(
536-
endpoints, engine_stats, request_stats, request
537-
)
538529

539530
curr_time = time.time()
540531
# Extract actual session ID from request headers for logging
@@ -572,17 +563,10 @@ async def route_general_request(
572563
break
573564
if request_endpoint:
574565
server_url = remaining[0].url
575-
elif isinstance(
576-
request.app.state.router,
577-
(KvawareRouter, PrefixAwareRouter, SessionRouter),
578-
):
566+
else:
579567
server_url = await request.app.state.router.route_request(
580568
remaining, engine_stats, request_stats, request, request_json
581569
)
582-
else:
583-
server_url = request.app.state.router.route_request(
584-
remaining, engine_stats, request_stats, request
585-
)
586570
logger.info(
587571
f"Routing request {request_id} to {server_url} "
588572
f"(attempt {attempt + 1}/{max_attempts})"
@@ -1189,7 +1173,7 @@ async def proxy_multipart_request(
11891173
request_stats = request_stats_monitor.get_request_stats(time.time())
11901174

11911175
# pick one using the router's configured logic (roundrobin, least-loaded, etc.)
1192-
chosen_url = router.route_request(
1176+
chosen_url = await router.route_request(
11931177
endpoints,
11941178
engine_stats,
11951179
request_stats,

0 commit comments

Comments
 (0)