From 727274a75ba7b2668cd2dc04ad61bbb7c5292bf4 Mon Sep 17 00:00:00 2001 From: Sage <80211083+sagearc@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:39:05 +0300 Subject: [PATCH] [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 Signed-off-by: Sage Ahrac --- rust/src/chat/src/parser/reasoning/mod.rs | 3 +- rust/src/chat/src/parser/reasoning/tests.rs | 17 ++++++++ rust/src/chat/src/parser/tool/mod.rs | 5 ++- rust/src/chat/src/parser/tool/tests.rs | 47 +++++++++++++++------ 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/rust/src/chat/src/parser/reasoning/mod.rs b/rust/src/chat/src/parser/reasoning/mod.rs index 9a1e37533b..f85c4a930d 100644 --- a/rust/src/chat/src/parser/reasoning/mod.rs +++ b/rust/src/chat/src/parser/reasoning/mod.rs @@ -85,7 +85,8 @@ impl ReasoningParserFactory { .register_pattern("deepseek-v3", names::DEEPSEEK_V3) .register_pattern("gemma-4", names::GEMMA4) .register_pattern("gemma4", names::GEMMA4) - .register_pattern("qwen", names::QWEN3) + .register_pattern("qwq", names::DEEPSEEK_R1) + .register_pattern("qwen3", names::QWEN3) .register_pattern("glm-5", names::GLM45) .register_pattern("glm-4.7", names::GLM45) .register_pattern("glm-4.6", names::GLM45) diff --git a/rust/src/chat/src/parser/reasoning/tests.rs b/rust/src/chat/src/parser/reasoning/tests.rs index 7b7f1fe94a..37a20f8e4f 100644 --- a/rust/src/chat/src/parser/reasoning/tests.rs +++ b/rust/src/chat/src/parser/reasoning/tests.rs @@ -37,6 +37,23 @@ fn factory_resolves_deepseek_v4_to_qwen3_alias() { ); } +#[test] +fn factory_distinguishes_qwen_model_families() { + let factory = ReasoningParserFactory::new(); + assert_eq!( + factory.resolve_name_for_model("Qwen/QwQ-32B"), + Some(names::DEEPSEEK_R1) + ); + assert_eq!( + factory.resolve_name_for_model("Qwen/Qwen3-8B"), + Some(names::QWEN3) + ); + assert_eq!( + factory.resolve_name_for_model("Qwen/Qwen2.5-0.5B-Instruct"), + None + ); +} + #[test] fn factory_routes_step3p5_models_to_dedicated_parser() { let factory = ReasoningParserFactory::new(); diff --git a/rust/src/chat/src/parser/tool/mod.rs b/rust/src/chat/src/parser/tool/mod.rs index e42b45a486..cc8b160d93 100644 --- a/rust/src/chat/src/parser/tool/mod.rs +++ b/rust/src/chat/src/parser/tool/mod.rs @@ -94,9 +94,10 @@ impl ToolParserFactory { .register_pattern("mistral-", names::MISTRAL) .register_pattern("mixtral-", names::MISTRAL) .register_pattern("qwen3-coder", names::QWEN3_CODER) - .register_pattern("qwen2.5-coder", names::QWEN3_CODER) .register_pattern("qwen3.5", names::QWEN3_CODER) - .register_pattern("qwen", names::QWEN3_XML) + .register_pattern("qwq", names::HERMES) + .register_pattern("qwen2.5", names::HERMES) + .register_pattern("qwen3", names::QWEN3_XML) .register_pattern("hermes", names::HERMES) .register_pattern("hy3", names::HY_V3) .register_pattern("hy_v3", names::HY_V3) diff --git a/rust/src/chat/src/parser/tool/tests.rs b/rust/src/chat/src/parser/tool/tests.rs index c89a50b224..d398d84e35 100644 --- a/rust/src/chat/src/parser/tool/tests.rs +++ b/rust/src/chat/src/parser/tool/tests.rs @@ -80,22 +80,45 @@ fn factory_creates_registered_parser_for_model() { factory.create_for_model("my-fake-model-v1", &[]).unwrap(); } +#[test] +fn factory_distinguishes_qwen_tool_formats() { + let factory = ToolParserFactory::new(); + + for (model, expected) in [ + ("Qwen/Qwen3.5-0.8B", Some(names::QWEN3_CODER)), + ("Qwen/Qwen3-0.6B", Some(names::QWEN3_XML)), + ("Qwen/Qwen3-Coder-30B", Some(names::QWEN3_CODER)), + ("Qwen/QwQ-32B", Some(names::HERMES)), + ("Qwen/Qwen2.5-0.5B-Instruct", Some(names::HERMES)), + ("Qwen/Qwen2-1.5B-Instruct", None), + ] { + assert_eq!(factory.resolve_name_for_model(model), expected, "{model}"); + } +} + +#[test] +fn factory_parses_qwen2_5_coder_template_with_hermes() { + let factory = ToolParserFactory::new(); + let tool_call = r#" +{"name":"get_weather","arguments":{"location":"Tokyo"}} +"#; + + let mut parser = factory.create_for_model("Qwen/Qwen2.5-Coder-7B-Instruct", &[]).unwrap(); + let mut output = ToolParserOutput::default(); + parser.parse_into(tool_call, &mut output).unwrap(); + output.append(parser.finish().unwrap()); + let output = output.coalesce(); + + assert!(output.normal_text().is_empty()); + assert_eq!(output.calls().len(), 1); + assert_eq!(output.calls()[0].name.as_deref(), Some("get_weather")); + assert_eq!(output.calls()[0].arguments, r#"{"location":"Tokyo"}"#); +} + #[test] fn factory_new_resolves_default_patterns() { let factory = ToolParserFactory::new(); - assert_eq!( - factory.resolve_name_for_model("Qwen/Qwen3.5-0.8B"), - Some(names::QWEN3_CODER) - ); - assert_eq!( - factory.resolve_name_for_model("Qwen/Qwen3-0.6B"), - Some(names::QWEN3_XML) - ); - assert_eq!( - factory.resolve_name_for_model("Qwen/Qwen3-Coder-30B"), - Some(names::QWEN3_CODER) - ); assert_eq!( factory.resolve_name_for_model("meta-llama-4-maverick"), Some(names::LLAMA4_JSON)