@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 586201ebdc
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 2, 04:45 UTC

[Rust Frontend] Cover different thinking modes in roundtrip tests (#44320)

Signed-off-by: Bugen Zhao <i@bugenzhao.com>

Bugen Zhao committed Jun 2, 2026 at 22:51 UTC 586201ebdc5c8b180a3e2f90174f152f4f8d65a0
1 file changed +75 -21
rust/src/chat/tests/roundtrip.rs
+75 -21
@@ -20,6 +20,7 @@ use vllm_chat::{
20 use vllm_text::{DecodedTextEvent, Finished, Prompt};
21
22 /// One model/parser configuration used to run the fixed roundtrip fixtures.
23 +#[derive(Clone)]
24 struct RoundtripCase {
25 /// Hugging Face model id resolved through the production backend loader.
26 model_id: &'static str,
@@ -31,11 +32,45 @@ struct RoundtripCase {
32 tool_call_parser: ParserSelection,
33 /// Reasoning parser selection used by the output processor.
34 reasoning_parser: ParserSelection,
35 + /// How this model's chat template handles thinking mode.
36 + thinking_behavior: ThinkingBehavior,
37 /// JSON formatting expected after this model's template has materialized
38 /// tool-call arguments.
39 json_fmt: JsonFmt,
40 }
41
42 +#[derive(Clone, Copy)]
43 +enum ThinkingBehavior {
44 + /// The chat template accepts explicit thinking on/off kwargs, and uses
45 + /// `default` when the request does not specify either kwarg.
46 + Toggleable { default: bool },
47 + /// The chat template always behaves as `value` for this fixture.
48 + Always { value: bool },
49 +}
50 +
51 +impl ThinkingBehavior {
52 + fn default(self) -> bool {
53 + match self {
54 + Self::Toggleable { default } => default,
55 + Self::Always { value } => value,
56 + }
57 + }
58 +
59 + fn fixtures(self) -> Vec<Option<bool>> {
60 + match self {
61 + Self::Toggleable { .. } => vec![
62 + Some(true), // explicitly enable thinking
63 + Some(false), // explicitly disable thinking
64 + None, // use default template behavior
65 + ],
66 + Self::Always { value } => vec![
67 + Some(value), // explicitly request the supported thinking behavior
68 + None, // use default template behavior
69 + ],
70 + }
71 + }
72 +}
73 +
74 impl RoundtripCase {
75 /// Qwen3 XML tool-call format with `qwen3` reasoning tags.
76 fn qwen3() -> Self {
@@ -44,6 +79,7 @@ impl RoundtripCase {
79 assistant_stop_suffix: "<|im_end|>\n",
80 tool_call_parser: ParserSelection::Auto,
81 reasoning_parser: ParserSelection::Auto,
82 + thinking_behavior: ThinkingBehavior::Toggleable { default: true },
83 json_fmt: spaced_json_fmt(),
84 }
85 }
@@ -55,6 +91,7 @@ impl RoundtripCase {
91 assistant_stop_suffix: "<|im_end|>\n",
92 tool_call_parser: ParserSelection::Auto,
93 reasoning_parser: ParserSelection::Auto,
94 + thinking_behavior: ThinkingBehavior::Toggleable { default: true },
95 json_fmt: compact_json_fmt(),
96 }
97 }
@@ -66,6 +103,7 @@ impl RoundtripCase {
103 assistant_stop_suffix: "[e~[\n",
104 tool_call_parser: ParserSelection::Auto,
105 reasoning_parser: ParserSelection::Auto,
106 + thinking_behavior: ThinkingBehavior::Always { value: true },
107 json_fmt: compact_json_fmt(),
108 }
109 }
@@ -77,6 +115,7 @@ impl RoundtripCase {
115 assistant_stop_suffix: "<|end▁of▁sentence|>",
116 tool_call_parser: ParserSelection::Auto,
117 reasoning_parser: ParserSelection::Auto,
118 + thinking_behavior: ThinkingBehavior::Toggleable { default: false },
119 json_fmt: compact_json_fmt(),
120 }
121 }
@@ -88,6 +127,7 @@ impl RoundtripCase {
127 assistant_stop_suffix: "",
128 tool_call_parser: ParserSelection::Auto,
129 reasoning_parser: ParserSelection::Auto,
130 + thinking_behavior: ThinkingBehavior::Toggleable { default: true },
131 json_fmt: compact_json_fmt(),
132 }
133 }
@@ -100,6 +140,7 @@ impl RoundtripCase {
140 assistant_stop_suffix: "<|im_end|>",
141 tool_call_parser: ParserSelection::Auto,
142 reasoning_parser: ParserSelection::Auto,
143 + thinking_behavior: ThinkingBehavior::Toggleable { default: true },
144 json_fmt: spaced_json_fmt(),
145 }
146 }
@@ -135,35 +176,44 @@ roundtrip_tests! {
176
177 /// Run the fixed reasoning+content fixture for one model/parser case.
178 async fn run_roundtrip_reasoning_and_content(case: RoundtripCase) -> Result<()> {
179 + for thinking in case.thinking_behavior.fixtures() {
180 + run_roundtrip_reasoning_and_content_inner(case.clone(), thinking).await?;
181 + }
182 + Ok(())
183 +}
184 +
185 +async fn run_roundtrip_reasoning_and_content_inner(
186 + case: RoundtripCase,
187 + thinking: Option<bool>,
188 +) -> Result<()> {
189 let backends = load_roundtrip_backends(&case).await?;
190 let request = roundtrip_request(
191 "roundtrip-reasoning-content",
192 vec![ChatMessage::text(ChatRole::User, "What is 2 + 2?")],
193 Vec::new(),
194 + thinking,
195 );
196 let expected_reasoning = "Need compute 2 + 2 directly.";
197 let expected_text = "The answer is 4.";
198 + let effective_thinking = thinking.unwrap_or(case.thinking_behavior.default());
199
147 - let result = run_roundtrip(
148 - &case,
149 - &backends,
150 - &request,
151 - AssistantMessage {
152 - content: vec![
153 - AssistantContentBlock::Reasoning {
154 - text: expected_reasoning.to_string(),
155 - },
156 - AssistantContentBlock::Text {
157 - text: expected_text.to_string(),
158 - },
159 - ],
160 - },
161 - )
162 - .await?;
200 + let assistant = {
201 + let mut content = Vec::new();
202 + if effective_thinking {
203 + content.push(AssistantContentBlock::Reasoning {
204 + text: expected_reasoning.to_string(),
205 + });
206 + }
207 + content.push(AssistantContentBlock::Text {
208 + text: expected_text.to_string(),
209 + });
210 + AssistantMessage { content }
211 + };
212 + let result = run_roundtrip(&case, &backends, &request, assistant).await?;
213
214 assert_eq!(
215 result.parsed_message.reasoning().as_deref().map(str::trim),
166 - Some(expected_reasoning)
216 + effective_thinking.then_some(expected_reasoning)
217 );
218 assert_eq!(result.parsed_message.text().trim(), expected_text);
219 assert_eq!(result.parsed_message.tool_calls().count(), 0);
@@ -186,6 +236,7 @@ async fn run_roundtrip_tool_call_mix(case: RoundtripCase) -> Result<()> {
236 "Check Shanghai weather and add 1.00 plus 2.",
237 )],
238 test_tools(),
239 + Some(true), // always enable thinking in this fixture
240 );
241 let expected_reasoning = "Need call the weather and add tools.";
242 let expected_text = "I will call the tools.";
@@ -487,6 +538,7 @@ fn roundtrip_request(
538 request_id: impl Into<String>,
539 messages: Vec<ChatMessage>,
540 tools: Vec<ChatTool>,
541 + thinking: Option<bool>,
542 ) -> ChatRequest {
543 let mut request = ChatRequest {
544 request_id: request_id.into(),
@@ -500,10 +552,12 @@ fn roundtrip_request(
552 ..ChatRequest::for_test()
553 };
554
503 - // Enable thinking for some models so that rendering and parsing the reasoning block is
504 - // exercised in the roundtrip.
505 - for key in ["thinking", "enable_thinking"] {
506 - request.chat_options.template_kwargs.insert(key.to_string(), true.into());
555 + // Explicitly enable or disable thinking so that rendering and parsing the reasoning block is
556 + // exercised or skipped in the roundtrip. If unspecified, use the default template behavior.
557 + if let Some(thinking) = thinking {
558 + for key in ["thinking", "enable_thinking"] {
559 + request.chat_options.template_kwargs.insert(key.to_string(), thinking.into());
560 + }
561 }
562
563 request