Skip to content

Fix Responses API null field serialization - #47184

Closed
shenoyvvarun wants to merge 1 commit into
vllm-project:mainfrom
shenoyvvarun:fix/responses-openapi-null-serialization
Closed

shenoyvvarun wants to merge 1 commit into
vllm-project:mainfrom
shenoyvvarun:fix/responses-openapi-null-serialization

Conversation

@shenoyvvarun

@shenoyvvarun shenoyvvarun commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #47200.

Align the Responses API JSON serialization with OpenAI OpenAPI nullable semantics.

This fixes cases where vLLM serialized null for fields that are optional but not nullable in the OpenAI schema, while preserving fields that are explicitly nullable.

This is breaking SMG compatibility for the Responses API because SMG strictly deserializes the response schema and fails with:

Failed to deserialize as ResponsesResponse at output[1]: invalid type: null, expected a string

Changes

  • Add error: null to successful ResponsesResponse objects, matching the OpenAI Response schema.
  • Omit non-nullable None fields only at their exact response positions:
    • top-level response: text, user, kv_transfer_params, input_messages, output_messages
    • output items: output[*].status, output[*].namespace
    • tools: tools[*].defer_loading
  • Keep spec-nullable fields as null, including incomplete_details, instructions, metadata, max_tool_calls, previous_response_id, prompt, reasoning, top_logprobs, encrypted_content, and function tool strict.
  • Apply the same position-aware cleanup to streaming SSE payloads.
  • Add regression coverage to ensure nested payloads/tool schemas with keys like status: null are preserved.

Testing

  • python -m py_compile vllm/entrypoints/openai/responses/protocol.py vllm/entrypoints/openai/responses/api_router.py tests/entrypoints/openai/responses/test_protocol.py
  • git diff --check
  • Live H100 validation with gpt-oss-120b:

before patch reproduced invalid null fields

{
  "id": "resp_8a560fdbb5350e2a",
  "created_at": 1782764749,
  "incomplete_details": null,
  "instructions": null,
  "metadata": null,
  "model": "vllm-model",
  "object": "response",
  "output": [
      {
          "id": "rs_89b849e20d9888a1",
          "summary": [],
          "type": "reasoning",
          "content": [
              {
                  "text": "The user wants horoscope for the person in the file. There's a file? Not provided. Possibly they referenced a file but not given. Maybe we need to ask for clarification or assume they want generic Aquarius horoscope. The user says \"What is the horoscope of the person in the file? She is an Aquarius.\" Since we don't have the file content, we can just provide generic Aquarius horoscope. Could also ask for more details. Likely respond with horoscope for Aquarius using the function get_horoscope. Use function: get_horoscope with sign \"Aquarius\".",
                  "type": "reasoning_text"
              }
          ],
          "encrypted_content": null,
          "status": null
      },
      {
          "arguments": "{\n  \"sign\": \"Aquarius\"\n}",
          "call_id": "call_a9ffe49ff074b8f0",
          "name": "get_horoscope",
          "type": "function_call",
          "id": "fc_a9ffe49ff074b8f0",
          "namespace": null,
          "status": null
      }
  ],
  "parallel_tool_calls": true,
  "temperature": 1.0,
  "tool_choice": "auto",
  "tools": [
      {
          "name": "get_horoscope",
          "parameters": {
              "type": "object",
              "properties": {
                  "sign": {
                      "type": "string",
                      "description": "An astrological sign like Taurus or Aquarius"
                  }
              },
              "required": [
                  "sign"
              ]
          },
          "strict": null,
          "type": "function",
          "defer_loading": null,
          "description": "Get todays horoscope for an astrological sign."
      }
  ],
  "top_p": 1.0,
  "background": false,
  "max_output_tokens": 130923,
  "max_tool_calls": null,
  "previous_response_id": null,
  "prompt": null,
  "reasoning": null,
  "service_tier": "auto",
  "status": "completed",
  "text": null,
  "top_logprobs": null,
  "truncation": "disabled",
  "usage": {
      "input_tokens": 149,
      "input_tokens_details": {
          "cached_tokens": 32,
          "input_tokens_per_turn": [
              149
          ],
          "cached_tokens_per_turn": [
              32
          ]
      },
      "output_tokens": 144,
      "output_tokens_details": {
          "reasoning_tokens": 125,
          "tool_output_tokens": 0,
          "output_tokens_per_turn": [
              144
          ],
          "tool_output_tokens_per_turn": [
              0
          ]
      },
      "total_tokens": 293
  },
  "user": null,
  "presence_penalty": 0.0,
  "frequency_penalty": 0.0,
  "kv_transfer_params": null,
  "input_messages": null,
  "output_messages": null
}

After no null fields

{
  "id": "resp_8b59f7dd2f6d3c92",
  "created_at": 1782765082,
  "incomplete_details": null,
  "instructions": null,
  "metadata": null,
  "model": "vllm-model",
  "object": "response",
  "error": null,
  "output": [
      {
          "id": "rs_8ae3781b7a60755d",
          "summary": [],
          "type": "reasoning",
          "content": [
              {
                  "text": "We need to get horoscope for Aquarius. Use function get_horoscope.",
                  "type": "reasoning_text"
              }
          ],
          "encrypted_content": null
      },
      {
          "arguments": "{\n  \"sign\": \"Aquarius\"\n}",
          "call_id": "call_91379d66d5eea525",
          "name": "get_horoscope",
          "type": "function_call",
          "id": "fc_91379d66d5eea525"
      }
  ],
  "parallel_tool_calls": true,
  "temperature": 1.0,
  "tool_choice": "auto",
  "tools": [
      {
          "name": "get_horoscope",
          "parameters": {
              "type": "object",
              "properties": {
                  "sign": {
                      "type": "string",
                      "description": "An astrological sign like Taurus or Aquarius"
                  }
              },
              "required": [
                  "sign"
              ]
          },
          "strict": null,
          "type": "function",
          "description": "Get todays horoscope for an astrological sign."
      }
  ],
  "top_p": 1.0,
  "background": false,
  "max_output_tokens": 130923,
  "max_tool_calls": null,
  "previous_response_id": null,
  "prompt": null,
  "reasoning": null,
  "service_tier": "auto",
  "status": "completed",
  "top_logprobs": null,
  "truncation": "disabled",
  "usage": {
      "input_tokens": 149,
      "input_tokens_details": {
          "cached_tokens": 0,
          "input_tokens_per_turn": [
              149
          ],
          "cached_tokens_per_turn": [
              0
          ]
      },
      "output_tokens": 46,
      "output_tokens_details": {
          "reasoning_tokens": 27,
          "tool_output_tokens": 0,
          "output_tokens_per_turn": [
              46
          ],
          "tool_output_tokens_per_turn": [
              0
          ]
      },
      "total_tokens": 195
  },
  "presence_penalty": 0.0,
  "frequency_penalty": 0.0
}

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added the frontend label Jun 30, 2026
Signed-off-by: Varun Shenoy <varun.vinayak.shenoy@oracle.com>
@shenoyvvarun

Copy link
Copy Markdown
Contributor Author

@chaunceyjiang Could you provide any thoughts on this?

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity within 90 days. It will be automatically closed if no further activity occurs within 30 days. Leave a comment if you feel this pull request should remain open. Thank you!

@github-actions

Copy link
Copy Markdown

This pull request has been automatically closed due to inactivity. Please feel free to reopen if you intend to continue working on it. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend stale Over 90 days of inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Responses API null serialization breaks SMG deserialization

1 participant