[misc] Remove unit test cases that fail the admission criteria (#30690)
This commit is contained in:
@@ -13,7 +13,6 @@ from sglang.srt.parser.reasoning_parser import (
|
||||
Nemotron3Detector,
|
||||
Qwen3Detector,
|
||||
ReasoningParser,
|
||||
StreamingParseResult,
|
||||
)
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
@@ -21,20 +20,6 @@ from sglang.test.test_utils import CustomTestCase
|
||||
register_cpu_ci(est_time=7, suite="base-a-test-cpu")
|
||||
|
||||
|
||||
class TestStreamingParseResult(CustomTestCase):
|
||||
def test_init_default(self):
|
||||
"""Test default initialization of StreamingParseResult."""
|
||||
result = StreamingParseResult()
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_init_with_values(self):
|
||||
"""Test initialization with specific values."""
|
||||
result = StreamingParseResult("normal", "reasoning")
|
||||
self.assertEqual(result.normal_text, "normal")
|
||||
self.assertEqual(result.reasoning_text, "reasoning")
|
||||
|
||||
|
||||
class TestBaseReasoningFormatDetector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = BaseReasoningFormatDetector(
|
||||
@@ -44,15 +29,6 @@ class TestBaseReasoningFormatDetector(CustomTestCase):
|
||||
stream_reasoning=True,
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization of BaseReasoningFormatDetector."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
self.assertEqual(self.detector._buffer, "")
|
||||
self.assertFalse(self.detector.stripped_think_start)
|
||||
|
||||
def test_detect_and_parse_normal_text(self):
|
||||
"""Test parsing normal text without reasoning."""
|
||||
text = "This is normal text"
|
||||
@@ -161,28 +137,6 @@ class TestDeepSeekR1Detector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = DeepSeekR1Detector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test DeepSeekR1Detector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertTrue(self.detector._in_reasoning) # force_reasoning=True
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_init_no_stream_reasoning(self):
|
||||
"""Test DeepSeekR1Detector with stream_reasoning=False."""
|
||||
detector = DeepSeekR1Detector(stream_reasoning=False)
|
||||
self.assertFalse(detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_r1_format(self):
|
||||
"""Test parsing DeepSeek-R1 format."""
|
||||
text = "I need to think about this. The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
# Should be treated as reasoning because force_reasoning=True
|
||||
self.assertEqual(
|
||||
result.reasoning_text, "I need to think about this. The answer is 42."
|
||||
)
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
def test_detect_and_parse_with_end_token(self):
|
||||
"""Test parsing with end token."""
|
||||
text = "I think this is the answer</think>The final answer is 42."
|
||||
@@ -203,20 +157,6 @@ class TestQwen3Detector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = Qwen3Detector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test Qwen3Detector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertFalse(self.detector._in_reasoning) # force_reasoning=False
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_qwen3_format(self):
|
||||
"""Test parsing Qwen3 format."""
|
||||
text = "<think>Let me think about this problem</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Let me think about this problem")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_without_thinking(self):
|
||||
"""Test parsing without thinking (enable_thinking=False case)."""
|
||||
text = "Direct answer without thinking."
|
||||
@@ -225,70 +165,10 @@ class TestQwen3Detector(CustomTestCase):
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
|
||||
class TestQwen3ForcedReasoningDetector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = Qwen3Detector(force_reasoning=True)
|
||||
|
||||
def test_init(self):
|
||||
"""Test Qwen3ForcedReasoningDetector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertTrue(self.detector._in_reasoning) # force_reasoning=True
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_qwen3_forced_reasoning_format(self):
|
||||
"""Test parsing Qwen3-ForcedReasoning format (no <think> start tag)."""
|
||||
text = "I need to think about this step by step.</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(
|
||||
result.reasoning_text, "I need to think about this step by step."
|
||||
)
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_with_start_token(self):
|
||||
"""Test parsing Qwen3-ForcedReasoning with optional <think> start tag."""
|
||||
text = "<think>I need to think about this.</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
# Should work because base class logic handles both force_reasoning=True OR start token
|
||||
self.assertEqual(result.reasoning_text, "I need to think about this.")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_streaming_qwen3_forced_reasoning_format(self):
|
||||
"""Test streaming parse of Qwen3-ForcedReasoning format."""
|
||||
# First chunk without <think> start
|
||||
result = self.detector.parse_streaming_increment("I need to")
|
||||
self.assertEqual(result.reasoning_text, "I need to")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
# More reasoning content
|
||||
result = self.detector.parse_streaming_increment(" think about this.")
|
||||
self.assertEqual(result.reasoning_text, " think about this.")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
# End token with normal text
|
||||
result = self.detector.parse_streaming_increment("</think>The answer is 42.")
|
||||
self.assertEqual(result.reasoning_text, "") # Buffer cleared
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
|
||||
class TestKimiDetector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = KimiDetector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test KimiDetector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "◁think▷")
|
||||
self.assertEqual(self.detector.think_end_token, "◁/think▷")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_kimi_format(self):
|
||||
"""Test parsing Kimi format."""
|
||||
text = "◁think▷Let me consider this carefully◁/think▷The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Let me consider this carefully")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_kimi_no_thinking(self):
|
||||
"""Test parsing Kimi format without thinking."""
|
||||
text = "Direct answer without thinking tokens."
|
||||
@@ -296,29 +176,6 @@ class TestKimiDetector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, text)
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_streaming_kimi_format(self):
|
||||
"""Test streaming parse of Kimi format."""
|
||||
# Test partial token
|
||||
result = self.detector.parse_streaming_increment("◁thi")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
# Complete start token
|
||||
result = self.detector.parse_streaming_increment("nk▷Start")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(result.reasoning_text, "Start")
|
||||
self.assertTrue(self.detector._in_reasoning)
|
||||
|
||||
# Add reasoning content
|
||||
result = self.detector.parse_streaming_increment("thinking...")
|
||||
self.assertEqual(result.reasoning_text, "thinking...")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
# End token - reasoning content is cleared when end token is processed
|
||||
result = self.detector.parse_streaming_increment("◁/think▷answer")
|
||||
self.assertEqual(result.reasoning_text, "") # Buffer cleared
|
||||
self.assertEqual(result.normal_text, "answer")
|
||||
|
||||
|
||||
class TestKimiK2Detector(CustomTestCase):
|
||||
"""Test cases for KimiK2 detector with tool interruption support."""
|
||||
@@ -334,36 +191,6 @@ class TestKimiK2Detector(CustomTestCase):
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_tool_interrupt(self):
|
||||
"""Test parsing with Kimi-K2 tool-section interruption."""
|
||||
text = "<think>thinking<|tool_calls_section_begin|><|tool_call_begin|>"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "thinking")
|
||||
self.assertEqual(
|
||||
result.normal_text, "<|tool_calls_section_begin|><|tool_call_begin|>"
|
||||
)
|
||||
|
||||
def test_streaming_tool_interrupt(self):
|
||||
"""Test streaming parse interrupted by tool section."""
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
result1 = self.detector.parse_streaming_increment("reasoning")
|
||||
self.assertEqual(result1.reasoning_text, "reasoning")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
|
||||
result2 = self.detector.parse_streaming_increment(
|
||||
"<|tool_calls_section_begin|>"
|
||||
)
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
self.assertEqual(result2.normal_text, "<|tool_calls_section_begin|>")
|
||||
|
||||
def test_streaming_after_interrupt_is_normal(self):
|
||||
"""After interruption, subsequent chunks should be normal text."""
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
self.detector.parse_streaming_increment("reasoning<|tool_calls_section_begin|>")
|
||||
result = self.detector.parse_streaming_increment("<|tool_call_begin|>")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
self.assertEqual(result.normal_text, "<|tool_call_begin|>")
|
||||
|
||||
|
||||
class TestGlm45Detector(CustomTestCase):
|
||||
"""Test cases for GLM45 detector with tool interruption support."""
|
||||
@@ -371,33 +198,6 @@ class TestGlm45Detector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = Glm45Detector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test Glm45Detector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertEqual(self.detector.tool_start_token, "<tool_call>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_normal_reasoning(self):
|
||||
"""Test parsing normal reasoning block without tool interruption."""
|
||||
text = "<think>Let me think about this step by step</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Let me think about this step by step")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_tool_interrupt(self):
|
||||
"""
|
||||
Test parsing with tool interruption.
|
||||
|
||||
GLM45 can interrupt reasoning with tool token (<tool_call>) without closing </think>.
|
||||
Should split at the first occurrence of tool_start_token using find().
|
||||
"""
|
||||
text = "<think>I need to think<tool_call>tool call data"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "I need to think")
|
||||
self.assertEqual(result.normal_text, "<tool_call>tool call data")
|
||||
|
||||
def test_detect_and_parse_multiple_tool_calls_find(self):
|
||||
"""
|
||||
Test that find() finds the FIRST occurrence of tool_start_token.
|
||||
@@ -413,17 +213,6 @@ class TestGlm45Detector(CustomTestCase):
|
||||
"<tool_call>first tool<tool_call>second tool<tool_call>final tool",
|
||||
)
|
||||
|
||||
def test_detect_and_parse_truncated_reasoning(self):
|
||||
"""
|
||||
Test truncated reasoning without tool or end tag.
|
||||
|
||||
Should return all content as reasoning_text.
|
||||
"""
|
||||
text = "<think>This is incomplete"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "This is incomplete")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
def test_detect_and_parse_normal_text_only(self):
|
||||
"""Test parsing text without reasoning block."""
|
||||
text = "Just the answer without any reasoning."
|
||||
@@ -431,50 +220,6 @@ class TestGlm45Detector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, text)
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_streaming_normal_flow(self):
|
||||
"""Test streaming with normal reasoning flow."""
|
||||
# Start reasoning
|
||||
result1 = self.detector.parse_streaming_increment("<think>")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
self.assertEqual(result1.reasoning_text, "")
|
||||
self.assertTrue(self.detector._in_reasoning)
|
||||
|
||||
# Reasoning content
|
||||
result2 = self.detector.parse_streaming_increment("thinking...")
|
||||
self.assertEqual(result2.normal_text, "")
|
||||
self.assertEqual(result2.reasoning_text, "thinking...")
|
||||
|
||||
# End reasoning
|
||||
result3 = self.detector.parse_streaming_increment("</think>answer")
|
||||
self.assertEqual(result3.normal_text, "answer")
|
||||
self.assertEqual(result3.reasoning_text, "")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
|
||||
def test_streaming_tool_interrupt_split_tokens(self):
|
||||
"""
|
||||
Test streaming with tool interruption where tool token is split across chunks.
|
||||
|
||||
This tests the buffer prefix logic that prevents partial emission of tool token.
|
||||
"""
|
||||
# Start reasoning
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
|
||||
# Add reasoning
|
||||
result1 = self.detector.parse_streaming_increment("thinking")
|
||||
self.assertEqual(result1.reasoning_text, "thinking")
|
||||
|
||||
# Send partial tool token (should be buffered, not emitted)
|
||||
result2 = self.detector.parse_streaming_increment("<tool_call>")
|
||||
# Tool token is in buffer, causing switch to normal mode
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
self.assertEqual(result2.normal_text, "<tool_call>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
|
||||
# Send tool args
|
||||
result3 = self.detector.parse_streaming_increment("tool args")
|
||||
self.assertEqual(result3.reasoning_text, "")
|
||||
self.assertEqual(result3.normal_text, "tool args")
|
||||
|
||||
def test_streaming_no_stream_reasoning(self):
|
||||
"""Test streaming without stream_reasoning enabled."""
|
||||
detector = Glm45Detector(stream_reasoning=False)
|
||||
@@ -526,21 +271,6 @@ class TestHunyuanDetector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = HunyuanDetector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test HunyuanDetector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertEqual(self.detector.tool_start_token, "<tool_calls>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_normal_reasoning(self):
|
||||
"""Test parsing normal reasoning block without tool interruption."""
|
||||
text = "<think>Let me think about this</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Let me think about this")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_without_thinking(self):
|
||||
"""Test parsing without thinking tokens (no_think mode)."""
|
||||
text = "Direct answer without thinking."
|
||||
@@ -548,42 +278,6 @@ class TestHunyuanDetector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, text)
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_detect_and_parse_tool_interrupt(self):
|
||||
"""Test parsing with tool call interruption during reasoning."""
|
||||
text = "<think>I need to check<tool_calls><tool_call>get_weather<tool_sep></tool_call></tool_calls>"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "I need to check")
|
||||
self.assertIn("<tool_calls>", result.normal_text)
|
||||
|
||||
def test_streaming_normal_reasoning(self):
|
||||
"""Test streaming parse of normal reasoning block."""
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
result1 = self.detector.parse_streaming_increment("reasoning content")
|
||||
self.assertEqual(result1.reasoning_text, "reasoning content")
|
||||
|
||||
result2 = self.detector.parse_streaming_increment("</think>answer")
|
||||
self.assertEqual(result2.normal_text, "answer")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
|
||||
def test_streaming_tool_interrupt(self):
|
||||
"""Test streaming parse interrupted by tool call section."""
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
result1 = self.detector.parse_streaming_increment("thinking")
|
||||
self.assertEqual(result1.reasoning_text, "thinking")
|
||||
|
||||
result2 = self.detector.parse_streaming_increment("<tool_calls>")
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
self.assertEqual(result2.normal_text, "<tool_calls>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
|
||||
def test_streaming_after_interrupt_is_normal(self):
|
||||
"""After tool interruption, subsequent chunks should be normal text."""
|
||||
self.detector.parse_streaming_increment("<think>")
|
||||
self.detector.parse_streaming_increment("reasoning<tool_calls>")
|
||||
result = self.detector.parse_streaming_increment("<tool_call>data")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
self.assertEqual(result.normal_text, "<tool_call>data")
|
||||
|
||||
def test_reasoning_parser_integration(self):
|
||||
"""Test Hunyuan through ReasoningParser API."""
|
||||
parser = ReasoningParser("hunyuan")
|
||||
@@ -617,21 +311,6 @@ class TestNemotron3Detector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = Nemotron3Detector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test Nemotron3Detector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<think>")
|
||||
self.assertEqual(self.detector.think_end_token, "</think>")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
self.assertFalse(self.detector._force_nonempty_content)
|
||||
|
||||
def test_detect_and_parse_complete_reasoning(self):
|
||||
"""Test parsing complete reasoning block."""
|
||||
text = "<think>Let me think about this</think>The answer is 42."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Let me think about this")
|
||||
self.assertEqual(result.normal_text, "The answer is 42.")
|
||||
|
||||
def test_detect_and_parse_no_thinking(self):
|
||||
"""Test parsing without thinking tokens."""
|
||||
text = "Direct answer without thinking."
|
||||
@@ -671,28 +350,11 @@ class TestNemotron3Detector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, "Truncated reasoning without end token")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_force_nonempty_content_no_thinking_tokens(self):
|
||||
"""Test force_nonempty_content with plain text (no thinking tokens)."""
|
||||
detector = Nemotron3Detector(force_nonempty_content=True)
|
||||
text = "Plain text without any thinking."
|
||||
result = detector.detect_and_parse(text)
|
||||
# Normal text already exists, no swap needed
|
||||
self.assertEqual(result.normal_text, text)
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
|
||||
class TestGemma4Detector(CustomTestCase):
|
||||
def setUp(self):
|
||||
self.detector = Gemma4Detector()
|
||||
|
||||
def test_init(self):
|
||||
"""Test Gemma4Detector initialization."""
|
||||
self.assertEqual(self.detector.think_start_token, "<|channel>")
|
||||
self.assertEqual(self.detector.think_end_token, "<channel|>")
|
||||
self.assertEqual(self.detector.think_start_self_label, "thought\n")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertTrue(self.detector.stream_reasoning)
|
||||
|
||||
def test_detect_and_parse_complete_reasoning(self):
|
||||
"""Test parsing complete Gemma4 reasoning block (think_start_self_label is stripped)."""
|
||||
text = "<|channel>thought\nLet me think about this<channel|>The answer is 42."
|
||||
@@ -707,49 +369,6 @@ class TestGemma4Detector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, text)
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_detect_and_parse_reasoning_only(self):
|
||||
"""Test parsing when output is all reasoning (no end token yet)."""
|
||||
text = "<|channel>thought\nStill thinking..."
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "Still thinking...")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
def test_streaming_complete_flow(self):
|
||||
"""Test streaming parse of Gemma4 reasoning flow."""
|
||||
chunks = [
|
||||
"<|channel>",
|
||||
"thought\nreasoning content",
|
||||
"<channel|>",
|
||||
"final answer",
|
||||
]
|
||||
all_reasoning = ""
|
||||
all_normal = ""
|
||||
for chunk in chunks:
|
||||
result = self.detector.parse_streaming_increment(chunk)
|
||||
all_reasoning += result.reasoning_text
|
||||
all_normal += result.normal_text
|
||||
self.assertIn("reasoning content", all_reasoning)
|
||||
self.assertIn("final answer", all_normal)
|
||||
|
||||
def test_streaming_full_start_sequence(self):
|
||||
"""Test streaming with the full start sequence (token + self_label)."""
|
||||
# Gemma4 start sequence is "<|channel>thought\n", not just "<|channel>"
|
||||
result = self.detector.parse_streaming_increment("<|channel>thought\n")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
self.assertTrue(self.detector._in_reasoning)
|
||||
|
||||
result = self.detector.parse_streaming_increment("reasoning content")
|
||||
self.assertEqual(result.reasoning_text, "reasoning content")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
|
||||
def test_streaming_partial_start_buffered(self):
|
||||
"""Test that partial start sequence is buffered."""
|
||||
# "<|channel>" alone is a prefix of "<|channel>thought\n", so it's buffered
|
||||
result = self.detector.parse_streaming_increment("<|channel>")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
|
||||
def test_streaming_end_token_mid_chunk(self):
|
||||
"""Test end token arriving in the same chunk as reasoning content."""
|
||||
self.detector.parse_streaming_increment("<|channel>thought\n")
|
||||
@@ -760,18 +379,6 @@ class TestGemma4Detector(CustomTestCase):
|
||||
self.assertEqual(result.normal_text, "the answer")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
|
||||
def test_streaming_split_end_token(self):
|
||||
"""Test end token split across two chunks."""
|
||||
self.detector.parse_streaming_increment("<|channel>thought\n")
|
||||
self.detector.parse_streaming_increment("reasoning content")
|
||||
|
||||
result1 = self.detector.parse_streaming_increment("<chan")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
|
||||
result2 = self.detector.parse_streaming_increment("nel|>final answer")
|
||||
self.assertFalse(self.detector._in_reasoning)
|
||||
self.assertIn("final answer", result2.normal_text)
|
||||
|
||||
def test_streaming_self_label_split_across_chunks(self):
|
||||
"""Test self_label ('thought\\n') arriving separately from start token."""
|
||||
result1 = self.detector.parse_streaming_increment("<|channel>")
|
||||
@@ -784,37 +391,6 @@ class TestGemma4Detector(CustomTestCase):
|
||||
result3 = self.detector.parse_streaming_increment("reasoning here")
|
||||
self.assertEqual(result3.reasoning_text, "reasoning here")
|
||||
|
||||
def test_streaming_force_reasoning(self):
|
||||
"""Test streaming with force_reasoning=True (no start token needed)."""
|
||||
detector = Gemma4Detector(force_reasoning=True)
|
||||
|
||||
result1 = detector.parse_streaming_increment("reasoning content")
|
||||
self.assertEqual(result1.reasoning_text, "reasoning content")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
|
||||
result2 = detector.parse_streaming_increment("<channel|>the answer")
|
||||
self.assertFalse(detector._in_reasoning)
|
||||
self.assertIn("the answer", result2.normal_text)
|
||||
|
||||
def test_streaming_multiple_reasoning_chunks(self):
|
||||
"""Test reasoning content arriving in many small chunks."""
|
||||
self.detector.parse_streaming_increment("<|channel>thought\n")
|
||||
|
||||
all_reasoning = ""
|
||||
for chunk in ["Think", "ing ", "step ", "by ", "step."]:
|
||||
result = self.detector.parse_streaming_increment(chunk)
|
||||
all_reasoning += result.reasoning_text
|
||||
self.assertEqual(result.normal_text, "")
|
||||
self.assertEqual(all_reasoning, "Thinking step by step.")
|
||||
|
||||
def test_force_reasoning(self):
|
||||
"""Test Gemma4Detector with force_reasoning=True."""
|
||||
detector = Gemma4Detector(force_reasoning=True)
|
||||
text = "This should be reasoning<channel|>The answer."
|
||||
result = detector.detect_and_parse(text)
|
||||
self.assertEqual(result.reasoning_text, "This should be reasoning")
|
||||
self.assertEqual(result.normal_text, "The answer.")
|
||||
|
||||
|
||||
class TestReasoningParser(CustomTestCase):
|
||||
def test_init_valid_model(self):
|
||||
@@ -990,42 +566,6 @@ class TestReasoningParser(CustomTestCase):
|
||||
class TestIntegrationScenarios(CustomTestCase):
|
||||
"""Integration tests for realistic usage scenarios."""
|
||||
|
||||
def test_deepseek_r1_complete_response(self):
|
||||
"""Test complete DeepSeek-R1 response parsing."""
|
||||
parser = ReasoningParser("deepseek-r1")
|
||||
text = "I need to solve this step by step. First, I'll analyze the problem. The given equation is x + 2 = 5. To solve for x, I subtract 2 from both sides: x = 5 - 2 = 3.</think>The answer is x = 3."
|
||||
|
||||
reasoning, normal = parser.parse_non_stream(text)
|
||||
self.assertIn("step by step", reasoning)
|
||||
self.assertIn(
|
||||
"= 3", reasoning
|
||||
) # The reasoning contains "x = 5 - 2 = 3" which has "= 3"
|
||||
self.assertEqual(normal, "The answer is x = 3.")
|
||||
|
||||
def test_qwen3_streaming_scenario(self):
|
||||
"""Test Qwen3 streaming scenario."""
|
||||
parser = ReasoningParser("qwen3")
|
||||
|
||||
chunks = [
|
||||
"<think>",
|
||||
"Let me analyze this problem.",
|
||||
" I need to consider multiple factors.",
|
||||
"</think>",
|
||||
"Based on my analysis, the solution is to use a different approach.",
|
||||
]
|
||||
|
||||
all_reasoning = ""
|
||||
all_normal = ""
|
||||
|
||||
for chunk in chunks:
|
||||
reasoning, normal = parser.parse_stream_chunk(chunk)
|
||||
all_reasoning += reasoning
|
||||
all_normal += normal
|
||||
|
||||
self.assertIn("analyze", all_reasoning)
|
||||
self.assertIn("multiple factors", all_reasoning)
|
||||
self.assertIn("different approach", all_normal)
|
||||
|
||||
def test_kimi_streaming_scenario(self):
|
||||
"""Test Kimi streaming scenario."""
|
||||
parser = ReasoningParser("kimi")
|
||||
@@ -1049,35 +589,6 @@ class TestIntegrationScenarios(CustomTestCase):
|
||||
self.assertIn("multiple factors", all_reasoning)
|
||||
self.assertIn("42", all_normal)
|
||||
|
||||
def test_gemma4_complete_response(self):
|
||||
"""Test complete Gemma4 response parsing (think_start_self_label stripped)."""
|
||||
parser = ReasoningParser("gemma4")
|
||||
text = "<|channel>thought\nI need to solve x + 2 = 5. Subtracting 2: x = 3.<channel|>The answer is x = 3."
|
||||
reasoning, normal = parser.parse_non_stream(text)
|
||||
self.assertIn("x = 3", reasoning)
|
||||
self.assertNotIn("thought\n", reasoning)
|
||||
self.assertEqual(normal, "The answer is x = 3.")
|
||||
|
||||
def test_gemma4_streaming_scenario(self):
|
||||
"""Test Gemma4 streaming scenario."""
|
||||
parser = ReasoningParser("gemma4")
|
||||
chunks = [
|
||||
"<|channel>",
|
||||
"thought\nLet me analyze.",
|
||||
" Multiple factors.",
|
||||
"<channel|>",
|
||||
"The solution is 42.",
|
||||
]
|
||||
all_reasoning = ""
|
||||
all_normal = ""
|
||||
for chunk in chunks:
|
||||
reasoning, normal = parser.parse_stream_chunk(chunk)
|
||||
all_reasoning += reasoning
|
||||
all_normal += normal
|
||||
self.assertIn("analyze", all_reasoning)
|
||||
self.assertIn("Multiple factors", all_reasoning)
|
||||
self.assertIn("42", all_normal)
|
||||
|
||||
def test_empty_reasoning_blocks(self):
|
||||
"""Test handling of empty reasoning blocks."""
|
||||
parser = ReasoningParser("qwen3")
|
||||
@@ -1157,22 +668,6 @@ class TestBufferLossBugFix(CustomTestCase):
|
||||
self.assertEqual(result2.normal_text, "</answer")
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
|
||||
def test_partial_start_tag_buffer_preservation(self):
|
||||
"""
|
||||
Test that partial start tag fragments are properly preserved.
|
||||
"""
|
||||
detector = BaseReasoningFormatDetector("<think>", "</think>")
|
||||
|
||||
# Send partial start tag
|
||||
result1 = detector.parse_streaming_increment("<th")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
self.assertEqual(result1.reasoning_text, "")
|
||||
|
||||
# Complete with non-matching text
|
||||
result2 = detector.parse_streaming_increment("is is text")
|
||||
self.assertEqual(result2.normal_text, "<this is text")
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
|
||||
def test_partial_end_tag_in_reasoning_mode(self):
|
||||
"""
|
||||
Test partial end tag handling when already in reasoning mode.
|
||||
@@ -1194,25 +689,6 @@ class TestBufferLossBugFix(CustomTestCase):
|
||||
# The reasoning text should be empty since buffer was cleared when end tag was processed
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
|
||||
def test_multiple_partial_fragments(self):
|
||||
"""
|
||||
Test handling of multiple partial fragments that don't match any tokens.
|
||||
"""
|
||||
detector = BaseReasoningFormatDetector("<think>", "</think>")
|
||||
|
||||
# Send multiple partial fragments
|
||||
result1 = detector.parse_streaming_increment("<")
|
||||
self.assertEqual(result1.normal_text, "")
|
||||
self.assertEqual(result1.reasoning_text, "")
|
||||
|
||||
result2 = detector.parse_streaming_increment("/")
|
||||
self.assertEqual(result2.normal_text, "")
|
||||
self.assertEqual(result2.reasoning_text, "")
|
||||
|
||||
result3 = detector.parse_streaming_increment("random>")
|
||||
self.assertEqual(result3.normal_text, "</random>")
|
||||
self.assertEqual(result3.reasoning_text, "")
|
||||
|
||||
def test_edge_case_exact_token_match(self):
|
||||
"""
|
||||
Test edge case where buffer content exactly matches a token.
|
||||
@@ -1242,19 +718,6 @@ class TestGptOssDetector(CustomTestCase):
|
||||
|
||||
self.detector = GptOssDetector()
|
||||
|
||||
def test_detect_and_parse_with_analysis_and_final(self):
|
||||
"""Test one-shot parsing with analysis (reasoning) and final (normal) blocks."""
|
||||
text = "<|start|><|channel|>analysis<|message|>thinking hard<|end|><|channel|>final<|message|>the answer<|end|>"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertIn("thinking hard", result.reasoning_text)
|
||||
self.assertIn("the answer", result.normal_text)
|
||||
|
||||
def test_detect_and_parse_normal_only(self):
|
||||
"""Test one-shot parsing with only final block."""
|
||||
text = "<|start|><|channel|>final<|message|>just the answer<|end|>"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertIn("just the answer", result.normal_text)
|
||||
|
||||
def test_streaming_analysis_then_final(self):
|
||||
"""Test streaming parse across multiple chunks."""
|
||||
chunks = [
|
||||
@@ -1273,13 +736,6 @@ class TestGptOssDetector(CustomTestCase):
|
||||
self.assertIn("reasoning part", all_reasoning)
|
||||
self.assertIn("answer", all_normal)
|
||||
|
||||
def test_streaming_with_tool_call(self):
|
||||
"""Test streaming parse with tool call events."""
|
||||
text = "<|start|><|channel|>analysis<|message|>think<|end|><|call|>tool_data<|return|><|channel|>final<|message|>result<|end|>"
|
||||
result = self.detector.detect_and_parse(text)
|
||||
self.assertIn("think", result.reasoning_text)
|
||||
self.assertIn("result", result.normal_text)
|
||||
|
||||
|
||||
class TestMiniMaxAppendThinkDetector(CustomTestCase):
|
||||
"""Test cases for MiniMaxAppendThinkDetector."""
|
||||
@@ -1478,18 +934,6 @@ class TestContinueFinalMessage(CustomTestCase):
|
||||
self.assertEqual(result.reasoning_text, "new reasoning")
|
||||
self.assertEqual(result.normal_text, "new answer")
|
||||
|
||||
def test_streaming_returns_empty_when_in_reasoning_and_end_buffered(self):
|
||||
"""Test that streaming returns empty when buffer could be partial end token."""
|
||||
detector = BaseReasoningFormatDetector(
|
||||
"<think>", "</think>", force_reasoning=True, stream_reasoning=True
|
||||
)
|
||||
# In reasoning mode, send partial end token
|
||||
result = detector.parse_streaming_increment("</")
|
||||
self.assertEqual(result.reasoning_text, "")
|
||||
self.assertEqual(result.normal_text, "")
|
||||
# This goes through the path where _in_reasoning is True but buffer
|
||||
# is a prefix of think_end_token → returns empty
|
||||
|
||||
|
||||
class TestGptOssDetectorToolCall(CustomTestCase):
|
||||
"""Test GptOssDetector tool_call raw_text handling."""
|
||||
|
||||
Reference in New Issue
Block a user