Skip to content

[Bugfix] Responses API assistant EasyInputMessageParam input - #44361

Merged
bbrowning merged 3 commits into
vllm-project:mainfrom
yzong-rh:yzong-rh/responses-easy-input
Jun 23, 2026
Merged

bbrowning merged 3 commits into
vllm-project:mainfrom
yzong-rh:yzong-rh/responses-easy-input

Conversation

@yzong-rh

@yzong-rh yzong-rh commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Fix Responses API running into Pydantic errors on assistant EasyInputMessageParams with string content (anything like {"role": "assistant", "type": "message", "content": "some string"})

The error was caused by assistant string content being incorrectly coerced into ResponseOutputMessage, causing Pydantic to try parsing each character of the string as an output content item.

This PR fixes the issue by not coercing EasyInputMessageParam into ResponseOutputMessage. EasyInputMessageParam is also now handled similarly to ResponseOutputMessage in construct_chat_messages_with_tool_call.

Test Plan

Updated unit test files:

  • tests/entrypoints/openai/responses/test_function_call_parsing.py
  • tests/entrypoints/openai/responses/test_responses_utils.py

Manual e2e validation:

  • Non-harmony Responses server with Qwen/Qwen3-8B
  • Harmony Responses server with openai/gpt-oss-20b

Send request:

curl -s http://localhost:<PORT>/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<MODEL>",
    "input": [
      {"role": "user", "content": "What is 5 * 3?"},
      {"role": "assistant", "type": "message", "content": "15. QED."},
      {"role": "user", "content": "Multiply the result by 2 and end with QED."}
    ],
    "enable_response_messages": true,
    "temperature": 0.0
  }' | jq .

Test Result

Unit tests pass.

Non-harmony Results:

Before:

  • Request failed with BadRequestError
  • Error included:
    • 2 validation errors for ValidatorIterator
    • 0.ResponseOutputTextParam
    • 0.ResponseOutputRefusalParam

After:

  • Request completed successfully
  • Serialized prompt preserves the assistant turn: 15. QED.
  • Final assistant output: 30. QED.

non_harmony_response.json
non_harmony_response_after.json

Harmony Results:

Before:

  • Request failed with BadRequestError
  • Error included:
    • 2 validation errors for ValidatorIterator
    • 0.ResponseOutputTextParam
    • 0.ResponseOutputRefusalParam

After:

  • Request completed successfully
  • input_messages preserves the assistant turn as role="assistant",
    channel="final", content 15. QED.
  • Final assistant output: 30 QED.

harmony_response.json
harmony_response_after.json

Made with Cursor

cc @sfeng33 @bbrowning


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

Signed-off-by: Yifan Zong <yzong@redhat.com>
Signed-off-by: Yifan Zong <yzong@redhat.com>
@yzong-rh yzong-rh changed the title [Bugfix] Responses API EasyInputMessageParam input [Bugfix] Responses API assistant EasyInputMessageParam input Jun 2, 2026
@mergify mergify Bot added frontend bug Something isn't working labels Jun 2, 2026
@harjothkhara

Copy link
Copy Markdown
Contributor

Small edge case that seems worth covering if this keeps the dict fallback path: assistant EasyInput content can also be a list of input content parts, and the new branch in construct_chat_messages_with_tool_call currently keeps only content[0].get("text").

For example, a valid assistant input shaped like {"type":"message","role":"assistant","content":[{"type":"input_text","text":"Hello "},{"type":"input_text","text":"world"}]} would fail the ResponseOutputMessage coercion in protocol.py, stay as the original dict, then be converted to only Hello here. Could this flatten all text-bearing parts, or explicitly reject unsupported list forms, and add a regression for the multi-item EasyInput list case? That would make the fix cover the full EasyInputMessageParam.content shape instead of only string content.

@yzong-rh

yzong-rh commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor Author

assistant EasyInput content can also be a list of input content parts

Yeah this a known issue. Many places in responses API don't handle content list correctly right now.

elif isinstance(item, ResponseOutputMessage):
output_text = item.content[0].text
if prev_assistant_msg:
previous_content = prev_assistant_msg.get("content")
if previous_content is None:
prev_assistant_msg["content"] = output_text
return None
return {
"role": "assistant",
"content": output_text,
}

I encountered this when refactoring Responses rendering previously (#42189)

Existing code also does not handle refusal or multi-part content.
This PR doesn't remedy this.

This fix copies mirrors the existing behavior but we do need a proper fix for these things. We probly should have a helper to do this. We'll need to decide how to handle refusal and concat text content ("".join or "\n".join).
-- Help would be appreciated here :) --

@bbrowning bbrowning left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The changes look reasonable and well-tested. Pulled locally - pre-commit passes as well as the tests below - both ones changed here and ones adjacent to the changed code.

# unit tests
pytest -v \
  tests/entrypoints/openai/responses/test_function_call_parsing.py \
  tests/entrypoints/openai/responses/test_harmony_utils.py \
  tests/entrypoints/openai/responses/test_protocol.py \
  tests/entrypoints/openai/responses/test_response_input_to_harmony.py \
  tests/entrypoints/openai/responses/test_responses_utils.py

# integration tests
pytest -v \
  tests/entrypoints/openai/responses/test_basic.py \
  tests/entrypoints/openai/responses/test_harmony.py

The manual curl example failed for me locally with a Nemotron 3 Super model served via Responses API before this change and success afterwards.

Thanks!

@bbrowning bbrowning added the ready ONLY add when PR is ready to merge/full CI is needed label Jun 22, 2026
@bbrowning
bbrowning merged commit 2a675a7 into vllm-project:main Jun 23, 2026
59 checks passed
nkzhenhua pushed a commit to nkzhenhua/vllm that referenced this pull request Jun 24, 2026
…oject#44361)

Signed-off-by: Yifan Zong <yzong@redhat.com>
Co-authored-by: Ben Browning <bbrownin@redhat.com>
wincent8 pushed a commit to wincent8/vllm that referenced this pull request Jun 29, 2026
…oject#44361)

Signed-off-by: Yifan Zong <yzong@redhat.com>
Co-authored-by: Ben Browning <bbrownin@redhat.com>
efschu pushed a commit to efschu/shvllm that referenced this pull request Jul 18, 2026
…oject#44361)

Signed-off-by: Yifan Zong <yzong@redhat.com>
Co-authored-by: Ben Browning <bbrownin@redhat.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…oject#44361)

Signed-off-by: Yifan Zong <yzong@redhat.com>
Co-authored-by: Ben Browning <bbrownin@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working frontend ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants