fix(reasoning): fall back to reasoning_content when nothing follows </think> tag

Fixes #38197. When a reasoning model's entire answer sits inside
<think>...</think> with nothing trailing after the closing tag,
_parse_content_for_reasoning() previously returned an empty string
as content, discarding the model's only real output even though
finish_reason=stop and the call succeeded.
This commit is contained in:
trakshan-mishra 2026-08-25 19:43:52 +05:30
parent 31a67561ab
commit 1b15772c86
2 changed files with 51 additions and 1 deletions

View file

@ -1627,7 +1627,14 @@ def _parse_content_for_reasoning(
)
if reasoning_match:
return reasoning_match.group(1), reasoning_match.group(2)
reasoning_content = reasoning_match.group(1)
content = reasoning_match.group(2)
if not content.strip():
# Model's entire answer was inside the think block with
# nothing after it — surface it as content instead of
# silently discarding the model's only real output.
content = reasoning_content
return reasoning_content, content
return None, message_text

View file

@ -0,0 +1,43 @@
"""
Repro for GitHub issue #38197: MiniMax-M2.7 returns message.content empty,
usage all zero, while reasoning_content has valid output, finish_reason=stop.
Root cause (confirmed against source + MiniMax docs, 2026-08-25):
MiniMax M2.7 (with reasoning_split unset/false, the default) returns its
answer wrapped as "<think>...</think><rest of answer>" in a single content
string. litellm.litellm_core_utils.prompt_templates.common_utils.
_parse_content_for_reasoning() splits this with a regex whose second capture
group is everything AFTER the closing </think> tag. When the model's real
answer sits entirely inside the <think> block with nothing after it, that
capture group is an empty string so `content` comes back "" while the
actual answer is sitting, discarded, in `reasoning_content`.
Tests the parsing function directly no network, no API key, no mock
server needed, since this isolates exactly where the bug lives.
"""
from litellm.litellm_core_utils.prompt_templates.common_utils import (
_parse_content_for_reasoning,
)
class TestMinimaxReasoningContentBug:
def test_answer_entirely_inside_think_tag_yields_empty_content(self):
raw = "<think>The answer to 2+2 is 4.</think>"
reasoning_content, content = _parse_content_for_reasoning(raw)
print("reasoning_content:", repr(reasoning_content))
print("content:", repr(content))
assert reasoning_content == "The answer to 2+2 is 4."
# Fixed: content now falls back to reasoning_content when nothing
# follows the closing </think> tag, instead of being empty.
assert content == "The answer to 2+2 is 4."
def test_answer_after_think_tag_still_works(self):
raw = "<think>Let me work this out.</think>The answer is 4."
reasoning_content, content = _parse_content_for_reasoning(raw)
assert reasoning_content == "Let me work this out."
assert content == "The answer is 4."