Text Generation
Transformers
Safetensors
PyTorch
nemotron_h
nvidia
nemotron-3
latent-moe
mtp
conversational
custom_code
8-bit precision
modelopt

Add streaming reasoning extraction with content promotion

#30
Files changed (1) hide show
  1. super_v3_reasoning_parser.py +52 -1
super_v3_reasoning_parser.py CHANGED
@@ -1,9 +1,23 @@
 
 
 
1
  from vllm.reasoning.abs_reasoning_parsers import ReasoningParserManager
2
  from vllm.reasoning.deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser
3
 
4
 
5
  @ReasoningParserManager.register_module("super_v3")
6
  class SuperV3ReasoningParser(DeepSeekR1ReasoningParser):
 
 
 
 
 
 
 
 
 
 
 
7
  def extract_reasoning(self, model_output, request):
8
  reasoning_content, final_content = super().extract_reasoning(
9
  model_output, request
@@ -25,4 +39,41 @@ class SuperV3ReasoningParser(DeepSeekR1ReasoningParser):
25
  # Put all nonempty content into the content, rather than return content
26
  reasoning_content, final_content = None, reasoning_content
27
 
28
- return reasoning_content, final_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Sequence
2
+
3
+ from vllm.entrypoints.openai.engine.protocol import DeltaMessage
4
  from vllm.reasoning.abs_reasoning_parsers import ReasoningParserManager
5
  from vllm.reasoning.deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser
6
 
7
 
8
  @ReasoningParserManager.register_module("super_v3")
9
  class SuperV3ReasoningParser(DeepSeekR1ReasoningParser):
10
+
11
+ def __init__(self, tokenizer, *args, **kwargs):
12
+ super().__init__(tokenizer, *args, **kwargs)
13
+ self._promote_reasoning_to_content: bool = False
14
+ chat_template_kwargs = kwargs.get("chat_template_kwargs") or {}
15
+ if isinstance(chat_template_kwargs, dict):
16
+ self._promote_reasoning_to_content = (
17
+ chat_template_kwargs.get("enable_thinking") is False
18
+ or chat_template_kwargs.get("force_nonempty_content") is True
19
+ )
20
+
21
  def extract_reasoning(self, model_output, request):
22
  reasoning_content, final_content = super().extract_reasoning(
23
  model_output, request
 
39
  # Put all nonempty content into the content, rather than return content
40
  reasoning_content, final_content = None, reasoning_content
41
 
42
+ return reasoning_content, final_content
43
+
44
+ def extract_reasoning_streaming(
45
+ self,
46
+ previous_text: str,
47
+ current_text: str,
48
+ delta_text: str,
49
+ previous_token_ids: Sequence[int],
50
+ current_token_ids: Sequence[int],
51
+ delta_token_ids: Sequence[int],
52
+ ) -> DeltaMessage | None:
53
+ delta_message = super().extract_reasoning_streaming(
54
+ previous_text,
55
+ current_text,
56
+ delta_text,
57
+ previous_token_ids,
58
+ current_token_ids,
59
+ delta_token_ids,
60
+ )
61
+ if not self._promote_reasoning_to_content:
62
+ return delta_message
63
+ if delta_message is None or not delta_message.reasoning:
64
+ return delta_message
65
+ # Skip the <think> tag token itself — its text is the raw tag, not content.
66
+ if self.start_token_id in delta_token_ids and self.end_token_id not in delta_token_ids:
67
+ return delta_message
68
+ end_pos = current_text.find(self.end_token)
69
+ content_after_think = (
70
+ current_text[end_pos + len(self.end_token):]
71
+ if end_pos != -1 else ""
72
+ )
73
+ if content_after_think:
74
+ return delta_message
75
+ # Duplicate mode: keep reasoning channel, also populate content so
76
+ # clients that only read content get a non-null value on every delta.
77
+ if not delta_message.content:
78
+ delta_message.content = delta_message.reasoning
79
+ return delta_message