[Kimi K3] Rework skipped-think fix as opt-in force_nonempty_content with streaming coverage (#34187)

Co-authored-by: yhyang201 <yhyang201@gmail.com>
Co-authored-by: Yuhao Yang <47235274+yhyang201@users.noreply.github.com>
This commit is contained in:
Xinyuan Tong
2026-09-03 17:37:28 +08:00
committed by GitHub
co-authored by yhyang201 Yuhao Yang
parent a6001478f4
commit 27b7a2dc3b
2 changed files with 144 additions and 0 deletions
@@ -190,5 +190,114 @@ def test_reasoning_parser_registration() -> None:
assert isinstance(ReasoningParser("kimi_k3").detector, KimiK3Detector)
def _stream_with_finish(detector: KimiK3Detector, chunks: list[str]) -> tuple[str, str]:
reasoning, content = _stream(detector, chunks)
result = detector.finish()
return reasoning + result.reasoning_text, content + result.normal_text
@pytest.mark.parametrize(
("text", "reasoning", "content"),
[
(
f"bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}",
"",
"bare answer",
),
(
f"{RESPONSE_OPEN}bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}",
"",
"bare answer",
),
("still going", "still going", ""),
("deep thought<|close|>", "deep thought", ""),
],
)
def test_fnc_non_stream_skipped_think_vs_truncated_reasoning(
text: str, reasoning: str, content: str
) -> None:
detector = KimiK3Detector(force_reasoning=True, force_nonempty_content=True)
result = detector.detect_and_parse(text)
assert result.reasoning_text == reasoning
assert result.normal_text == content
@pytest.mark.parametrize("chunk_size", [1, 5, 13])
def test_fnc_streaming_skipped_think_answer(chunk_size: int) -> None:
detector = KimiK3Detector(force_reasoning=True, force_nonempty_content=True)
text = f"bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}"
reasoning, content = _stream_with_finish(detector, _chunks(text, chunk_size))
# Streamed as reasoning in real time; finish() re-emits the cleaned
# payload as content once the channel close proves skipped-think.
assert reasoning == text
assert content == "bare answer"
@pytest.mark.parametrize("chunk_size", [1, 5, 13])
def test_fnc_streaming_truncated_reasoning_stays_reasoning(chunk_size: int) -> None:
detector = KimiK3Detector(force_reasoning=True, force_nonempty_content=True)
reasoning, content = _stream_with_finish(
detector, _chunks("still going", chunk_size)
)
assert reasoning == "still going"
assert content == ""
@pytest.mark.parametrize("chunk_size", [5, 13])
def test_fnc_streaming_long_think_streams_without_close(chunk_size: int) -> None:
detector = KimiK3Detector(force_reasoning=True, force_nonempty_content=True)
text = "x" * 20000
reasoning = ""
for chunk in _chunks(text, chunk_size):
# Live chunks flow immediately — no hold-back to starve SSE idle timeouts.
reasoning += detector.parse_streaming_increment(chunk).reasoning_text
assert reasoning == text
result = detector.finish()
assert result.reasoning_text == ""
assert result.normal_text == ""
def test_fnc_streaming_response_open_not_reemitted() -> None:
detector = KimiK3Detector(force_reasoning=True, force_nonempty_content=True)
text = f"{RESPONSE_OPEN}bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}"
reasoning, content = _stream_with_finish(detector, _chunks(text, 5))
# The channel switch already streamed the answer as content; finish()
# must not re-emit it.
assert reasoning == ""
assert content == "bare answer"
def test_fnc_streaming_force_reasoning_off_not_reemitted() -> None:
detector = KimiK3Detector(force_reasoning=False, force_nonempty_content=True)
text = f"bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}"
reasoning, content = _stream_with_finish(detector, _chunks(text, 5))
assert reasoning == ""
assert content == "bare answer"
@pytest.mark.parametrize("force_nonempty_content", [False, True])
def test_stream_reasoning_off_truncation_flushes_reasoning(
force_nonempty_content: bool,
) -> None:
detector = KimiK3Detector(
force_reasoning=True,
stream_reasoning=False,
force_nonempty_content=force_nonempty_content,
)
reasoning, content = _stream_with_finish(detector, _chunks("still going", 5))
assert reasoning == "still going"
assert content == ""
def test_fnc_stream_reasoning_off_skipped_think_reemits_content() -> None:
detector = KimiK3Detector(
force_reasoning=True, stream_reasoning=False, force_nonempty_content=True
)
text = f"bare answer{RESPONSE_CLOSE}{MESSAGE_CLOSE}"
reasoning, content = _stream_with_finish(detector, _chunks(text, 5))
assert reasoning == ""
assert content == "bare answer"
if __name__ == "__main__":
sys.exit(pytest.main([__file__]))