diff --git a/docs/features/reasoning_outputs.md b/docs/features/reasoning_outputs.md
index 85681669dfb2..a30d0b1223c7 100644
--- a/docs/features/reasoning_outputs.md
+++ b/docs/features/reasoning_outputs.md
@@ -11,6 +11,7 @@ vLLM currently supports the following reasoning models:
| Model Series | Parser Name | Structured Output Support | Tool Calling |
|--------------|-------------|------------------|-------------|
| [DeepSeek R1 series](https://huggingface.co/collections/deepseek-ai/deepseek-r1-678e1e131c0169c0bc89728d) | `deepseek_r1` | `json`, `regex` | ❌ |
+| [DeepSeek-V3.1](https://huggingface.co/collections/deepseek-ai/deepseek-v31-68a491bed32bd77e7fca048f) | `deepseek_v3` | `json`, `regex` | ❌ |
| [QwQ-32B](https://huggingface.co/Qwen/QwQ-32B) | `deepseek_r1` | `json`, `regex` | ✅ |
| [IBM Granite 3.2 language models](https://huggingface.co/collections/ibm-granite/granite-32-language-models-67b3bc8c13508f6d064cff9a) | `granite` | ❌ | ❌ |
| [Qwen3 series](https://huggingface.co/collections/Qwen/qwen3-67dd247413f0e2e4f653967f) | `qwen3` | `json`, `regex` | ✅ |
diff --git a/tests/reasoning/test_deepseekv3_reasoning_parser.py b/tests/reasoning/test_deepseekv3_reasoning_parser.py
new file mode 100644
index 000000000000..08e012b2a312
--- /dev/null
+++ b/tests/reasoning/test_deepseekv3_reasoning_parser.py
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
+
+import pytest
+from transformers import AutoTokenizer
+
+from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
+ DeltaMessage)
+from vllm.reasoning import (DeepSeekR1ReasoningParser,
+ DeepSeekV3ReasoningParser, IdentityReasoningParser)
+
+REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-V3.1"
+
+
+@pytest.fixture(scope="module")
+def tokenizer():
+ return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
+
+
+@pytest.mark.parametrize(
+ "thinking,expected_parser_type",
+ [
+ (True, DeepSeekR1ReasoningParser),
+ (False, IdentityReasoningParser),
+ ],
+)
+def test_parser_selection(tokenizer, thinking, expected_parser_type):
+ parser = DeepSeekV3ReasoningParser(tokenizer, thinking=thinking)
+ assert isinstance(parser._parser, expected_parser_type)
+
+
+def test_identity_reasoning_parser_basic(tokenizer):
+ parser = IdentityReasoningParser(tokenizer)
+
+ # Test is_reasoning_end always returns False
+ input_text = "This is some output"
+ input_tokens = tokenizer.tokenize(input_text)
+ input_ids = tokenizer.convert_tokens_to_ids(input_tokens)
+ assert parser.is_reasoning_end(input_ids) is False
+
+ # Test extract_content_ids returns all input_ids
+ assert parser.extract_content_ids(input_ids) == input_ids
+
+ # Test extract_reasoning_content returns (None, model_output)
+ request = ChatCompletionRequest(model="test-model",
+ messages=[],
+ temperature=1.0)
+ reasoning, content = parser.extract_reasoning_content(input_text, request)
+ assert reasoning is None
+ assert content == input_text
+
+ # Test extract_reasoning_content_streaming returns DeltaMessage or None
+ result = parser.extract_reasoning_content_streaming(
+ previous_text="",
+ current_text="Hello world",
+ delta_text="Hello world",
+ previous_token_ids=[],
+ current_token_ids=input_ids,
+ delta_token_ids=input_ids,
+ )
+ assert isinstance(result, DeltaMessage)
+ assert result.content == "Hello world"
+
+ # If delta_text is empty, should return None
+ result_none = parser.extract_reasoning_content_streaming(
+ previous_text="Hello world",
+ current_text="Hello world",
+ delta_text="",
+ previous_token_ids=input_ids,
+ current_token_ids=input_ids,
+ delta_token_ids=[],
+ )
+ assert result_none is None
diff --git a/vllm/entrypoints/openai/serving_chat.py b/vllm/entrypoints/openai/serving_chat.py
index 16564214e353..45eebaf6a2f0 100644
--- a/vllm/entrypoints/openai/serving_chat.py
+++ b/vllm/entrypoints/openai/serving_chat.py
@@ -527,7 +527,8 @@ async def chat_completion_stream_generator(
try:
if self.reasoning_parser:
- reasoning_parser = self.reasoning_parser(tokenizer)
+ reasoning_parser = self.reasoning_parser(
+ tokenizer, **(request.chat_template_kwargs or {}))
except RuntimeError as e:
logger.exception("Error in reasoning parser creation.")
data = self.create_streaming_error_response(str(e))
@@ -1230,7 +1231,8 @@ async def chat_completion_full_generator(
if self.reasoning_parser:
try:
- reasoning_parser = self.reasoning_parser(tokenizer)
+ reasoning_parser = self.reasoning_parser(
+ tokenizer, **(request.chat_template_kwargs or {}))
except RuntimeError as e:
logger.exception("Error in reasoning parser creation.")
return self.create_error_response(str(e))
diff --git a/vllm/reasoning/__init__.py b/vllm/reasoning/__init__.py
index b987adeb6428..97f02f003584 100644
--- a/vllm/reasoning/__init__.py
+++ b/vllm/reasoning/__init__.py
@@ -3,10 +3,12 @@
from .abs_reasoning_parsers import ReasoningParser, ReasoningParserManager
from .deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser
+from .deepseek_v3_reasoning_parser import DeepSeekV3ReasoningParser
from .glm4_moe_reasoning_parser import Glm4MoeModelReasoningParser
from .gptoss_reasoning_parser import GptOssReasoningParser
from .granite_reasoning_parser import GraniteReasoningParser
from .hunyuan_a13b_reasoning_parser import HunyuanA13BReasoningParser
+from .identity_reasoning_parser import IdentityReasoningParser
from .mistral_reasoning_parser import MistralReasoningParser
from .qwen3_reasoning_parser import Qwen3ReasoningParser
from .step3_reasoning_parser import Step3ReasoningParser
@@ -15,6 +17,8 @@
"ReasoningParser",
"ReasoningParserManager",
"DeepSeekR1ReasoningParser",
+ "IdentityReasoningParser",
+ "DeepSeekV3ReasoningParser",
"GraniteReasoningParser",
"HunyuanA13BReasoningParser",
"Qwen3ReasoningParser",
diff --git a/vllm/reasoning/abs_reasoning_parsers.py b/vllm/reasoning/abs_reasoning_parsers.py
index df9e84163f16..6ab1f0d1ec18 100644
--- a/vllm/reasoning/abs_reasoning_parsers.py
+++ b/vllm/reasoning/abs_reasoning_parsers.py
@@ -34,7 +34,7 @@ class ReasoningParser:
It is used to extract reasoning content from the model output.
"""
- def __init__(self, tokenizer: AnyTokenizer):
+ def __init__(self, tokenizer: AnyTokenizer, *args, **kwargs):
self.model_tokenizer = tokenizer
@cached_property
diff --git a/vllm/reasoning/deepseek_r1_reasoning_parser.py b/vllm/reasoning/deepseek_r1_reasoning_parser.py
index 1a5ca46a60f1..063964177e25 100644
--- a/vllm/reasoning/deepseek_r1_reasoning_parser.py
+++ b/vllm/reasoning/deepseek_r1_reasoning_parser.py
@@ -29,7 +29,7 @@ class DeepSeekR1ReasoningParser(ReasoningParser):
start_token: str = ""
end_token: str = ""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
if not self.model_tokenizer:
diff --git a/vllm/reasoning/deepseek_v3_reasoning_parser.py b/vllm/reasoning/deepseek_v3_reasoning_parser.py
new file mode 100644
index 000000000000..0d5c3166c00a
--- /dev/null
+++ b/vllm/reasoning/deepseek_v3_reasoning_parser.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
+
+from collections.abc import Sequence
+from typing import Optional, Union
+
+from transformers import PreTrainedTokenizerBase
+
+from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
+ DeltaMessage)
+from vllm.logger import init_logger
+from vllm.reasoning import (DeepSeekR1ReasoningParser, ReasoningParser,
+ ReasoningParserManager)
+
+from .identity_reasoning_parser import IdentityReasoningParser
+
+logger = init_logger(__name__)
+
+
+@ReasoningParserManager.register_module("deepseek_v3")
+class DeepSeekV3ReasoningParser(ReasoningParser):
+ """
+ V3 parser that delegates to either DeepSeekR1ReasoningParser or
+ IdentityReasoningParser based on `thinking` and `separate_reasoning`.
+ """
+
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
+ super().__init__(tokenizer)
+
+ thinking = bool(kwargs.pop("thinking", False))
+
+ if thinking:
+ self._parser = DeepSeekR1ReasoningParser(tokenizer)
+ else:
+ self._parser = IdentityReasoningParser(tokenizer)
+
+ def is_reasoning_end(self, input_ids: Sequence[int]) -> bool:
+ return self._parser.is_reasoning_end(input_ids)
+
+ def extract_content_ids(self, input_ids: list[int]) -> list[int]:
+ return self._parser.extract_content_ids(input_ids)
+
+ def extract_reasoning_content(
+ self, model_output: str, request: ChatCompletionRequest
+ ) -> tuple[Optional[str], Optional[str]]:
+ return self._parser.extract_reasoning_content(model_output, request)
+
+ def extract_reasoning_content_streaming(
+ self,
+ previous_text: str,
+ current_text: str,
+ delta_text: str,
+ previous_token_ids: Sequence[int],
+ current_token_ids: Sequence[int],
+ delta_token_ids: Sequence[int],
+ ) -> Union[DeltaMessage, None]:
+ return self._parser.extract_reasoning_content_streaming(
+ previous_text,
+ current_text,
+ delta_text,
+ previous_token_ids,
+ current_token_ids,
+ delta_token_ids,
+ )
diff --git a/vllm/reasoning/glm4_moe_reasoning_parser.py b/vllm/reasoning/glm4_moe_reasoning_parser.py
index 460e38d2d396..1b31502cd9e8 100644
--- a/vllm/reasoning/glm4_moe_reasoning_parser.py
+++ b/vllm/reasoning/glm4_moe_reasoning_parser.py
@@ -26,7 +26,7 @@ class Glm4MoeModelReasoningParser(ReasoningParser):
from the model's output.
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
self.think_start_token = ""
self.think_end_token = ""
diff --git a/vllm/reasoning/gptoss_reasoning_parser.py b/vllm/reasoning/gptoss_reasoning_parser.py
index 3bd4d872ce22..a2bbc971a909 100644
--- a/vllm/reasoning/gptoss_reasoning_parser.py
+++ b/vllm/reasoning/gptoss_reasoning_parser.py
@@ -24,7 +24,7 @@ class GptOssReasoningParser(ReasoningParser):
is only used for detecting the end of the reasoning content.
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
self.reasoning_end_token_ids = self.model_tokenizer.encode(
"<|start|>assistant<|channel|>final<|message|>")
diff --git a/vllm/reasoning/granite_reasoning_parser.py b/vllm/reasoning/granite_reasoning_parser.py
index 5820001b918f..85f84f825ac8 100644
--- a/vllm/reasoning/granite_reasoning_parser.py
+++ b/vllm/reasoning/granite_reasoning_parser.py
@@ -24,7 +24,7 @@ class GraniteReasoningParser(ReasoningParser):
and "Here is my response:" to separate its thinking / response outputs.
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
# NOTE: There have been some observed occurrences of quantized
diff --git a/vllm/reasoning/hunyuan_a13b_reasoning_parser.py b/vllm/reasoning/hunyuan_a13b_reasoning_parser.py
index 9deec8a1e8fb..ef791fb41284 100644
--- a/vllm/reasoning/hunyuan_a13b_reasoning_parser.py
+++ b/vllm/reasoning/hunyuan_a13b_reasoning_parser.py
@@ -40,7 +40,7 @@ class HunyuanA13BReasoningParser(ReasoningParser):
response ends: "\n": [524, 9399, 29]
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
self.think_start_expr = r"\n"
self.think_end_expr = r"\n\n"
diff --git a/vllm/reasoning/identity_reasoning_parser.py b/vllm/reasoning/identity_reasoning_parser.py
new file mode 100644
index 000000000000..d26bea6f1e0a
--- /dev/null
+++ b/vllm/reasoning/identity_reasoning_parser.py
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
+
+from collections.abc import Sequence
+from typing import Optional, Union
+
+from transformers import PreTrainedTokenizerBase
+
+from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
+ DeltaMessage)
+from vllm.logger import init_logger
+from vllm.reasoning import ReasoningParser
+
+logger = init_logger(__name__)
+
+
+class IdentityReasoningParser(ReasoningParser):
+ """
+ Identity reasoning parser.
+
+ This parser does not attempt to parse or strip out reasoning tokens.
+ It treats the entire model output as content and ignores reasoning.
+ """
+
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
+ super().__init__(tokenizer)
+ if not self.model_tokenizer:
+ raise ValueError(
+ "The model tokenizer must be passed to the ReasoningParser "
+ "constructor during construction.")
+
+ def is_reasoning_end(self, input_ids: list[int]) -> bool:
+ # Always return True, since we never treat reasoning specially
+ return True
+
+ def extract_content_ids(self, input_ids: list[int]) -> list[int]:
+ # Identity: return all tokens as content
+ return input_ids
+
+ def extract_reasoning_content_streaming(
+ self,
+ previous_text: str,
+ current_text: str,
+ delta_text: str,
+ previous_token_ids: Sequence[int],
+ current_token_ids: Sequence[int],
+ delta_token_ids: Sequence[int],
+ ) -> Union[DeltaMessage, None]:
+ # Just wrap delta_text as content, ignore reasoning
+ if delta_text:
+ return DeltaMessage(content=delta_text)
+ return None
+
+ def extract_reasoning_content(
+ self, model_output: str, request: ChatCompletionRequest
+ ) -> tuple[Optional[str], Optional[str]]:
+ # No reasoning separation: return None for reasoning_content,
+ # and full model_output as content
+ return None, model_output
diff --git a/vllm/reasoning/mistral_reasoning_parser.py b/vllm/reasoning/mistral_reasoning_parser.py
index 6c707a4079fa..d2d17b2cdc32 100644
--- a/vllm/reasoning/mistral_reasoning_parser.py
+++ b/vllm/reasoning/mistral_reasoning_parser.py
@@ -19,7 +19,7 @@ class MistralReasoningParser(DeepSeekR1ReasoningParser):
text. This parser extracts the reasoning content from the model output.
"""
- def __init__(self, tokenizer: MistralTokenizer):
+ def __init__(self, tokenizer: MistralTokenizer, *args, **kwargs):
if not isinstance(tokenizer, MistralTokenizer):
raise ValueError(
"The tokenizer must be an instance of MistralTokenizer.")
diff --git a/vllm/reasoning/qwen3_reasoning_parser.py b/vllm/reasoning/qwen3_reasoning_parser.py
index 61bafc724c17..a0e9b1b42c6d 100644
--- a/vllm/reasoning/qwen3_reasoning_parser.py
+++ b/vllm/reasoning/qwen3_reasoning_parser.py
@@ -26,7 +26,7 @@ class Qwen3ReasoningParser(ReasoningParser):
output.
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
self.think_start_token = ""
self.think_end_token = ""
diff --git a/vllm/reasoning/step3_reasoning_parser.py b/vllm/reasoning/step3_reasoning_parser.py
index f642ea977c58..3b7eeafca0c8 100644
--- a/vllm/reasoning/step3_reasoning_parser.py
+++ b/vllm/reasoning/step3_reasoning_parser.py
@@ -24,7 +24,7 @@ class Step3ReasoningParser(ReasoningParser):
text. This parser extracts all content before as reasoning content.
"""
- def __init__(self, tokenizer: PreTrainedTokenizerBase):
+ def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs):
super().__init__(tokenizer)
self.think_end_token = ""