diff --git a/sgl-router/src/tool_parser/mod.rs b/sgl-router/src/tool_parser/mod.rs
index 42d42ea5b..41b8fae2f 100644
--- a/sgl-router/src/tool_parser/mod.rs
+++ b/sgl-router/src/tool_parser/mod.rs
@@ -25,6 +25,6 @@ pub use types::{FunctionCall, PartialToolCall, StreamResult, TokenConfig, ToolCa
// Re-export parsers for convenience
pub use parsers::{
- DeepSeekParser, JsonParser, KimiK2Parser, LlamaParser, MistralParser, PythonicParser,
- QwenParser, Step3Parser,
+ DeepSeekParser, Glm4MoeParser, GptOssParser, JsonParser, KimiK2Parser, LlamaParser,
+ MistralParser, PythonicParser, QwenParser, Step3Parser,
};
diff --git a/sgl-router/src/tool_parser/parsers/glm4_moe_parser.rs b/sgl-router/src/tool_parser/parsers/glm4_moe_parser.rs
new file mode 100644
index 000000000..017de1256
--- /dev/null
+++ b/sgl-router/src/tool_parser/parsers/glm4_moe_parser.rs
@@ -0,0 +1,292 @@
+use async_trait::async_trait;
+use regex::Regex;
+use serde_json::Value;
+
+use crate::tool_parser::{
+ errors::{ToolParserError, ToolParserResult},
+ state::ParseState,
+ traits::ToolParser,
+ types::{FunctionCall, StreamResult, ToolCall},
+};
+
+/// GLM-4 MoE format parser for tool calls
+///
+/// Handles the GLM-4 MoE specific format:
+/// `{name}\n{key}\n{value}\n`
+///
+/// Features:
+/// - XML-style tags for tool calls
+/// - Key-value pairs for arguments
+/// - Support for multiple sequential tool calls
+pub struct Glm4MoeParser {
+ /// Regex for extracting complete tool calls
+ tool_call_extractor: Regex,
+ /// Regex for extracting function details
+ func_detail_extractor: Regex,
+ /// Regex for extracting argument key-value pairs
+ arg_extractor: Regex,
+}
+
+impl Glm4MoeParser {
+ /// Create a new GLM-4 MoE parser
+ pub fn new() -> Self {
+ // Use (?s) flag for DOTALL mode to handle newlines
+ let tool_call_pattern = r"(?s).*?";
+ let tool_call_extractor = Regex::new(tool_call_pattern).expect("Valid regex pattern");
+
+ let func_detail_pattern = r"(?s)([^\n]*)\n(.*)";
+ let func_detail_extractor = Regex::new(func_detail_pattern).expect("Valid regex pattern");
+
+ let arg_pattern = r"(?s)(.*?)\s*(.*?)";
+ let arg_extractor = Regex::new(arg_pattern).expect("Valid regex pattern");
+
+ Self {
+ tool_call_extractor,
+ func_detail_extractor,
+ arg_extractor,
+ }
+ }
+
+ /// Check if text contains GLM-4 MoE tool markers
+ fn has_tool_markers(&self, text: &str) -> bool {
+ text.contains("")
+ }
+
+ /// Parse arguments from key-value pairs
+ fn parse_arguments(&self, args_text: &str) -> ToolParserResult> {
+ let mut arguments = serde_json::Map::new();
+
+ for capture in self.arg_extractor.captures_iter(args_text) {
+ let key = capture.get(1).map_or("", |m| m.as_str()).trim();
+ let value_str = capture.get(2).map_or("", |m| m.as_str()).trim();
+
+ // Try to parse the value as JSON first, fallback to string
+ let value = if let Ok(json_val) = serde_json::from_str::(value_str) {
+ json_val
+ } else {
+ // Try parsing as Python literal (similar to Python's ast.literal_eval)
+ if value_str == "true" || value_str == "True" {
+ Value::Bool(true)
+ } else if value_str == "false" || value_str == "False" {
+ Value::Bool(false)
+ } else if value_str == "null" || value_str == "None" {
+ Value::Null
+ } else if let Ok(num) = value_str.parse::() {
+ Value::Number(num.into())
+ } else if let Ok(num) = value_str.parse::() {
+ if let Some(n) = serde_json::Number::from_f64(num) {
+ Value::Number(n)
+ } else {
+ Value::String(value_str.to_string())
+ }
+ } else {
+ Value::String(value_str.to_string())
+ }
+ };
+
+ arguments.insert(key.to_string(), value);
+ }
+
+ Ok(arguments)
+ }
+
+ /// Parse a single tool call block
+ fn parse_tool_call(&self, block: &str) -> ToolParserResult