diff --git a/sgl-router/src/tool_parser/mod.rs b/sgl-router/src/tool_parser/mod.rs
index b2f775c8b..bc4c5a020 100644
--- a/sgl-router/src/tool_parser/mod.rs
+++ b/sgl-router/src/tool_parser/mod.rs
@@ -5,7 +5,7 @@ pub mod errors;
pub mod json_parser;
pub mod mistral_parser;
pub mod partial_json;
-
+pub mod qwen_parser;
pub mod registry;
pub mod state;
pub mod traits;
@@ -18,6 +18,7 @@ mod tests;
pub use errors::{ToolParserError, ToolParserResult};
pub use json_parser::JsonParser;
pub use mistral_parser::MistralParser;
+pub use qwen_parser::QwenParser;
pub use registry::ParserRegistry;
pub use state::{ParsePhase, ParseState};
pub use traits::{PartialJsonParser, ToolParser};
diff --git a/sgl-router/src/tool_parser/qwen_parser.rs b/sgl-router/src/tool_parser/qwen_parser.rs
new file mode 100644
index 000000000..00d4c3e29
--- /dev/null
+++ b/sgl-router/src/tool_parser/qwen_parser.rs
@@ -0,0 +1,389 @@
+use async_trait::async_trait;
+use regex::Regex;
+use serde_json::Value;
+
+use crate::tool_parser::{
+ errors::{ToolParserError, ToolParserResult},
+ partial_json::PartialJson,
+ state::ParseState,
+ traits::ToolParser,
+ types::{FunctionCall, StreamResult, ToolCall},
+};
+
+/// Qwen format parser for tool calls
+///
+/// Handles the Qwen 2.5/3 specific format:
+/// `\n{"name": "func", "arguments": {...}}\n`
+///
+/// Features:
+/// - XML-style tags with JSON content
+/// - Support for multiple sequential tool calls
+/// - Newline-aware parsing
+pub struct QwenParser {
+ /// Parser for handling incomplete JSON during streaming
+ partial_json: PartialJson,
+ /// Regex for extracting tool calls
+ extractor: Regex,
+}
+
+impl QwenParser {
+ /// Create a new Qwen parser
+ pub fn new() -> Self {
+ // Use (?s) flag for DOTALL mode to handle newlines
+ let pattern = r"(?s)\n(.*?)\n";
+ let extractor = Regex::new(pattern).expect("Valid regex pattern");
+
+ Self {
+ partial_json: PartialJson::default(),
+ extractor,
+ }
+ }
+
+ /// Extract all tool call blocks from text
+ fn extract_tool_calls<'a>(&self, text: &'a str) -> Vec<&'a str> {
+ self.extractor
+ .captures_iter(text)
+ .filter_map(|cap| cap.get(1).map(|m| m.as_str()))
+ .collect()
+ }
+
+ /// Parse a single JSON object into a ToolCall
+ fn parse_single_object(&self, obj: &Value, index: usize) -> ToolParserResult