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...
Your current environment
Reproducible on current
main. Bug is invllm/entrypoints/chat_utils.py.🐛 Describe the bug
_parse_chat_message_contentreadsreasoningfrom incoming messages but never falls back toreasoning_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.CustomChatCompletionMessageParamalso only declaresreasoning.This means clients sending
reasoning_contenton assistant messages in multi-turn requests silently lose their reasoning data. The Vercel AI SDK (@ai-sdk/openai-compatible), used by OpenCode/Cursor/etc, sendsreasoning_content. The docs promise it still works:The existing test (
test_multi_turn_tools_and_reasoning) doesn't catch this because it round-trips viachoice.message.model_dump(), which uses the output field namereasoning.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
Suggested fix
And add
reasoning_content: str | NonetoCustomChatCompletionMessageParam.Related
reasoning_content->reasoning#27755 — rename proposalreasoning_content->reasoning#27752 — rename implementationBefore submitting a new issue...