main
py 71 lines 3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Laguna reasoning parser.
4
5 ``DeepSeekV3ReasoningParser.is_reasoning_end`` walks the entire
6 token sequence backwards and returns ``True`` on the first ``</think>`` it
7 sees. When called on ``prompt_token_ids`` that mistakes any stray
8 ``</think>`` in conversation history, few-shot examples or tool descriptions
9 for a template-injected "thinking already ended" marker. In the streaming
10 path (see ``vllm/entrypoints/openai/chat_completion/serving.py``,
11 ``prompt_is_reasoning_end_arr``) that false positive short-circuits the
12 reasoning parser for the whole response, so any ``<think>...</think>`` the
13 model emits itself ends up in the content field instead of the reasoning
14 field.
15
16 As we have more flexible templates, we instead scope
17 the backward search to the current assistant turn: the
18 walk terminates as soon as we hit the ``<assistant>`` start-of-message
19 token. A ``</think>`` in a prior user turn or few-shot example is no longer
20 visible.
21 """
22
23 from collections.abc import Sequence
24
25 from transformers import PreTrainedTokenizerBase
26
27 from vllm.reasoning.deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser
28 from vllm.reasoning.deepseek_v3_reasoning_parser import DeepSeekV3ReasoningParser
29 from vllm.reasoning.identity_reasoning_parser import IdentityReasoningParser
30
31
32 class PoolsideV1ReasoningParser(DeepSeekV3ReasoningParser):
33 """Drop-in replacement for ``deepseek_v3`` that tolerates ``</think>``
34 tokens appearing anywhere in the prompt other than the generation prefix.
35 """
36
37 _start_of_assistant_message = "<assistant>"
38
39 def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
40 super().__init__(tokenizer, *args, **kwargs)
41
42 if self._start_of_assistant_message not in self.vocab:
43 raise ValueError(
44 f"Tokenizer must contain {self._start_of_assistant_message!r} token"
45 )
46 self._start_of_assistant_message_token_id = self.vocab[
47 self._start_of_assistant_message
48 ]
49
50 def is_reasoning_end(self, input_ids: Sequence[int]) -> bool:
51 # IdentityReasoningParser always returns True: no reasoning to parse.
52 if isinstance(self._parser, IdentityReasoningParser):
53 return True
54
55 assert isinstance(self._parser, DeepSeekR1ReasoningParser)
56 for tok_id in reversed(input_ids):
57 # <think>: reasoning is not yet ended.
58 if tok_id == self._parser.start_token_id:
59 return False
60 # </think>: reasoning has ended.
61 if tok_id == self._parser.end_token_id:
62 return True
63 # <assistant>: reached the start of the current assistant turn
64 # without seeing either marker. Anything further back belongs to
65 # the prior conversation and should be ignored.
66 if tok_id == self._start_of_assistant_message_token_id:
67 return False
68 return False
69
70
71 __all__ = ["PoolsideV1ReasoningParser"]