Repository navigation
Description
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:
reasoningused to be calledreasoning_content. For now,reasoning_contentwill 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 FAILSuggested 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
- RFC [RFC]:
reasoning_content->reasoning#27755 — rename proposal - PR
reasoning_content->reasoning#27752 — rename implementation - PR [Bugfix] Interleaved thinking keeps compatibility with reasoning_content #33635 / bf001da — incomplete compat fix (output only, missed input)
- PR [Bugfix] Add reasoning_content backward compat to DeltaMessage for streaming #34030 — similar streaming output compat gap (closed unmerged)
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
chaunceyjiang commentedon Mar 30, 2026
Yes, currently only the
reasoningfield can be used.reasoning_contentis no longer supported.delta9000 commentedon Mar 30, 2026
Noted, thanks for taking a look.
I'll switch to fixing this on Opencode's side.
Thanks!
bbrowning commentedon May 14, 2026
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.fix: pass reasoning_content through to server for KV-cache fidelity (#…
fix: echo thinking under the reasoning field the endpoint actually us…
Send thinking replay as both reasoning_content and reasoning fields
Align reasoning/reasoning_content dialects at the chat completions bo…
github-actions commentedon Aug 15, 2026
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!
fix: echo thinking under the reasoning field the endpoint actually us…
sohom-cs commentedon Sep 24, 2026
Status check on this one: #42664 restored
reasoning_contentfor/v1/chat/completions, and #57814 / #52513 cover/tokenizeand pooling at the request-model level. The remaining path is offlineLLM.chat()(and any caller passing raw dicts), where_parse_chat_message_contentstill reads onlymessage["reasoning"], soreasoning_contentis dropped there.Is a fallback at that level wanted (read
reasoning_contentwhenreasoningis absent, declare it onCustomChatCompletionMessageParam), or is request-level normalization the intended boundary? I have a small change with tests ready if it is.cc @bbrowning @sfeng33 @delta9000