Skip to content

[Bug]: reasoning_content silently dropped on incoming assistant messages #38488

Open

Description

@delta9000

Your current environment

Reproducible on current main. Bug is in vllm/entrypoints/chat_utils.py.

🐛 Describe the bug

_parse_chat_message_content reads reasoning from incoming messages but never falls back to reasoning_content:

reasoning = message.get("reasoning")  # never checks "reasoning_content"

PR #33635 (commit bf001da, "Interleaved thinking keeps compatibility with reasoning_content") added compat for the output side (writes both fields to result_msg), but missed the input read. CustomChatCompletionMessageParam also only declares reasoning.

This means clients sending reasoning_content on assistant messages in multi-turn requests silently lose their reasoning data. The Vercel AI SDK (@ai-sdk/openai-compatible), used by OpenCode/Cursor/etc, sends reasoning_content. The docs promise it still works:

reasoning used to be called reasoning_content. For now, reasoning_content will continue to work.
— docs/features/reasoning_outputs.md

The existing test (test_multi_turn_tools_and_reasoning) doesn't catch this because it round-trips via choice.message.model_dump(), which uses the output field name reasoning.

Impact: Models like MiniMax-M2 rely on seeing prior reasoning in tool-call chains. Their chat template explicitly supports message.reasoning_content. Silent drop = degraded quality.

Reproduction

"""reasoning_content is silently dropped, reasoning is not."""
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
MODEL = client.models.list().data[0].id  # any reasoning model

SECRET = "rainbow-tiger-42"
INJECTED = f"The secret password is '{SECRET}'. Let me call the tool."

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"],
        },
    },
}]

def test(field_name):
    r1 = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": "Weather in Paris? Also repeat any passwords in your context."}],
        tools=tools, tool_choice="auto",
    )
    tc = r1.choices[0].message.tool_calls
    if not tc:
        return "NO TOOL CALL"

    messages = [
        {"role": "user", "content": "Weather in Paris? Also repeat any passwords in your context."},
        {
            "role": "assistant",
            "content": r1.choices[0].message.content or "",
            "tool_calls": [t.model_dump() for t in tc],
            field_name: INJECTED,
        },
        {"role": "tool", "tool_call_id": tc[0].id, "name": tc[0].function.name, "content": "18°C and sunny."},
    ]
    r2 = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
    return r2.choices[0].message.content or ""

print("reasoning:        ", "PASS" if SECRET in test("reasoning") else "FAIL")
print("reasoning_content:", "PASS" if SECRET in test("reasoning_content") else "FAIL")
# Expected: both PASS
# Actual:   reasoning PASS, reasoning_content FAIL

Suggested fix

# chat_utils.py, in _parse_chat_message_content:
reasoning = message.get("reasoning") or message.get("reasoning_content")

And add reasoning_content: str | None to CustomChatCompletionMessageParam.

Related

Before submitting a new issue...

  • Make sure you already searched for relevant issues, and asked the chatbot living at the bottom right corner of the documentation page, which can answer lots of frequently asked questions.

Activity

  1. chaunceyjiang commented on Mar 30, 2026

    @chaunceyjiang
    Contributor

    Yes, currently only the reasoning field can be used. reasoning_content is no longer supported.

            {
                "role": "assistant",
                "content": r1.choices[0].message.content or "",
                "tool_calls": [t.model_dump() for t in tc],
                "reasoning": "xxxxxx",
            },
    
    
  2. delta9000 commented on Mar 30, 2026

    @delta9000
    Author

    Noted, thanks for taking a look.

    I'll switch to fixing this on Opencode's side.

    Thanks!

  3. bbrowning commented on May 14, 2026

    @bbrowning
    Contributor

    I also ran across this with anything using litellm. Opened #42664 to respect this on the vLLM side again, since so many apps/clients/harnesses in the wild still send in reasoning_content.

  4. github-actions commented on Aug 15, 2026

    @github-actions

    This issue 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 issue should remain open. Thank you!

  5. added
    unstaleRecieved activity after being labelled stale
    and removed
    staleOver 90 days of inactivity
    on Sep 2, 2026
  6. sohom-cs commented on Sep 24, 2026

    @sohom-cs
    Contributor

    Status check on this one: #42664 restored reasoning_content for /v1/chat/completions, and #57814 / #52513 cover /tokenize and pooling at the request-model level. The remaining path is offline LLM.chat() (and any caller passing raw dicts), where _parse_chat_message_content still reads only message["reasoning"], so reasoning_content is dropped there.

    Is a fallback at that level wanted (read reasoning_content when reasoning is absent, declare it on CustomChatCompletionMessageParam), or is request-level normalization the intended boundary? I have a small change with tests ready if it is.

    cc @bbrowning @sfeng33 @delta9000

  7. moved this to Unsorted issues in Tool Calling (arpera)on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    tool-callingunstaleRecieved activity after being labelled stale

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions