[Frontend] Make `model` optional on all `/derender` request classes (#51463)

Signed-off-by: Vinay Damodaran <vrdn@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

Vinay R Damodaran committed Aug 11, 2026 at 21:48 UTC 3ee2df30337a301164c46ae444b76ee67e71c106
3 files changed +37 -14
tests/entrypoints/scale_out/derender/test_derender.py
+19
@@ -308,6 +308,25 @@ async def test_derender_chat_unknown_model(client):
308 assert response.status_code == 404
309
310
311 +@pytest.mark.asyncio
312 +async def test_derender_chat_model_omitted_resolves_served_name(client):
313 + """Omitting `model` resolves the served name rather than rejecting.
314 +
315 + Mirrors test_serving_chat.py's "full name is returned when no model is
316 + specified" assertion for the derender path. Asserts the resolved value,
317 + not just the status, so the fallback is proven to have fired.
318 + """
319 + gen_req = await _render_chat(client)
320 + synthetic_ids = gen_req["token_ids"][:3]
321 +
322 + response = await client.post(
323 + "/v1/chat/completions/derender",
324 + json={"generate_response": _make_generate_response(synthetic_ids)},
325 + )
326 + assert response.status_code == 200
327 + assert response.json()["model"] == MODEL_NAME
328 +
329 +
330 # ---------------------------------------------------------------------------
331 # Completion derender tests
332 # ---------------------------------------------------------------------------
vllm/entrypoints/scale_out/derender/serving.py
+12 -8
@@ -166,16 +166,17 @@ class ServingDerender(BaseServing):
166 total_tokens=prompt_tokens + completion_tokens,
167 )
168
169 + model_name = request.model or self.models.model_name()
170 logger.debug(
171 "derender_chat request_id=%s model=%s choices=%d completion_tokens=%d",
172 gen.request_id,
172 - request.model,
173 + model_name,
174 len(choices),
175 completion_tokens,
176 )
177 return ChatCompletionResponse(
178 id=gen.request_id,
178 - model=request.model,
179 + model=model_name,
180 created=int(time.time()),
181 choices=choices,
182 usage=usage,
@@ -231,17 +232,18 @@ class ServingDerender(BaseServing):
232 total_tokens=total_prompt_tokens + total_completion_tokens,
233 )
234
235 + model_name = request.model or self.models.model_name()
236 logger.debug(
237 "derender_completion request_id=%s model=%s choices=%d"
238 " completion_tokens=%d",
239 first.request_id,
238 - request.model,
240 + model_name,
241 len(choices),
242 total_completion_tokens,
243 )
244 return CompletionResponse(
245 id=first.request_id,
244 - model=request.model,
246 + model=model_name,
247 created=int(time.time()),
248 choices=choices,
249 usage=usage,
@@ -264,9 +266,10 @@ class ServingDerender(BaseServing):
266 if error_check_ret is not None:
267 return error_check_ret
268
269 + model_name = request.model or self.models.model_name()
270 try:
271 chunk, updated_state = await self.online_derenderer.derender_chat_stream(
269 - model=request.model,
272 + model=model_name,
273 generate_chunk=request.generate_chunk,
274 state=request.stream_state,
275 chat_request=request.chat_request,
@@ -284,7 +287,7 @@ class ServingDerender(BaseServing):
287 logger.debug(
288 "derender_chat_stream request_id=%s model=%s delta_tokens=%d",
289 request.generate_chunk.request_id,
287 - request.model,
290 + model_name,
291 sum(
292 len(c.token_ids) for c in request.generate_chunk.choices if c.token_ids
293 ),
@@ -304,12 +307,13 @@ class ServingDerender(BaseServing):
307 if error_check_ret is not None:
308 return error_check_ret
309
310 + model_name = request.model or self.models.model_name()
311 try:
312 (
313 chunk,
314 updated_state,
315 ) = await self.online_derenderer.derender_completion_stream(
312 - model=request.model,
316 + model=model_name,
317 generate_chunk=request.generate_chunk,
318 state=request.stream_state,
319 prompt_tokens=request.prompt_tokens,
@@ -325,7 +329,7 @@ class ServingDerender(BaseServing):
329 logger.debug(
330 "derender_completion_stream request_id=%s model=%s delta_tokens=%d",
331 request.generate_chunk.request_id,
328 - request.model,
332 + model_name,
333 sum(
334 len(c.token_ids) for c in request.generate_chunk.choices if c.token_ids
335 ),
vllm/entrypoints/scale_out/token_in_token_out/protocol.py
+6 -6
@@ -277,8 +277,8 @@ class DerenderChatRequest(BaseModel):
277 # --8<-- [start:derender-chat-request]
278 stream: Literal[False] = False
279
280 - model: str
281 - """Served model name."""
280 + model: str | None = None
281 + """Served model name. Defaults to the server's served model name."""
282
283 generate_response: GenerateResponse
284 """The complete token-in / token-out engine response to derender."""
@@ -311,8 +311,8 @@ class DerenderCompletionRequest(BaseModel):
311 # --8<-- [start:derender-completion-request]
312 stream: Literal[False] = False
313
314 - model: str
315 - """Served model name."""
314 + model: str | None = None
315 + """Served model name. Defaults to the server's served model name."""
316
317 generate_responses: list[GenerateResponse]
318 """One response per prompt, parallel to the list[GenerateRequest]
@@ -428,7 +428,7 @@ class DerenderChatStreamRequest(BaseModel):
428
429 stream: Literal[True]
430
431 - model: str
431 + model: str | None = None
432 generate_chunk: GenerateStreamResponse
433 """One SSE chunk from ``/inference/v1/generate`` (``stream=True``)."""
434
@@ -452,7 +452,7 @@ class DerenderCompletionStreamRequest(BaseModel):
452
453 stream: Literal[True]
454
455 - model: str
455 + model: str | None = None
456 generate_chunk: GenerateStreamResponse
457 """One SSE chunk from ``/inference/v1/generate``."""
458