main
py 162 lines 5.48 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from typing import cast
5
6 from transformers import PreTrainedTokenizerBase
7
8 from vllm.entrypoints.generate.base.protocol import DeltaMessage
9 from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
10 from vllm.reasoning import ReasoningParser
11 from vllm.utils.mistral import is_mistral_tokenizer
12
13
14 class StreamingReasoningReconstructor:
15 def __init__(self):
16 self.reasoning = None
17 self.other_content = None
18
19 def append_delta(self, delta: DeltaMessage):
20 # content and the reasoning content should not be present
21 # at the same time
22 assert delta.content is None or delta.reasoning is None, (
23 "Both content and reasoning content are present in the delta message"
24 )
25 if delta.content is not None:
26 if self.other_content is None:
27 self.other_content = delta.content
28 else:
29 self.other_content += delta.content
30 else:
31 if self.reasoning is None:
32 self.reasoning = delta.reasoning
33 else:
34 self.reasoning += delta.reasoning
35
36
37 def run_reasoning_extraction(
38 reasoning_parser: ReasoningParser,
39 model_output: list[str],
40 request: ChatCompletionRequest | None = None,
41 streaming: bool = False,
42 ) -> tuple[str | None, str | None]:
43 if streaming:
44 reconstructor = run_reasoning_extraction_streaming(
45 reasoning_parser,
46 model_output,
47 request,
48 )
49 return (
50 reconstructor.reasoning,
51 reconstructor.other_content or None,
52 )
53 else:
54 reasoning, content = run_reasoning_extraction_nonstreaming(
55 reasoning_parser, model_output, request
56 )
57 return reasoning, content
58
59
60 def run_reasoning_extraction_mistral(
61 reasoning_parser: ReasoningParser,
62 model_output: list[int],
63 request: ChatCompletionRequest | None = None,
64 streaming: bool = False,
65 ) -> tuple[str | None, str | None]:
66 assert is_mistral_tokenizer(reasoning_parser.model_tokenizer), type(
67 reasoning_parser.model_tokenizer
68 )
69 if streaming:
70 reconstructor = run_reasoning_extraction_streaming_mistral(
71 reasoning_parser,
72 model_output,
73 request,
74 )
75 return (
76 reconstructor.reasoning,
77 reconstructor.other_content or None,
78 )
79 else:
80 str_output = reasoning_parser.model_tokenizer.convert_ids_to_tokens(
81 model_output
82 )
83 reasoning, content = run_reasoning_extraction_nonstreaming(
84 reasoning_parser, str_output, request
85 )
86 return reasoning, content
87
88
89 def run_reasoning_extraction_nonstreaming(
90 reasoning_parser: ReasoningParser,
91 model_output: list[str],
92 request: ChatCompletionRequest | None = None,
93 ) -> tuple[str | None, str | None]:
94 request = request or ChatCompletionRequest(messages=[], model="test-model")
95 return reasoning_parser.extract_reasoning(
96 model_output="".join(model_output), request=request
97 )
98
99
100 def run_reasoning_extraction_streaming(
101 reasoning_parser: ReasoningParser,
102 model_deltas: list[str],
103 request: ChatCompletionRequest | None = None,
104 ) -> StreamingReasoningReconstructor:
105 request = request or ChatCompletionRequest(messages=[], model="test-model")
106 reconstructor = StreamingReasoningReconstructor()
107 previous_text = ""
108 previous_tokens: list[int] = []
109 tokenizer = cast(PreTrainedTokenizerBase, reasoning_parser.model_tokenizer)
110 for delta in model_deltas:
111 token_delta = [
112 reasoning_parser.vocab[token]
113 for token in tokenizer.tokenize(delta)
114 if token in reasoning_parser.vocab
115 ]
116 current_text = previous_text + delta
117 current_tokens = previous_tokens + token_delta
118 delta_message = reasoning_parser.extract_reasoning_streaming(
119 previous_text,
120 current_text,
121 delta,
122 previous_tokens,
123 current_tokens,
124 token_delta,
125 )
126 if delta_message is not None:
127 reconstructor.append_delta(delta_message)
128 previous_text = current_text
129 previous_tokens = current_tokens
130 return reconstructor
131
132
133 def run_reasoning_extraction_streaming_mistral(
134 reasoning_parser: ReasoningParser,
135 model_deltas: list[int],
136 request: ChatCompletionRequest | None = None,
137 ) -> StreamingReasoningReconstructor:
138 assert is_mistral_tokenizer(reasoning_parser.model_tokenizer), type(
139 reasoning_parser.model_tokenizer
140 )
141 request = request or ChatCompletionRequest(messages=[], model="test-model")
142 reconstructor = StreamingReasoningReconstructor()
143 previous_text = ""
144 previous_tokens: list[int] = []
145 for model_delta in model_deltas:
146 token_delta = [model_delta]
147 delta = reasoning_parser.model_tokenizer.convert_ids_to_tokens([model_delta])[0]
148 current_text = previous_text + delta
149 current_tokens = previous_tokens + token_delta
150 delta_message = reasoning_parser.extract_reasoning_streaming(
151 previous_text,
152 current_text,
153 delta,
154 previous_tokens,
155 current_tokens,
156 token_delta,
157 )
158 if delta_message is not None:
159 reconstructor.append_delta(delta_message)
160 previous_text = current_text
161 previous_tokens = current_tokens
162 return reconstructor