| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import pytest |
| 5 | from transformers import AutoTokenizer, PreTrainedTokenizerBase |
| 6 | |
| 7 | from vllm.entrypoints.generate.base.protocol import DeltaMessage |
| 8 | from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest |
| 9 | from vllm.reasoning import ReasoningParserManager |
| 10 | from vllm.reasoning.deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser |
| 11 | from vllm.reasoning.deepseek_v3_reasoning_parser import DeepSeekV3ReasoningParser |
| 12 | from vllm.reasoning.identity_reasoning_parser import IdentityReasoningParser |
| 13 | |
| 14 | pytestmark = pytest.mark.skip_global_cleanup |
| 15 | |
| 16 | REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-V3.1" |
| 17 | |
| 18 | |
| 19 | @pytest.fixture(scope="module") |
| 20 | def tokenizer() -> PreTrainedTokenizerBase: |
| 21 | return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME) |
| 22 | |
| 23 | |
| 24 | @pytest.mark.parametrize( |
| 25 | "thinking,expected_parser_type", |
| 26 | [ |
| 27 | (True, DeepSeekR1ReasoningParser), |
| 28 | (False, IdentityReasoningParser), |
| 29 | ], |
| 30 | ) |
| 31 | def test_parser_selection(tokenizer, thinking, expected_parser_type): |
| 32 | parser = DeepSeekV3ReasoningParser( |
| 33 | tokenizer, chat_template_kwargs={"thinking": thinking} |
| 34 | ) |
| 35 | |
| 36 | assert isinstance(parser._parser, expected_parser_type) |
| 37 | |
| 38 | |
| 39 | def test_deepseek_v4_reasoning_parser_alias(): |
| 40 | from vllm.reasoning.deepseek_v4_engine_reasoning_parser import ( |
| 41 | DeepSeekV4ParserReasoningAdapter, |
| 42 | ) |
| 43 | |
| 44 | parser_cls = ReasoningParserManager.get_reasoning_parser("deepseek_v4") |
| 45 | |
| 46 | assert parser_cls is DeepSeekV4ParserReasoningAdapter |
| 47 | |
| 48 | |
| 49 | def test_identity_reasoning_parser_basic(tokenizer): |
| 50 | parser = IdentityReasoningParser(tokenizer) |
| 51 | |
| 52 | # Test is_reasoning_end always returns True |
| 53 | input_text = "This is some output" |
| 54 | input_tokens = tokenizer.tokenize(input_text) |
| 55 | input_ids = tokenizer.convert_tokens_to_ids(input_tokens) |
| 56 | assert parser.is_reasoning_end(input_ids) is True |
| 57 | assert parser.is_reasoning_end_streaming(input_ids, input_ids) is True |
| 58 | |
| 59 | # Test extract_content_ids returns all input_ids |
| 60 | assert parser.extract_content_ids(input_ids) == input_ids |
| 61 | |
| 62 | # Test extract_reasoning returns (None, model_output) |
| 63 | request = ChatCompletionRequest(model="test-model", messages=[], temperature=1.0) |
| 64 | reasoning, content = parser.extract_reasoning(input_text, request) |
| 65 | assert reasoning is None |
| 66 | assert content == input_text |
| 67 | |
| 68 | # Test extract_reasoning_streaming returns DeltaMessage or None |
| 69 | result = parser.extract_reasoning_streaming( |
| 70 | previous_text="", |
| 71 | current_text="Hello world", |
| 72 | delta_text="Hello world", |
| 73 | previous_token_ids=[], |
| 74 | current_token_ids=input_ids, |
| 75 | delta_token_ids=input_ids, |
| 76 | ) |
| 77 | assert isinstance(result, DeltaMessage) |
| 78 | assert result.content == "Hello world" |
| 79 | |
| 80 | # If delta_text is empty, should return None |
| 81 | result_none = parser.extract_reasoning_streaming( |
| 82 | previous_text="Hello world", |
| 83 | current_text="Hello world", |
| 84 | delta_text="", |
| 85 | previous_token_ids=input_ids, |
| 86 | current_token_ids=input_ids, |
| 87 | delta_token_ids=[], |
| 88 | ) |
| 89 | assert result_none is None |
| 90 | |
| 91 | |
| 92 | @pytest.mark.parametrize("thinking,expected", [(True, 3), (False, 0)]) |
| 93 | def test_count_reasoning_tokens_delegates(tokenizer, thinking, expected): |
| 94 | parser = DeepSeekV3ReasoningParser( |
| 95 | tokenizer, chat_template_kwargs={"thinking": thinking} |
| 96 | ) |
| 97 | think = tokenizer.convert_tokens_to_ids(["<think>", "</think>"]) |
| 98 | token_ids = [think[0], 11, 12, 13, think[1], 14] |
| 99 | |
| 100 | assert parser.count_reasoning_tokens(token_ids) == expected |