| |
| |
| |
| |
|
|
| use std::collections::HashMap; |
|
|
| |
| pub struct Router { |
| experts: Vec<String>, |
| |
| tag_to_expert: HashMap<String, String>, |
| |
| |
| expert_priority: HashMap<String, i32>, |
| } |
|
|
| |
| |
| |
| |
| |
| |
| const ROUTING_KEYWORDS: &[(&str, &[&str])] = &[ |
| |
| ( |
| "structural", |
| &[ |
| "struct", |
| "impl", |
| "trait", |
| "enum", |
| "generics", |
| "derive", |
| "type", |
| "where", |
| "associated", |
| "implement", |
| "implementation", |
| "constructor", |
| ], |
| ), |
| |
| ( |
| "flow_error", |
| &[ |
| "match", "result", "option", "error", "unwrap", "expect", "panic", "map_err", |
| "and_then", "thread", "spawn", "async", "await", "tokio", "mutex", "lock", "arc", |
| "atomic", |
| ], |
| ), |
| |
| ( |
| "system_io", |
| &[ |
| "io", |
| "file", |
| "read", |
| "write", |
| "hashmap", |
| "hashset", |
| "vec", |
| "iterator", |
| "bufreader", |
| "open", |
| "create", |
| "append", |
| "stdin", |
| "stdout", |
| "serialize", |
| ], |
| ), |
| |
| ( |
| "rust_coding", |
| &[ |
| "rust", "cargo", "rustc", "unsafe", "compile", "borrow", "lifetime", "macro", "crate", |
| "module", "rustfmt", "clippy", |
| ], |
| ), |
| |
| ( |
| "system_ops", |
| &[ |
| "linux", |
| "bash", |
| "shell", |
| "server", |
| "deploy", |
| "docker", |
| "nginx", |
| "ssh", |
| "sudo", |
| "systemd", |
| "cron", |
| "devops", |
| "container", |
| "kubernetes", |
| "ansible", |
| "terraform", |
| ], |
| ), |
| ( |
| "planning", |
| &[ |
| "plan", |
| "roadmap", |
| "architecture", |
| "design", |
| "strategy", |
| "proposal", |
| "timeline", |
| "milestone", |
| "sprint", |
| "backlog", |
| "requirement", |
| "specification", |
| ], |
| ), |
| ]; |
|
|
| #[allow(dead_code)] |
| impl Router { |
| |
| pub fn new(experts: &[String]) -> Self { |
| let expert_priority = build_expert_priority(); |
| Self { |
| experts: experts.to_vec(), |
| tag_to_expert: build_tag_to_expert_map(), |
| expert_priority, |
| } |
| } |
|
|
| |
| |
| |
| |
| pub fn classify_with_tags(&self, query: &str, tags: &[String]) -> String { |
| |
| let mut best_expert: Option<&str> = None; |
| let mut best_priority: i32 = -1; |
|
|
| for tag in tags { |
| let clean = tag.trim_start_matches('#'); |
| if let Some(expert) = self.tag_to_expert.get(clean) { |
| if self.experts.contains(expert) { |
| let priority = self |
| .expert_priority |
| .get(expert.as_str()) |
| .copied() |
| .unwrap_or(0); |
|
|
| tracing::debug!("Router: tag #{clean} β expert {expert} (priority={priority})"); |
|
|
| if priority > best_priority { |
| best_priority = priority; |
| best_expert = Some(expert); |
| } |
| } |
| } |
| } |
|
|
| if let Some(expert) = best_expert { |
| return expert.to_string(); |
| } |
|
|
| |
| self.classify(query) |
| } |
|
|
| |
| pub fn classify(&self, query: &str) -> String { |
| let result = self.classify_keyword(query); |
| |
| if self.experts.contains(&result) { |
| result |
| } else { |
| "general".to_string() |
| } |
| } |
|
|
| |
| fn classify_keyword(&self, query: &str) -> String { |
| let query_lower = query.to_lowercase(); |
|
|
| let mut best_match = "general".to_string(); |
| let mut best_score = 0i32; |
|
|
| for (expert, keywords) in ROUTING_KEYWORDS { |
| let score: i32 = keywords |
| .iter() |
| .map(|kw| { |
| |
| let count = query_lower.matches(kw).count() as i32; |
| |
| let exact_count = query_lower |
| .split_whitespace() |
| .filter(|&w| { |
| w == *kw || w.trim_matches(|c: char| !c.is_alphanumeric()) == *kw |
| }) |
| .count() as i32; |
| count + exact_count * 2 |
| }) |
| .sum(); |
|
|
| if score > best_score { |
| best_score = score; |
| best_match = expert.to_string(); |
| } |
| } |
|
|
| best_match |
| } |
|
|
| |
| pub fn experts(&self) -> &[String] { |
| &self.experts |
| } |
|
|
| |
| pub fn routing_info(&self) -> String { |
| let mut info = String::from("Available experts and routing keywords:\n"); |
| for (expert, keywords) in ROUTING_KEYWORDS { |
| info.push_str(&format!(" - {}: {}\n", expert, keywords.join(", "))); |
| } |
| info.push_str(" - general: default (fallback)\n"); |
| info.push_str("\nHashtag β expert mapping:\n"); |
| for (tag, expert) in &self.tag_to_expert { |
| info.push_str(&format!(" - #{tag} β {expert}\n")); |
| } |
| info |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| fn build_tag_to_expert_map() -> HashMap<String, String> { |
| let mut map = HashMap::new(); |
|
|
| |
| for tag in &["struct", "impl", "trait", "enum"] { |
| map.insert(tag.to_string(), "structural".to_string()); |
| } |
|
|
| |
| for tag in &["match", "result", "option", "error", "concurrency"] { |
| map.insert(tag.to_string(), "flow_error".to_string()); |
| } |
|
|
| |
| for tag in &["io", "file", "collections", "regex"] { |
| map.insert(tag.to_string(), "system_io".to_string()); |
| } |
|
|
| |
| for tag in &["rust", "cargo", "tokio", "borrow", "lifetime"] { |
| map.insert(tag.to_string(), "rust_coding".to_string()); |
| } |
|
|
| map.insert("casual".to_string(), "friendly_chat".to_string()); |
| map.insert("chat".to_string(), "friendly_chat".to_string()); |
|
|
| for tag in &["teaching", "learn", "tutorial"] { |
| map.insert(tag.to_string(), "teaching".to_string()); |
| } |
|
|
| for tag in &["algorithms", "math", "memory"] { |
| map.insert(tag.to_string(), "rust_coding".to_string()); |
| } |
|
|
| for tag in &[ |
| "devops", |
| "linux", |
| "networking", |
| "database", |
| "web", |
| "security", |
| ] { |
| map.insert(tag.to_string(), "general".to_string()); |
| } |
|
|
| map |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| fn build_expert_priority() -> HashMap<String, i32> { |
| let mut map = HashMap::new(); |
|
|
| |
| map.insert("structural".to_string(), 20); |
| map.insert("flow_error".to_string(), 20); |
| map.insert("system_io".to_string(), 20); |
|
|
| |
| map.insert("rust_coding".to_string(), 10); |
| map.insert("teaching".to_string(), 10); |
|
|
| |
| map.insert("general".to_string(), 5); |
|
|
| |
| map.insert("friendly_chat".to_string(), 0); |
|
|
| map |
| } |
|
|