[Kimi K3 Bug] Fix kimi k3 reasoning parser (#57098)

Signed-off-by: yewentao256 <zhyanwentao@126.com> Co-authored-by: Flora Feng <4florafeng@gmail.com>

Wentao Ye committed Sep 17, 2026 at 13:05 UTC f3aa88d2309523f8438a239a55f59a28179ce5ef
1 file changed +10 -5
vllm/reasoning/kimi_k3_reasoning_parser.py
+10 -5
@@ -256,11 +256,13 @@ class KimiK3ReasoningParser(ReasoningParser):
256 ) -> tuple[str | None, str | None]:
257 """Split full text into ``(reasoning, rest)`` for the non-streaming path.
258
259 - Handles three shapes:
260 - * no think channel at all -> ``(None, model_output)`` (all content)
259 + Handles four shapes:
260 + * response opener without think markers -> no think channel; all content
261 * open marker present -> reasoning starts after ``<|open|>think<|sep|>``
262 * open marker absent but a close marker exists (gen-prefix consumed
263 the open) -> reasoning starts at offset 0
264 + * neither marker present -> truncated reasoning after a consumed
265 + generation prefix
266 ``rest`` is whatever follows the close marker, fed on to the tool parser.
267 """
268 if not self._thinking_enabled:
@@ -270,11 +272,14 @@ class KimiK3ReasoningParser(ReasoningParser):
272 # reasoning content begins right after think-open (or at start if the
273 # open marker was already consumed as a generation prefix)
274 content_start = m_open.end() if m_open is not None else 0
273 - # if there is no think channel at all, everything is content
274 - if m_open is None and self._think_close_re.search(model_output) is None:
275 - return None, self._content_after_reasoning(model_output, request)
275
276 m_close = self._think_close_re.search(model_output, content_start)
277 + if (
278 + m_open is None
279 + and m_close is None
280 + and self._response_open_re.search(model_output) is not None
281 + ):
282 + return None, self._content_after_reasoning(model_output, request)
283 if m_close is not None:
284 reasoning = model_output[content_start : m_close.start()]
285 rest = model_output[m_close.end() :]