Skip to content

[Bugfix][Rust Frontend] Accept empty message content like the Python frontend - #64471

Open
my-git9 wants to merge 1 commit into
vllm-project:mainfrom
my-git9:fix/rust-frontend-empty-message-content
Open

my-git9 wants to merge 1 commit into
vllm-project:mainfrom
my-git9:fix/rust-frontend-empty-message-content

Conversation

@my-git9

@my-git9 my-git9 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Overview

The Rust frontend rejects empty user message content on /v1/chat/completions/render and /tokenize with 400 BadRequestError: message content cannot be empty, while the Python frontend renders it as a normal (empty) turn. This PR drops the Rust-only content check so both frontends accept the same requests.

Fixes #64433 (second bug, see #62741 below for the first one).

Claims

  • POST /v1/chat/completions/render accepts {"role": "user", "content": ""} and {"role": "user", "content": []} and returns 200 with non-empty token_ids.
  • POST /tokenize accepts the same two payloads and returns 200 with non-empty tokens.
  • messages: [] is still rejected with 400 (messages cannot be empty); only the content check is removed.
  • Rust/Python parity for chat-message validation is restored: the removed check was Rust-only and never existed in the Python frontend.

Validation

Reproducer (unit/route level, no server or GPU needed):

cd rust
cargo test -p vllm-server --lib empty_message_content
running 2 tests
test routes::tests::render_chat_accepts_empty_message_content ... ok
test routes::tests::tokenize_chat_accepts_empty_message_content ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 538 filtered out; finished in 0.03s

Other checks:

  • cargo fmt --check passes.
  • Both routes reach the shared validate_messages() helper, so the change is exercised through the public HTTP entry points (/v1/chat/completions/render, /tokenize), not a test-only path.
  • CI: .buildkite/test_areas/rust_frontend_cargo.yaml runs the whole rust/ workspace test suite on any rust/ change, so the new tests run in CI.

Details

Root cause. validate_messages() in rust/src/server/src/routes/openai/utils/types.rs is shared by the chat-completions render path and the /tokenize chat form. It was introduced in #44222 together with /tokenize and added a user-message content check (Text("") / Parts([]) → 400) that has no counterpart in the Python frontend (vllm/renderers/), which accepts empty content and renders an empty turn. Empty content is legal in the OpenAI schema, so the Rust frontend was stricter than both Python and the API spec.

Design. Minimal fix: delete the content loop, keep the messages non-empty check (both frontends still require at least one message). Net −8 lines in production code (low complexity, no new abstractions, no behaviour change outside these two routes); the only two call sites are routes/openai/chat_completions/types.rs and routes/openai/tokenize/types.rs.

Tests. tokenize_chat_empty_message_content_returns_400 asserted the old (incorrect) behaviour and is replaced by tokenize_chat_accepts_empty_message_content; a new render_chat_accepts_empty_message_content covers the render route. Both assert 200 plus a non-empty token list for "" and [], so a regression that silently produces an empty prompt is also caught.

Tradeoffs / limitations. Empty turns are now tokenized (a few template control tokens) instead of rejected — same cost profile as Python. Validation was done at the route level; no local GPU/Python environment was available to run a side-by-side end-to-end comparison against the Python frontend, so the parity claim is based on the Python renderer's code path (no empty-content rejection there) rather than a live diff of responses.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

@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.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mergify mergify Bot added rust bug Something isn't working labels Oct 29, 2026
…frontend

The chat-completions and tokenize routes rejected empty user content
(`""` or `[]`) with a 400, while the Python frontend renders it as an
empty turn. Drop the content checks and keep only the non-empty messages
check, so both frontends accept the same requests.

Co-authored-by: Trae Agent <noreply@trae.ai>
Signed-off-by: xin.li <xin.li@daocloud.io>

This branch has not been deployed

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

Labels

bug Something isn't working rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: vllm-rs render drops tools for tool_choice "none" and rejects empty content, unlike the Python frontend

1 participant