[Frontend] DeepSeek V4 0731 reasoning effort prompts & mappings (#50580)

Bugen Zhao committed Aug 4, 2026 at 23:51 UTC 77434861904a9f01ea4818fe9f0c7b2a5c05686e
5 files changed +205 -51
rust/src/chat/src/renderer/deepseek_v4/encoding.rs
+23 -10
@@ -24,11 +24,16 @@ const THINKING_END_TOKEN: &str = "</think>";
24 const DSML_TOKEN: &str = "|DSML|";
25 const USER_SP_TOKEN: &str = "<|User|>";
26 const ASSISTANT_SP_TOKEN: &str = "<|Assistant|>";
27 -const REASONING_EFFORT_MAX: &str = concat!(
27 +const REASONING_EFFORT_HIGH: &str = concat!(
28 "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n",
29 "You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n",
30 "Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n",
31 );
32 +const REASONING_EFFORT_MAX: &str = concat!(
33 + "Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.\n",
34 + "You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.\n",
35 + "Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.\n\n",
36 +);
37
38 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
39 enum ThinkingMode {
@@ -47,7 +52,7 @@ struct RenderedToolSchema<'a> {
52
53 /// Render one chat request into the final prompt string.
54 pub(super) fn render_request(request: &ChatRequest) -> Result<String> {
50 - let (thinking_mode, max_reasoning_effort) = resolve_thinking_options(request)?;
55 + let (thinking_mode, reasoning_effort_prompt) = resolve_thinking_options(request)?;
56 let request_tools = request_tools(request);
57 let synthetic_tool_system = needs_synthetic_tool_system(request, request_tools);
58 let drop_thinking = request.parse_template_bool("drop_thinking")?.unwrap_or(true)
@@ -55,8 +60,8 @@ pub(super) fn render_request(request: &ChatRequest) -> Result<String> {
60 let last_user_render_index =
61 find_last_user_render_index(request.messages.as_slice(), synthetic_tool_system);
62 let mut out = String::from(BOS_TOKEN);
58 - if thinking_mode == ThinkingMode::Thinking && max_reasoning_effort {
59 - out.push_str(REASONING_EFFORT_MAX);
63 + if thinking_mode == ThinkingMode::Thinking {
64 + out.push_str(reasoning_effort_prompt);
65 }
66
67 let mut request_tools_attached = false;
@@ -124,20 +129,28 @@ pub(super) fn render_request(request: &ChatRequest) -> Result<String> {
129 /// wrapper, the Rust renderer only consumes the typed top-level
130 /// `reasoning_effort`; the generic template-kwargs map is left for HF
131 /// templates.
127 -fn resolve_thinking_options(request: &ChatRequest) -> Result<(ThinkingMode, bool)> {
128 - let mut thinking_mode = match request.enable_thinking()?.unwrap_or(false) {
132 +fn resolve_thinking_options(request: &ChatRequest) -> Result<(ThinkingMode, &'static str)> {
133 + let mut thinking_mode = match request.enable_thinking()?.unwrap_or(true) {
134 true => ThinkingMode::Thinking,
135 false => ThinkingMode::Chat,
136 };
132 - let mut max_reasoning_effort = false;
137 + let mut reasoning_effort_prompt = REASONING_EFFORT_HIGH;
138
139 match request.chat_options.reasoning_effort {
140 Some(ReasoningEffort::None) => thinking_mode = ThinkingMode::Chat,
136 - Some(ReasoningEffort::Max | ReasoningEffort::XHigh) => max_reasoning_effort = true,
137 - Some(_) | None => {}
141 + Some(ReasoningEffort::Max) => {
142 + reasoning_effort_prompt = REASONING_EFFORT_MAX;
143 + }
144 + Some(ReasoningEffort::XHigh | ReasoningEffort::High) => {
145 + reasoning_effort_prompt = REASONING_EFFORT_HIGH;
146 + }
147 + Some(ReasoningEffort::Minimal | ReasoningEffort::Medium | ReasoningEffort::Low) => {
148 + reasoning_effort_prompt = "";
149 + }
150 + None => {}
151 }
152
140 - Ok((thinking_mode, max_reasoning_effort))
153 + Ok((thinking_mode, reasoning_effort_prompt))
154 }
155
156 /// Return request-level tools only when native tool parsing is enabled.
rust/src/chat/src/renderer/deepseek_v4/tests.rs
+100 -4
@@ -22,7 +22,9 @@ fn render_request(request: &ChatRequest) -> String {
22 }
23
24 fn fixture_request(input_name: &str) -> ChatRequest {
25 - fixture_chat_request(&fixture_path(input_name), deepseek_fixture_options())
25 + let mut request = fixture_chat_request(&fixture_path(input_name), deepseek_fixture_options());
26 + request.chat_options.reasoning_effort = Some(ReasoningEffort::Low);
27 + request
28 }
29
30 fn deepseek_fixture_options() -> FixtureRequestOptions {
@@ -75,6 +77,29 @@ fn reasoning_effort_max_adds_prefix_when_thinking_is_enabled() {
77
78 let rendered = render_request(&request);
79
80 + expect![[r#"
81 + <|begin▁of▁sentence|>Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.
82 + You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.
83 + Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.
84 +
85 + <|User|>solve it<|Assistant|><think>"#]]
86 + .assert_eq(&rendered);
87 +}
88 +
89 +#[test]
90 +fn reasoning_effort_high_adds_0731_high_prefix() {
91 + let mut request = ChatRequest {
92 + messages: vec![ChatMessage::user("solve it")],
93 + ..ChatRequest::for_test()
94 + };
95 + request
96 + .chat_options
97 + .template_kwargs
98 + .insert("thinking".to_string(), Value::Bool(true));
99 + request.chat_options.reasoning_effort = Some(ReasoningEffort::High);
100 +
101 + let rendered = render_request(&request);
102 +
103 expect![[r#"
104 <|begin▁of▁sentence|>Reasoning Effort: Absolute maximum with no shortcuts permitted.
105 You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.
@@ -84,6 +109,66 @@ fn reasoning_effort_max_adds_prefix_when_thinking_is_enabled() {
109 .assert_eq(&rendered);
110 }
111
112 +#[test]
113 +fn omitted_thinking_and_effort_default_to_high() {
114 + let request = ChatRequest {
115 + messages: vec![ChatMessage::user("solve it")],
116 + ..ChatRequest::for_test()
117 + };
118 +
119 + let rendered = render_request(&request);
120 +
121 + assert!(rendered.starts_with("<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"));
122 + assert!(rendered.ends_with("<|Assistant|><think>"));
123 +}
124 +
125 +#[test]
126 +fn reasoning_effort_xhigh_maps_to_high() {
127 + let mut request = ChatRequest {
128 + messages: vec![ChatMessage::user("solve it")],
129 + ..ChatRequest::for_test()
130 + };
131 + request.chat_options.reasoning_effort = Some(ReasoningEffort::XHigh);
132 +
133 + let rendered = render_request(&request);
134 +
135 + assert!(rendered.starts_with("<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"));
136 + assert!(rendered.ends_with("<|Assistant|><think>"));
137 +}
138 +
139 +#[test]
140 +fn minimal_and_medium_reasoning_effort_map_to_low() {
141 + for effort in [ReasoningEffort::Minimal, ReasoningEffort::Medium] {
142 + let mut request = ChatRequest {
143 + messages: vec![ChatMessage::user("solve it")],
144 + ..ChatRequest::for_test()
145 + };
146 + request.chat_options.reasoning_effort = Some(effort);
147 +
148 + let rendered = render_request(&request);
149 +
150 + expect!["<|begin▁of▁sentence|><|User|>solve it<|Assistant|><think>"]
151 + .assert_eq(&rendered);
152 + }
153 +}
154 +
155 +#[test]
156 +fn reasoning_effort_low_keeps_the_default_prompt() {
157 + let mut request = ChatRequest {
158 + messages: vec![ChatMessage::user("solve it")],
159 + ..ChatRequest::for_test()
160 + };
161 + request
162 + .chat_options
163 + .template_kwargs
164 + .insert("thinking".to_string(), Value::Bool(true));
165 + request.chat_options.reasoning_effort = Some(ReasoningEffort::Low);
166 +
167 + let rendered = render_request(&request);
168 +
169 + expect!["<|begin▁of▁sentence|><|User|>solve it<|Assistant|><think>"].assert_eq(&rendered);
170 +}
171 +
172 #[test]
173 fn reasoning_effort_none_disables_thinking() {
174 let mut request = ChatRequest {
@@ -119,12 +204,13 @@ fn reasoning_effort_template_kwarg_is_ignored() {
204
205 let rendered = render_request(&request);
206
122 - expect!["<|begin▁of▁sentence|><|User|>solve it<|Assistant|><think>"].assert_eq(&rendered);
207 + assert!(rendered.starts_with("<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"));
208 + assert!(rendered.ends_with("<|Assistant|><think>"));
209 }
210
211 #[test]
212 fn tool_results_are_sorted_by_previous_assistant_tool_call_order() {
127 - let request = ChatRequest {
213 + let mut request = ChatRequest {
214 messages: vec![
215 ChatMessage::assistant_blocks(vec![
216 AssistantContentBlock::ToolCall(AssistantToolCall {
@@ -144,6 +230,11 @@ fn tool_results_are_sorted_by_previous_assistant_tool_call_order() {
230 ..ChatRequest::for_test()
231 };
232
233 + request
234 + .chat_options
235 + .template_kwargs
236 + .insert("thinking".to_string(), Value::Bool(false));
237 +
238 let rendered = render_request(&request);
239
240 expect![[r#"
@@ -186,6 +277,7 @@ fn drop_thinking_false_keeps_prior_assistant_reasoning() {
277 .chat_options
278 .template_kwargs
279 .insert("drop_thinking".to_string(), Value::Bool(false));
280 + request.chat_options.reasoning_effort = Some(ReasoningEffort::Low);
281
282 let rendered = render_request(&request);
283
@@ -197,7 +289,7 @@ fn drop_thinking_false_keeps_prior_assistant_reasoning() {
289
290 #[test]
291 fn continue_final_assistant_omits_final_eos() {
200 - let request = ChatRequest {
292 + let mut request = ChatRequest {
293 messages: vec![
294 ChatMessage::user("write"),
295 ChatMessage::assistant_text("partial answer"),
@@ -208,6 +300,10 @@ fn continue_final_assistant_omits_final_eos() {
300 },
301 ..ChatRequest::for_test()
302 };
303 + request
304 + .chat_options
305 + .template_kwargs
306 + .insert("thinking".to_string(), Value::Bool(false));
307
308 let rendered = render_request(&request);
309
tests/tokenizers_/test_deepseek_v4.py
+47 -20
@@ -76,13 +76,16 @@ def test_deepseek_v4_tokenizer_registered():
76 )
77
78
79 -def test_deepseek_v4_defaults_to_chat_mode():
79 +def test_deepseek_v4_defaults_to_thinking_with_high_effort():
80 prompt = _tokenizer().apply_chat_template(
81 [{"role": "user", "content": "Hello"}],
82 tokenize=False,
83 )
84
85 - assert prompt == ("<|begin▁of▁sentence|><|User|>Hello<|Assistant|></think>")
85 + assert prompt.startswith(
86 + "<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"
87 + )
88 + assert prompt.endswith("<|Assistant|><think>")
89
90
91 @pytest.mark.parametrize("kwargs", [{"thinking": True}, {"enable_thinking": True}])
@@ -93,7 +96,21 @@ def test_deepseek_v4_enables_thinking_with_compatible_kwargs(kwargs):
96 **kwargs,
97 )
98
96 - assert prompt == ("<|begin▁of▁sentence|><|User|>Hello<|Assistant|><think>")
99 + assert prompt.startswith(
100 + "<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"
101 + )
102 + assert prompt.endswith("<|Assistant|><think>")
103 +
104 +
105 +@pytest.mark.parametrize("kwargs", [{"thinking": False}, {"enable_thinking": False}])
106 +def test_deepseek_v4_explicitly_disables_thinking(kwargs):
107 + prompt = _tokenizer().apply_chat_template(
108 + [{"role": "user", "content": "Hello"}],
109 + tokenize=False,
110 + **kwargs,
111 + )
112 +
113 + assert prompt == ("<|begin▁of▁sentence|><|User|>Hello<|Assistant|></think>")
114
115
116 def test_deepseek_v4_uses_v4_tool_prompt_from_request_tools():
@@ -123,7 +140,10 @@ def test_deepseek_v4_uses_v4_tool_prompt_from_request_tools():
140 assert "</|DSML|tool_calls>" in prompt
141 assert "function_calls" not in prompt
142 assert '"name": "get_weather"' in prompt
126 - assert prompt.endswith("<|User|>Weather?<|Assistant|></think>")
143 + assert prompt.startswith(
144 + "<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"
145 + )
146 + assert prompt.endswith("<|User|>Weather?<|Assistant|><think>")
147
148
149 def test_deepseek_v4_renders_parsed_history_tool_arguments():
@@ -183,8 +203,17 @@ def test_deepseek_v4_renders_parsed_history_tool_arguments():
203 assert 'parameter name="arguments"' not in prompt
204
205
186 -@pytest.mark.parametrize("reasoning_effort", ["minimal", "low", "medium", "high"])
187 -def test_deepseek_v4_accepts_openai_reasoning_effort_values(reasoning_effort):
206 +@pytest.mark.parametrize(
207 + ("reasoning_effort", "expected_prefix"),
208 + [
209 + ("low", "<|begin▁of▁sentence|><|User|>Hello"),
210 + ("high", "<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"),
211 + ("max", "<|begin▁of▁sentence|>Reasoning Effort: Beyond maximum"),
212 + ],
213 +)
214 +def test_deepseek_v4_renders_0731_reasoning_effort_prompts(
215 + reasoning_effort, expected_prefix
216 +):
217 prompt = _tokenizer().apply_chat_template(
218 [{"role": "user", "content": "Hello"}],
219 tokenize=False,
@@ -193,7 +222,7 @@ def test_deepseek_v4_accepts_openai_reasoning_effort_values(reasoning_effort):
222 )
223
224 assert prompt.endswith("<|Assistant|><think>")
196 - assert "Reasoning Effort: Absolute maximum" not in prompt
225 + assert prompt.startswith(expected_prefix)
226
227
228 def test_deepseek_v4_none_reasoning_effort_disables_thinking():
@@ -211,11 +240,11 @@ def test_deepseek_v4_none_reasoning_effort_disables_thinking():
240 ("reasoning_effort", "expected_mode", "expected_effort"),
241 [
242 ("none", "chat", None),
214 - ("minimal", "thinking", "high"),
215 - ("low", "thinking", "high"),
216 - ("medium", "thinking", "high"),
243 + ("minimal", "thinking", "low"),
244 + ("low", "thinking", "low"),
245 + ("medium", "thinking", "low"),
246 ("high", "thinking", "high"),
218 - ("xhigh", "thinking", "max"),
247 + ("xhigh", "thinking", "high"),
248 ("max", "thinking", "max"),
249 ("unexpected", "thinking", "high"),
250 ],
@@ -248,7 +277,7 @@ def test_deepseek_v4_maps_compatible_thinking_reasoning_effort_values(
277 assert captured_kwargs[-1]["reasoning_effort"] == expected_effort
278
279
251 -def test_deepseek_v4_preserves_reference_max_reasoning_effort():
280 +def test_deepseek_v4_renders_0731_max_reasoning_effort():
281 prompt = _tokenizer().apply_chat_template(
282 [{"role": "user", "content": "Hello"}],
283 tokenize=False,
@@ -256,12 +285,10 @@ def test_deepseek_v4_preserves_reference_max_reasoning_effort():
285 reasoning_effort="max",
286 )
287
259 - assert prompt.startswith(
260 - "<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum"
261 - )
288 + assert prompt.startswith("<|begin▁of▁sentence|>Reasoning Effort: Beyond maximum")
289
290
264 -def test_deepseek_v4_maps_xhigh_to_reference_max_reasoning_effort():
291 +def test_deepseek_v4_maps_xhigh_to_high_reasoning_effort():
292 prompt = _tokenizer().apply_chat_template(
293 [{"role": "user", "content": "Hello"}],
294 tokenize=False,
@@ -277,10 +304,10 @@ def test_deepseek_v4_maps_xhigh_to_reference_max_reasoning_effort():
304 @pytest.mark.parametrize(
305 ("case_id", "kwargs"),
306 [
280 - (1, {"thinking": True}),
281 - (2, {"thinking": True}),
282 - (3, {"thinking": True}),
283 - (4, {}),
307 + (1, {"thinking": True, "reasoning_effort": "low"}),
308 + (2, {"thinking": True, "reasoning_effort": "low"}),
309 + (3, {"thinking": True, "reasoning_effort": "low"}),
310 + (4, {"thinking": False}),
311 ],
312 )
313 def test_deepseek_v4_matches_reference_golden_fixtures(case_id, kwargs):
vllm/tokenizers/deepseek_v4.py
+10 -6
@@ -29,10 +29,12 @@ def get_deepseek_v4_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer:
29 tools: list[dict[str, Any]] | None = None,
30 **kwargs,
31 ) -> str | list[int]:
32 - thinking = kwargs.get("thinking", False)
33 - enable_thinking = kwargs.get("enable_thinking", False)
34 - thinking = thinking or enable_thinking
35 - thinking_mode = "thinking" if thinking else "chat"
32 + thinking = kwargs.get("thinking")
33 + enable_thinking = kwargs.get("enable_thinking")
34 + thinking_enabled = bool(thinking) or bool(enable_thinking)
35 + if "thinking" not in kwargs and "enable_thinking" not in kwargs:
36 + thinking_enabled = True
37 + thinking_mode = "thinking" if thinking_enabled else "chat"
38
39 conversation = kwargs.get("conversation", messages)
40 messages = conversation.copy()
@@ -42,12 +44,14 @@ def get_deepseek_v4_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer:
44
45 reasoning_effort = kwargs.get("reasoning_effort")
46 if not isinstance(reasoning_effort, str):
45 - reasoning_effort = None
47 + reasoning_effort = "high" if thinking_enabled else None
48 elif reasoning_effort == "none":
49 thinking_mode = "chat"
50 reasoning_effort = None
49 - elif reasoning_effort in ("max", "xhigh"):
51 + elif reasoning_effort == "max":
52 reasoning_effort = "max"
53 + elif reasoning_effort in ("low", "minimal", "medium"):
54 + reasoning_effort = "low"
55 else:
56 reasoning_effort = "high"
57
vllm/tokenizers/deepseek_v4_encoding.py
+25 -11
@@ -65,11 +65,20 @@ tool_output_template: str = (
65 "<tool_result>{content}</tool_result>"
66 )
67
68 -REASONING_EFFORT_MAX = (
69 - "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n"
70 - "You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n"
71 - "Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n"
72 -)
68 +REASONING_EFFORT_PROMPTS: Dict[str, str] = {
69 + "low": "",
70 + "high": (
71 + "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n"
72 + "You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n"
73 + "Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n"
74 + ),
75 + "max": (
76 + "Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.\n"
77 + "You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.\n"
78 + "Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.\n\n"
79 + ),
80 +}
81 +DEFAULT_REASONING_EFFORT = "low"
82
83 TOOLS_TEMPLATE = """## Tools
84
@@ -202,7 +211,8 @@ def render_message(index: int, messages: List[Dict[str, Any]], thinking_mode: st
211 messages: Full list of messages in the conversation.
212 thinking_mode: Either "chat" or "thinking".
213 drop_thinking: Whether to drop reasoning content from earlier turns.
205 - reasoning_effort: Optional reasoning effort level ("max", "high", or None).
214 + reasoning_effort: Reasoning effort level, one of "low", "high", "max".
215 + None is treated as "low".
216
217 Returns:
218 Encoded string for this message.
@@ -227,10 +237,13 @@ def render_message(index: int, messages: List[Dict[str, Any]], thinking_mode: st
237 if tool_calls:
238 tool_calls = tool_calls_from_openai_format(tool_calls)
239
230 - # Reasoning effort prefix (only at index 0 in thinking mode with max effort)
231 - assert reasoning_effort in ['max', None, 'high'], f"Invalid reasoning effort: {reasoning_effort}"
232 - if index == 0 and thinking_mode == "thinking" and reasoning_effort == 'max':
233 - prompt += REASONING_EFFORT_MAX
240 + reasoning_effort = reasoning_effort or DEFAULT_REASONING_EFFORT
241 + assert reasoning_effort in REASONING_EFFORT_PROMPTS, (
242 + f"Invalid reasoning effort: {reasoning_effort}, expected one of "
243 + f"{list(REASONING_EFFORT_PROMPTS)}"
244 + )
245 + if index == 0 and thinking_mode == "thinking":
246 + prompt += REASONING_EFFORT_PROMPTS[reasoning_effort]
247
248 if role == "system":
249 prompt += system_msg_template.format(content=content or "")
@@ -497,7 +510,8 @@ def encode_messages(
510 drop_thinking: If True, drop reasoning from earlier assistant turns
511 (only keep reasoning for messages after the last user message).
512 add_default_bos_token: Whether to prepend BOS token at conversation start.
500 - reasoning_effort: Optional reasoning effort level ("max", "high", or None).
513 + reasoning_effort: Reasoning effort level, one of "low", "high", "max".
514 + Only takes effect in thinking mode. None is treated as "low".
515
516 Returns:
517 The encoded prompt string.