diff --git a/python/sglang/srt/parser/reasoning_parser.py b/python/sglang/srt/parser/reasoning_parser.py index 8405b0e69..838f40005 100644 --- a/python/sglang/srt/parser/reasoning_parser.py +++ b/python/sglang/srt/parser/reasoning_parser.py @@ -782,6 +782,20 @@ class InklingDetector(BaseReasoningFormatDetector): self._buffer = "" return self._parse_blocks(text) + def finish(self) -> StreamingParseResult: + # Flush reasoning buffered under stream_reasoning=False when the stream + # ends before a control/end token closes the block (e.g. max_tokens cut + # a thinking block short). Mirrors the non-streaming flush in + # detect_and_parse; without it the trailing reasoning trace is dropped. + reasoning_text = "" + if self._kind == "reasoning" and not self.stream_reasoning: + reasoning_text = self._pending_reasoning + self._buffer = "" + self._pending_reasoning = "" + self._pending_header = "" + self._kind = None + return StreamingParseResult(reasoning_text=reasoning_text) + @staticmethod def _partial_control_length(text: str) -> int: max_token_len = max(map(len, _INKLING_CONTROL_TOKENS)) diff --git a/test/registered/unit/parser/test_reasoning_parser.py b/test/registered/unit/parser/test_reasoning_parser.py index 7fc452ba6..e5616b27f 100644 --- a/test/registered/unit/parser/test_reasoning_parser.py +++ b/test/registered/unit/parser/test_reasoning_parser.py @@ -249,6 +249,24 @@ class TestInklingDetector(CustomTestCase): content += detector.parse_streaming_increment(chunk).normal_text self.assertEqual(content, " worldnext", msg=f"chunks={chunks!r}") + def test_finish_flushes_reasoning_truncated_before_end_token(self): + """Bug regression: with stream_reasoning=False the detector buffers the + thinking block and only flushes it on a control/end token. When + generation is cut mid-block (e.g. max_tokens) the stream ends with no + end token, so the buffered trace was dropped entirely; finish() must + emit it, matching the non-streaming detect_and_parse path.""" + detector = InklingDetector(stream_reasoning=False) + source = "<|message_model|><|content_thinking|>truncated thinking" + streamed_reasoning = "" + for char in source: + streamed_reasoning += detector.parse_streaming_increment( + char + ).reasoning_text + # The block never closed, so nothing surfaces mid-stream. + self.assertEqual(streamed_reasoning, "") + # finish() flushes the buffered trace instead of dropping it. + self.assertEqual(detector.finish().reasoning_text, "truncated thinking") + class TestKimiDetector(CustomTestCase): def setUp(self):