Skip to content

[Bug]:OpenAIMessageConverter produces assistant messages with null content when using vLLM + Qwen3 thinking mode #1268

@BeastDamon

Description

@BeastDamon

[Bug] OpenAIMessageConverter produces assistant messages with null content when using vLLM + Qwen3 thinking mode

Environment

  • AgentScope Java: 1.0.10
  • Model: Qwen3.5-122B-A10B (via vLLM, OpenAI-compatible API)
  • Model Client: OpenAIChatModel
  • Agent: ReActAgent with enablePlan() and tool calling

Description

When using OpenAIChatModel with a vLLM-hosted Qwen3 model that has thinking/reasoning enabled, the API returns 400 Bad Request with:

Input error. Field required: input.messages.N.content

This happens because OpenAIMessageConverter.convertAssistantMessage() does not set the content field when the assistant message only contains ThinkingBlock + ToolUseBlock (no TextBlock).

Root Cause

In OpenAIMessageConverter.java (line ~267):

private OpenAIMessage convertAssistantMessage(Msg msg) {
    OpenAIMessage.Builder builder = OpenAIMessage.builder().role("assistant");

    String textContent = textExtractor.apply(msg);
    if (textContent != null && !textContent.isEmpty()) {
        builder.content(textContent);  // Only set when non-empty
    }
    // ... handle ThinkingBlock, ToolUseBlock ...
    return builder.build();
}

When the model responds with reasoning (ThinkingBlock) + tool calls (ToolUseBlock) but no text content, textContent is null, so content is never set on the builder. The resulting JSON message has no content field at all.

The OpenAI official API tolerates content: null for assistant messages with tool_calls, but vLLM's OpenAI-compatible endpoint requires content to be present (even as an empty string "").

Steps to Reproduce

  1. Deploy a Qwen3/Qwen3.5 model via vLLM with --reasoning-parser qwen3
  2. Configure OpenAIChatModel pointing to the vLLM endpoint
  3. Create a ReActAgent with tools (e.g., enablePlan())
  4. Send a message that triggers tool calling
  5. After the first tool call + result round, the next LLM call fails with Field required: input.messages.N.content

Actual Behavior

io.agentscope.core.model.exception.BadRequestException: 
OpenAI API error in streaming response: Input error. Field required: input.messages.6.content

The error index varies (messages.2, messages.6, messages.22) depending on how many rounds of tool calling occurred before the failure.

Expected Behavior

The convertAssistantMessage method should ensure content is always set, at minimum as an empty string "", especially when tool_calls are present.

Suggested Fix

private OpenAIMessage convertAssistantMessage(Msg msg) {
    OpenAIMessage.Builder builder = OpenAIMessage.builder().role("assistant");

    String textContent = textExtractor.apply(msg);
    if (textContent != null && !textContent.isEmpty()) {
        builder.content(textContent);
    } else {
        // Ensure content is never null for vLLM/Qwen3 compatibility
        builder.content("");
    }
    // ... rest unchanged ...
}

Additional Context

  • This is a known issue with vLLM's Qwen3 reasoning parser. See: vLLM Forum: Qwen3.5 only output reasoning and no content — vLLM may return content=None with all output in the reasoning field for Qwen3 series models.
  • The same ReActAgent configuration works correctly with Qwen3.5-122B-A10B-Thinking (the -Thinking variant), because that model's output format is correctly parsed by vLLM, producing non-null content.
  • Even when vLLM correctly separates reasoning/content, the assistant message after a tool call may legitimately have empty text content (only ThinkingBlock + ToolUseBlock), which still triggers this bug.

Workaround

Use Qwen3.5-*-Thinking model variants which produce correctly formatted output with vLLM's reasoning parser.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    Status

    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions