@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c7aa186d67
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 5, 07:15 UTC

[Frontend] Supports resubmitting output items with missing fields in Responses API (#41355)

Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com>

Chauncey committed May 6, 2026 at 10:21 UTC c7aa186d67b6f051680831418e957c67f34ba7a2
1 file changed +60 -10
vllm/entrypoints/openai/responses/protocol.py
+60 -10
@@ -23,7 +23,9 @@ from openai.types.responses import (
23 ResponseOutputItem,
24 ResponseOutputItemAddedEvent,
25 ResponseOutputItemDoneEvent,
26 + ResponseOutputMessage,
27 ResponsePrompt,
28 + ResponseReasoningItem,
29 ResponseReasoningTextDeltaEvent,
30 ResponseReasoningTextDoneEvent,
31 ResponseStatus,
@@ -451,18 +453,21 @@ class ResponsesRequest(OpenAIBaseModel):
453
454 @model_validator(mode="before")
455 @classmethod
454 - def function_call_parsing(cls, data):
455 - """Parse function_call dictionaries into ResponseFunctionToolCall objects.
456 - This ensures Pydantic can properly resolve union types in the input field.
457 - Function calls provided as dicts are converted to ResponseFunctionToolCall
458 - objects before validation, while invalid structures are left for Pydantic
459 - to reject with appropriate error messages.
460 - """
456 + def input_item_parsing(cls, data):
457 + """Parse input items that are missing required fields or that Pydantic
458 + cannot disambiguate in a Union of TypedDict / BaseModel types.
459 +
460 + Specifically handles:
461 + - function_call -> ResponseFunctionToolCall
462 + - reasoning -> ResponseReasoningItem (auto-generates id)
463 + - message(role=assistant) -> ResponseOutputMessage (auto-generates
464 + id/status and annotations)
465
466 + Invalid structures are left for Pydantic to reject.
467 + """
468 input_data = data.get("input")
469
470 # Early return for None, strings, or bytes
465 - # (strings are iterable but shouldn't be processed)
471 if input_data is None or isinstance(input_data, (str, bytes)):
472 return data
473
@@ -476,16 +481,61 @@ class ResponsesRequest(OpenAIBaseModel):
481
482 processed_input = []
483 for item in input_data:
479 - if isinstance(item, dict) and item.get("type") == "function_call":
484 + if not isinstance(item, dict):
485 + processed_input.append(item)
486 + continue
487 +
488 + item_type = item.get("type")
489 +
490 + if item_type == "function_call":
491 try:
492 processed_input.append(ResponseFunctionToolCall(**item))
493 except ValidationError:
483 - # Let Pydantic handle validation for malformed function calls
494 logger.debug(
495 "Failed to parse function_call to ResponseFunctionToolCall, "
496 "leaving for Pydantic validation"
497 )
498 processed_input.append(item)
499 +
500 + elif item_type == "reasoning":
501 + if "id" not in item:
502 + item = {**item, "id": f"rs_{random_uuid()}"}
503 + try:
504 + processed_input.append(ResponseReasoningItem(**item))
505 + except ValidationError:
506 + logger.debug(
507 + "Failed to parse reasoning to ResponseReasoningItem, "
508 + "leaving for Pydantic validation"
509 + )
510 + processed_input.append(item)
511 +
512 + elif item_type == "message" and item.get("role") == "assistant":
513 + item = dict(item)
514 + if "id" not in item:
515 + item["id"] = f"msg_{random_uuid()}"
516 + if "status" not in item:
517 + item["status"] = "completed"
518 + # ResponseOutputText requires annotations
519 + if isinstance(item.get("content"), list):
520 + new_content = []
521 + for c in item["content"]:
522 + if (
523 + isinstance(c, dict)
524 + and c.get("type") == "output_text"
525 + and "annotations" not in c
526 + ):
527 + c = {**c, "annotations": []}
528 + new_content.append(c)
529 + item["content"] = new_content
530 + try:
531 + processed_input.append(ResponseOutputMessage(**item))
532 + except ValidationError:
533 + logger.debug(
534 + "Failed to parse assistant message to ResponseOutputMessage, "
535 + "leaving for Pydantic validation"
536 + )
537 + processed_input.append(item)
538 +
539 else:
540 processed_input.append(item)
541