diff --git a/sgl-router/Cargo.toml b/sgl-router/Cargo.toml index a9280e983..b05b62568 100644 --- a/sgl-router/Cargo.toml +++ b/sgl-router/Cargo.toml @@ -64,6 +64,7 @@ prost-types = "0.13" deadpool = { version = "0.12", features = ["managed", "rt_tokio_1"] } backoff = { version = "0.4", features = ["tokio"] } strum = { version = "0.26", features = ["derive"] } +once_cell = "1.21.3" [build-dependencies] tonic-build = "0.12" diff --git a/sgl-router/src/tool_parser/parsers/pythonic_parser.rs b/sgl-router/src/tool_parser/parsers/pythonic_parser.rs index e74272345..6e0a1ecff 100644 --- a/sgl-router/src/tool_parser/parsers/pythonic_parser.rs +++ b/sgl-router/src/tool_parser/parsers/pythonic_parser.rs @@ -23,6 +23,8 @@ use crate::tool_parser::{ pub struct PythonicParser { /// Regex to detect tool calls in Pythonic format tool_call_regex: Regex, + /// Regex to parse function calls - cached for reuse + call_regex: Regex, } impl PythonicParser { @@ -33,7 +35,13 @@ impl PythonicParser { let pattern = r"\[[a-zA-Z_]\w*\("; let tool_call_regex = Regex::new(pattern).expect("Valid regex pattern"); - Self { tool_call_regex } + // Compile the function call regex once + let call_regex = Regex::new(r"(?s)^([a-zA-Z_]\w*)\((.*)\)$").expect("Valid regex pattern"); + + Self { + tool_call_regex, + call_regex, + } } /// Extract tool calls using bracket counting (similar to MistralParser) @@ -120,10 +128,8 @@ impl PythonicParser { /// Parse a single function call from Python syntax fn parse_function_call(&self, call_str: &str) -> ToolParserResult> { - // Match function_name(args) - use (?s) to make . match newlines - let call_regex = Regex::new(r"(?s)^([a-zA-Z_]\w*)\((.*)\)$").unwrap(); - - if let Some(captures) = call_regex.captures(call_str.trim()) { + // Use cached regex instead of creating new one + if let Some(captures) = self.call_regex.captures(call_str.trim()) { let function_name = captures.get(1).unwrap().as_str(); let args_str = captures.get(2).unwrap().as_str(); diff --git a/sgl-router/src/tool_parser/registry.rs b/sgl-router/src/tool_parser/registry.rs index 1a740f1a2..f694d680c 100644 --- a/sgl-router/src/tool_parser/registry.rs +++ b/sgl-router/src/tool_parser/registry.rs @@ -3,9 +3,13 @@ use crate::tool_parser::parsers::{ MistralParser, PythonicParser, QwenParser, Step3Parser, }; use crate::tool_parser::traits::ToolParser; +use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; +/// Global singleton registry instance - created once and reused +pub static GLOBAL_REGISTRY: Lazy = Lazy::new(ParserRegistry::new_internal); + /// Registry for tool parsers and model mappings pub struct ParserRegistry { /// Map of parser name to parser instance @@ -17,8 +21,19 @@ pub struct ParserRegistry { } impl ParserRegistry { - /// Create a new parser registry with default mappings - pub fn new() -> Self { + /// Get the global singleton instance + pub fn new() -> &'static Self { + &GLOBAL_REGISTRY + } + + /// Create a new instance for testing (not the singleton) + #[cfg(test)] + pub fn new_for_testing() -> Self { + Self::new_internal() + } + + /// Internal constructor for creating the singleton instance + fn new_internal() -> Self { let mut registry = Self { parsers: HashMap::new(), model_mapping: HashMap::new(), @@ -202,8 +217,8 @@ impl ParserRegistry { } } -impl Default for ParserRegistry { +impl Default for &'static ParserRegistry { fn default() -> Self { - Self::new() + ParserRegistry::new() } } diff --git a/sgl-router/src/tool_parser/tests.rs b/sgl-router/src/tool_parser/tests.rs index 4aec1f172..27a258ad5 100644 --- a/sgl-router/src/tool_parser/tests.rs +++ b/sgl-router/src/tool_parser/tests.rs @@ -74,7 +74,7 @@ fn test_parser_registry() { #[test] fn test_parser_registry_pattern_matching() { - let mut registry = ParserRegistry::new(); + let mut registry = ParserRegistry::new_for_testing(); // Test that model mappings work by checking the list registry.map_model("test-model", "json");