From cc1058cc63e3d39d4d76fab0a16aa687a60b07f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fit=20ERDO=C4=9EAN?= Date: Tue, 15 Sep 2026 19:43:50 +0300 Subject: [PATCH] [#19229][fix] Discard a complete think tag in NemotronV3 reasoning finish() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NemotronV3ReasoningParser.finish() flushes whatever parse_delta withheld when the stream ends. parse_delta holds back a delta that is exactly a or tag while it waits to see what follows, so a stream that ends on such a tag surfaces the tag itself as reasoning_content or content. DeepSeekR1Parser.finish() already discards a buffer that holds exactly a tag; apply the same rule here. Signed-off-by: Yiğit ERDOĞAN --- tensorrt_llm/llmapi/reasoning_parser.py | 13 ++++++++----- tests/unittest/llmapi/test_reasoning_parser.py | 9 +++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tensorrt_llm/llmapi/reasoning_parser.py b/tensorrt_llm/llmapi/reasoning_parser.py index 6dfc24547b07..5900eaea7917 100644 --- a/tensorrt_llm/llmapi/reasoning_parser.py +++ b/tensorrt_llm/llmapi/reasoning_parser.py @@ -634,10 +634,15 @@ def finish(self) -> ReasoningParserResult: as reasoning_content since we are still in reasoning mode. If the closing tag was already found (or reasoning was never - entered), flushes any remaining buffer as content.""" + entered), flushes any remaining buffer as content. + + A buffer holding exactly a complete tag is a delimiter rather than + model output, so it is discarded.""" + remaining = self._buffer + self._buffer = "" + if remaining in (self.reasoning_start, self.reasoning_end): + remaining = "" if self.in_reasoning and not self._found_closing_tag: - remaining = self._buffer - self._buffer = "" if self._force_nonempty_content: all_content = self._accumulated_reasoning + remaining self._accumulated_reasoning = "" @@ -648,8 +653,6 @@ def finish(self) -> ReasoningParserResult: if remaining: return ReasoningParserResult(reasoning_content=remaining) return ReasoningParserResult() - remaining = self._buffer - self._buffer = "" if remaining: return ReasoningParserResult(content=remaining) return ReasoningParserResult() diff --git a/tests/unittest/llmapi/test_reasoning_parser.py b/tests/unittest/llmapi/test_reasoning_parser.py index 88700cc26114..272c64ff7071 100644 --- a/tests/unittest/llmapi/test_reasoning_parser.py +++ b/tests/unittest/llmapi/test_reasoning_parser.py @@ -795,6 +795,15 @@ def test_nano_v3_reasoning_parser_stream(delta_texts: list, content: list, (["reasoning", "data"], "", "", { "force_nonempty_content": True }), + # A stream that ends on a complete tag: the tag is a delimiter, + # not model output. + (["a", R1_END], "", "", None), + (["a", R1_END], "a", "", { + "force_nonempty_content": True + }), + ([R1_START], "", "", { + "enable_thinking": False + }), ]) def test_nano_v3_reasoning_parser_finish(delta_texts: list, finish_content: str, finish_reasoning: str,