[Rust Frontend] Fix Qwen parser auto-detection (#51169)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Bugen Zhao <i@bugenzhao.com> Signed-off-by: Sage Ahrac <sagiahrak@gmail.com>

Sage committed Aug 20, 2026 at 12:39 UTC 727274a75ba7b2668cd2dc04ad61bbb7c5292bf4
4 files changed +57 -15
rust/src/chat/src/parser/reasoning/mod.rs
+2 -1
@@ -85,7 +85,8 @@ impl ReasoningParserFactory {
85 .register_pattern("deepseek-v3", names::DEEPSEEK_V3)
86 .register_pattern("gemma-4", names::GEMMA4)
87 .register_pattern("gemma4", names::GEMMA4)
88 - .register_pattern("qwen", names::QWEN3)
88 + .register_pattern("qwq", names::DEEPSEEK_R1)
89 + .register_pattern("qwen3", names::QWEN3)
90 .register_pattern("glm-5", names::GLM45)
91 .register_pattern("glm-4.7", names::GLM45)
92 .register_pattern("glm-4.6", names::GLM45)
rust/src/chat/src/parser/reasoning/tests.rs
+17
@@ -37,6 +37,23 @@ fn factory_resolves_deepseek_v4_to_qwen3_alias() {
37 );
38 }
39
40 +#[test]
41 +fn factory_distinguishes_qwen_model_families() {
42 + let factory = ReasoningParserFactory::new();
43 + assert_eq!(
44 + factory.resolve_name_for_model("Qwen/QwQ-32B"),
45 + Some(names::DEEPSEEK_R1)
46 + );
47 + assert_eq!(
48 + factory.resolve_name_for_model("Qwen/Qwen3-8B"),
49 + Some(names::QWEN3)
50 + );
51 + assert_eq!(
52 + factory.resolve_name_for_model("Qwen/Qwen2.5-0.5B-Instruct"),
53 + None
54 + );
55 +}
56 +
57 #[test]
58 fn factory_routes_step3p5_models_to_dedicated_parser() {
59 let factory = ReasoningParserFactory::new();
rust/src/chat/src/parser/tool/mod.rs
+3 -2
@@ -94,9 +94,10 @@ impl ToolParserFactory {
94 .register_pattern("mistral-", names::MISTRAL)
95 .register_pattern("mixtral-", names::MISTRAL)
96 .register_pattern("qwen3-coder", names::QWEN3_CODER)
97 - .register_pattern("qwen2.5-coder", names::QWEN3_CODER)
97 .register_pattern("qwen3.5", names::QWEN3_CODER)
99 - .register_pattern("qwen", names::QWEN3_XML)
98 + .register_pattern("qwq", names::HERMES)
99 + .register_pattern("qwen2.5", names::HERMES)
100 + .register_pattern("qwen3", names::QWEN3_XML)
101 .register_pattern("hermes", names::HERMES)
102 .register_pattern("hy3", names::HY_V3)
103 .register_pattern("hy_v3", names::HY_V3)
rust/src/chat/src/parser/tool/tests.rs
+35 -12
@@ -80,22 +80,45 @@ fn factory_creates_registered_parser_for_model() {
80 factory.create_for_model("my-fake-model-v1", &[]).unwrap();
81 }
82
83 +#[test]
84 +fn factory_distinguishes_qwen_tool_formats() {
85 + let factory = ToolParserFactory::new();
86 +
87 + for (model, expected) in [
88 + ("Qwen/Qwen3.5-0.8B", Some(names::QWEN3_CODER)),
89 + ("Qwen/Qwen3-0.6B", Some(names::QWEN3_XML)),
90 + ("Qwen/Qwen3-Coder-30B", Some(names::QWEN3_CODER)),
91 + ("Qwen/QwQ-32B", Some(names::HERMES)),
92 + ("Qwen/Qwen2.5-0.5B-Instruct", Some(names::HERMES)),
93 + ("Qwen/Qwen2-1.5B-Instruct", None),
94 + ] {
95 + assert_eq!(factory.resolve_name_for_model(model), expected, "{model}");
96 + }
97 +}
98 +
99 +#[test]
100 +fn factory_parses_qwen2_5_coder_template_with_hermes() {
101 + let factory = ToolParserFactory::new();
102 + let tool_call = r#"<tool_call>
103 +{"name":"get_weather","arguments":{"location":"Tokyo"}}
104 +</tool_call>"#;
105 +
106 + let mut parser = factory.create_for_model("Qwen/Qwen2.5-Coder-7B-Instruct", &[]).unwrap();
107 + let mut output = ToolParserOutput::default();
108 + parser.parse_into(tool_call, &mut output).unwrap();
109 + output.append(parser.finish().unwrap());
110 + let output = output.coalesce();
111 +
112 + assert!(output.normal_text().is_empty());
113 + assert_eq!(output.calls().len(), 1);
114 + assert_eq!(output.calls()[0].name.as_deref(), Some("get_weather"));
115 + assert_eq!(output.calls()[0].arguments, r#"{"location":"Tokyo"}"#);
116 +}
117 +
118 #[test]
119 fn factory_new_resolves_default_patterns() {
120 let factory = ToolParserFactory::new();
121
87 - assert_eq!(
88 - factory.resolve_name_for_model("Qwen/Qwen3.5-0.8B"),
89 - Some(names::QWEN3_CODER)
90 - );
91 - assert_eq!(
92 - factory.resolve_name_for_model("Qwen/Qwen3-0.6B"),
93 - Some(names::QWEN3_XML)
94 - );
95 - assert_eq!(
96 - factory.resolve_name_for_model("Qwen/Qwen3-Coder-30B"),
97 - Some(names::QWEN3_CODER)
98 - );
122 assert_eq!(
123 factory.resolve_name_for_model("meta-llama-4-maverick"),
124 Some(names::LLAMA4_JSON)