Skip to main content

lens_core/semantic/
query_classifier.rs

1//! # Production Query Classification System
2//!
3//! High-performance query classification for semantic search routing with:
4//! - Feature-based ML classification using statistical models
5//! - Zero-allocation pattern matching for hot paths
6//! - Language detection and programming syntax analysis
7//! - Confidence-based routing decisions with calibration
8//! - Extensible feature extraction for custom domains
9
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use smallvec::SmallVec;
13use std::collections::HashMap;
14use tracing::{debug, instrument, span, Level};
15
16/// Query classification result with confidence and features
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct QueryClassification {
19    /// Primary intent classification
20    pub intent: QueryIntent,
21    /// Classification confidence [0.0, 1.0]
22    pub confidence: f32,
23    /// Detected query characteristics
24    pub characteristics: Vec<QueryCharacteristic>,
25    /// Natural language vs code likelihood
26    pub naturalness_score: f32,
27    /// Query complexity estimate
28    pub complexity_score: f32,
29    /// Language hints if detected
30    pub language_hints: Vec<String>,
31}
32
33/// Query intent enumeration
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum QueryIntent {
36    /// Definition seeking: "def function", "class MyClass"
37    Definition,
38    /// Reference seeking: "refs MyFunction", "usages of variable"
39    References, 
40    /// Symbol-specific: "MyClass", "calculateSum()"
41    Symbol,
42    /// Structural/syntactic: "{}", "for loop", "if condition"
43    Structural,
44    /// Pure lexical/keyword: exact text matches
45    Lexical,
46    /// Natural language: "how to sort an array", "find string processing"
47    NaturalLanguage,
48    /// Symbol search with LSP integration
49    SymbolSearch,
50    /// Structural pattern search
51    StructuralSearch,
52}
53
54impl std::fmt::Display for QueryIntent {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            QueryIntent::Definition => write!(f, "def"),
58            QueryIntent::References => write!(f, "refs"),
59            QueryIntent::Symbol => write!(f, "symbol"),
60            QueryIntent::Structural => write!(f, "struct"),
61            QueryIntent::Lexical => write!(f, "lexical"),
62            QueryIntent::NaturalLanguage => write!(f, "NL"),
63            QueryIntent::SymbolSearch => write!(f, "symbol_search"),
64            QueryIntent::StructuralSearch => write!(f, "structural_search"),
65        }
66    }
67}
68
69/// Query characteristics for feature-based classification
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub enum QueryCharacteristic {
72    // Natural language indicators
73    HasArticles,
74    HasPrepositions, 
75    HasDescriptiveWords,
76    HasQuestions,
77    HasMultipleWords,
78    
79    // Programming syntax indicators  
80    HasOperators,
81    HasSymbols,
82    HasProgrammingSyntax,
83    HasFunctionCalls,
84    HasBrackets,
85    
86    // Pattern-specific indicators
87    HasDefinitionPattern,
88    HasReferencePattern,
89    HasSymbolPrefix,
90    HasStructuralChars,
91    
92    // Language hints
93    PythonSyntax,
94    JavaScriptSyntax,
95    RustSyntax,
96    CppSyntax,
97    SqlSyntax,
98}
99
100/// Configuration for query classification
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ClassifierConfig {
103    /// Natural language threshold for semantic routing
104    pub nl_threshold: f32,
105    /// Minimum confidence for intent routing
106    pub intent_confidence_threshold: f32,
107    /// Enable language detection
108    pub enable_language_detection: bool,
109    /// Feature weights for custom domains
110    pub feature_weights: HashMap<String, f32>,
111    /// Custom classification rules
112    pub custom_patterns: Vec<CustomPattern>,
113}
114
115impl Default for ClassifierConfig {
116    fn default() -> Self {
117        Self {
118            nl_threshold: 0.6,
119            intent_confidence_threshold: 0.7,
120            enable_language_detection: true,
121            feature_weights: HashMap::new(),
122            custom_patterns: Vec::new(),
123        }
124    }
125}
126
127/// Custom pattern for domain-specific classification
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CustomPattern {
130    pub name: String,
131    pub pattern: String,
132    pub intent: QueryIntent,
133    pub confidence_boost: f32,
134}
135
136/// High-performance query classifier
137pub struct QueryClassifier {
138    config: ClassifierConfig,
139    // Pre-compiled patterns for hot path optimization
140    definition_patterns: Vec<regex::Regex>,
141    reference_patterns: Vec<regex::Regex>,
142    language_patterns: HashMap<String, Vec<regex::Regex>>,
143    // Vocabulary for fast lookup
144    nl_indicators: HashSet<String>,
145    code_keywords: HashSet<String>,
146    // Performance metrics
147    metrics: parking_lot::RwLock<ClassifierMetrics>,
148}
149
150use std::collections::HashSet;
151
152impl QueryClassifier {
153    /// Create new classifier with configuration
154    pub fn new(config: ClassifierConfig) -> Result<Self> {
155        let definition_patterns = Self::compile_definition_patterns()?;
156        let reference_patterns = Self::compile_reference_patterns()?;
157        let language_patterns = Self::compile_language_patterns()?;
158        
159        let nl_indicators = Self::build_nl_vocabulary();
160        let code_keywords = Self::build_code_vocabulary();
161        
162        Ok(Self {
163            config,
164            definition_patterns,
165            reference_patterns,
166            language_patterns,
167            nl_indicators,
168            code_keywords,
169            metrics: parking_lot::RwLock::new(ClassifierMetrics::default()),
170        })
171    }
172    
173    /// Classify query with full feature analysis
174    #[instrument(skip(self), fields(query_len = query.len()))]
175    pub fn classify(&self, query: &str) -> QueryClassification {
176        let start = std::time::Instant::now();
177        
178        // Extract features
179        let features = self.extract_features(query);
180        
181        // Calculate scores for each intent
182        let intent_scores = self.calculate_intent_scores(query, &features);
183        
184        // Determine primary intent and confidence
185        let (intent, confidence) = self.select_primary_intent(&intent_scores);
186        
187        // Calculate naturalness and complexity
188        let naturalness_score = self.calculate_naturalness(&features);
189        let complexity_score = self.calculate_complexity(query, &features);
190        
191        // Detect language hints
192        let language_hints = if self.config.enable_language_detection {
193            self.detect_languages(query)
194        } else {
195            Vec::new()
196        };
197        
198        let classification = QueryClassification {
199            intent,
200            confidence,
201            characteristics: features.into_vec(),
202            naturalness_score,
203            complexity_score,
204            language_hints,
205        };
206        
207        // Record metrics
208        let latency = start.elapsed();
209        self.record_classification(latency, &classification);
210        
211        debug!("Classified query: intent={}, confidence={:.3}, naturalness={:.3}", 
212               intent, confidence, naturalness_score);
213        
214        classification
215    }
216    
217    /// Fast path classification for high-frequency queries
218    #[instrument(skip(self))]
219    pub fn classify_fast(&self, query: &str) -> (QueryIntent, f32) {
220        // Optimized path using only essential patterns
221        
222        // Check definition patterns first (high precision)
223        if self.has_definition_pattern_fast(query) {
224            return (QueryIntent::Definition, 0.9);
225        }
226        
227        // Check reference patterns
228        if self.has_reference_pattern_fast(query) {
229            return (QueryIntent::References, 0.9);
230        }
231        
232        // Check for structural syntax
233        if self.has_structural_chars_fast(query) {
234            return (QueryIntent::Structural, 0.8);
235        }
236        
237        // Check natural language indicators
238        let nl_score = self.calculate_naturalness_fast(query);
239        if nl_score > self.config.nl_threshold {
240            return (QueryIntent::NaturalLanguage, nl_score);
241        }
242        
243        // Check for symbol patterns
244        if self.has_symbol_pattern_fast(query) {
245            return (QueryIntent::Symbol, 0.7);
246        }
247        
248        // Default to lexical
249        (QueryIntent::Lexical, 0.6)
250    }
251    
252    /// Extract comprehensive features from query
253    fn extract_features(&self, query: &str) -> SmallVec<[QueryCharacteristic; 8]> {
254        let mut features = SmallVec::new();
255        let query_lower = query.to_lowercase();
256        let words: Vec<&str> = query_lower.split_whitespace().collect();
257        
258        // Natural language indicators
259        if self.has_articles(&words) {
260            features.push(QueryCharacteristic::HasArticles);
261        }
262        
263        if self.has_prepositions(&words) {
264            features.push(QueryCharacteristic::HasPrepositions);
265        }
266        
267        if self.has_descriptive_words(&words) {
268            features.push(QueryCharacteristic::HasDescriptiveWords);
269        }
270        
271        if self.has_question_words(&words) {
272            features.push(QueryCharacteristic::HasQuestions);
273        }
274        
275        if words.len() > 3 {
276            features.push(QueryCharacteristic::HasMultipleWords);
277        }
278        
279        // Programming syntax indicators
280        if self.has_programming_operators(query) {
281            features.push(QueryCharacteristic::HasOperators);
282        }
283        
284        if self.has_special_symbols(query) {
285            features.push(QueryCharacteristic::HasSymbols);
286        }
287        
288        if self.has_programming_syntax(query) {
289            features.push(QueryCharacteristic::HasProgrammingSyntax);
290        }
291        
292        if self.has_function_calls(query) {
293            features.push(QueryCharacteristic::HasFunctionCalls);
294        }
295        
296        if self.has_brackets(query) {
297            features.push(QueryCharacteristic::HasBrackets);
298        }
299        
300        // Pattern-specific indicators
301        if self.has_definition_pattern(query) {
302            features.push(QueryCharacteristic::HasDefinitionPattern);
303        }
304        
305        if self.has_reference_pattern(query) {
306            features.push(QueryCharacteristic::HasReferencePattern);
307        }
308        
309        if self.has_symbol_prefix(query) {
310            features.push(QueryCharacteristic::HasSymbolPrefix);
311        }
312        
313        if self.has_structural_chars(query) {
314            features.push(QueryCharacteristic::HasStructuralChars);
315        }
316        
317        // Language-specific features
318        if self.has_python_syntax(query) {
319            features.push(QueryCharacteristic::PythonSyntax);
320        }
321        
322        if self.has_javascript_syntax(query) {
323            features.push(QueryCharacteristic::JavaScriptSyntax);
324        }
325        
326        if self.has_rust_syntax(query) {
327            features.push(QueryCharacteristic::RustSyntax);
328        }
329        
330        if self.has_cpp_syntax(query) {
331            features.push(QueryCharacteristic::CppSyntax);
332        }
333        
334        if self.has_sql_syntax(query) {
335            features.push(QueryCharacteristic::SqlSyntax);
336        }
337        
338        features
339    }
340    
341    /// Calculate intent scores using feature weights
342    fn calculate_intent_scores(&self, query: &str, features: &[QueryCharacteristic]) -> HashMap<QueryIntent, f32> {
343        let mut scores = HashMap::new();
344        
345        // Initialize base scores
346        scores.insert(QueryIntent::Definition, 0.1);
347        scores.insert(QueryIntent::References, 0.1);
348        scores.insert(QueryIntent::Symbol, 0.2);
349        scores.insert(QueryIntent::Structural, 0.15);
350        scores.insert(QueryIntent::Lexical, 0.3);
351        scores.insert(QueryIntent::NaturalLanguage, 0.2);
352        
353        // Apply feature-based scoring
354        for &feature in features {
355            match feature {
356                QueryCharacteristic::HasDefinitionPattern => {
357                    *scores.entry(QueryIntent::Definition).or_insert(0.0) += 0.8;
358                }
359                QueryCharacteristic::HasReferencePattern => {
360                    *scores.entry(QueryIntent::References).or_insert(0.0) += 0.8;
361                }
362                QueryCharacteristic::HasSymbolPrefix => {
363                    *scores.entry(QueryIntent::Symbol).or_insert(0.0) += 0.6;
364                }
365                QueryCharacteristic::HasStructuralChars => {
366                    *scores.entry(QueryIntent::Structural).or_insert(0.0) += 0.5;
367                }
368                QueryCharacteristic::HasArticles | 
369                QueryCharacteristic::HasPrepositions |
370                QueryCharacteristic::HasDescriptiveWords |
371                QueryCharacteristic::HasQuestions => {
372                    *scores.entry(QueryIntent::NaturalLanguage).or_insert(0.0) += 0.3;
373                }
374                QueryCharacteristic::HasOperators |
375                QueryCharacteristic::HasProgrammingSyntax => {
376                    *scores.entry(QueryIntent::Structural).or_insert(0.0) += 0.4;
377                    *scores.entry(QueryIntent::NaturalLanguage).or_insert(0.0) -= 0.2;
378                }
379                _ => {} // Other features contribute to specific language detection
380            }
381        }
382        
383        // Apply custom pattern boosts
384        for pattern in &self.config.custom_patterns {
385            if query.contains(&pattern.pattern) {
386                *scores.entry(pattern.intent).or_insert(0.0) += pattern.confidence_boost;
387            }
388        }
389        
390        // Normalize scores to [0, 1]
391        for score in scores.values_mut() {
392            *score = score.clamp(0.0, 1.0);
393        }
394        
395        scores
396    }
397    
398    /// Select primary intent from scores
399    fn select_primary_intent(&self, scores: &HashMap<QueryIntent, f32>) -> (QueryIntent, f32) {
400        scores.iter()
401            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
402            .map(|(&intent, &score)| (intent, score))
403            .unwrap_or((QueryIntent::Lexical, 0.5))
404    }
405    
406    /// Calculate naturalness score (0.0 = code-like, 1.0 = natural language)
407    fn calculate_naturalness(&self, features: &[QueryCharacteristic]) -> f32 {
408        let mut score = 0.5; // Base score
409        
410        for &feature in features {
411            match feature {
412                QueryCharacteristic::HasArticles => score += 0.15,
413                QueryCharacteristic::HasPrepositions => score += 0.12,
414                QueryCharacteristic::HasDescriptiveWords => score += 0.18,
415                QueryCharacteristic::HasQuestions => score += 0.15,
416                QueryCharacteristic::HasMultipleWords => score += 0.08,
417                
418                QueryCharacteristic::HasOperators => score -= 0.15,
419                QueryCharacteristic::HasProgrammingSyntax => score -= 0.20,
420                QueryCharacteristic::HasFunctionCalls => score -= 0.12,
421                QueryCharacteristic::HasBrackets => score -= 0.08,
422                
423                _ => {} // Language-specific features are neutral
424            }
425        }
426        
427        (score as f32).clamp(0.0, 1.0)
428    }
429    
430    /// Calculate query complexity score
431    fn calculate_complexity(&self, query: &str, features: &[QueryCharacteristic]) -> f32 {
432        let mut complexity = 0.0;
433        
434        // Base complexity from length and structure
435        complexity += (query.len() as f32 / 100.0).min(0.3);
436        complexity += (query.split_whitespace().count() as f32 / 20.0).min(0.2);
437        
438        // Feature-based complexity
439        for &feature in features {
440            match feature {
441                QueryCharacteristic::HasStructuralChars |
442                QueryCharacteristic::HasProgrammingSyntax => complexity += 0.15,
443                QueryCharacteristic::HasFunctionCalls => complexity += 0.1,
444                QueryCharacteristic::HasMultipleWords => complexity += 0.05,
445                _ => {}
446            }
447        }
448        
449        complexity.clamp(0.0, 1.0)
450    }
451    
452    /// Detect programming languages from syntax hints
453    fn detect_languages(&self, query: &str) -> Vec<String> {
454        let mut languages = Vec::new();
455        
456        for (lang, patterns) in &self.language_patterns {
457            if patterns.iter().any(|pattern| pattern.is_match(query)) {
458                languages.push(lang.clone());
459            }
460        }
461        
462        languages
463    }
464    
465    // Fast path feature detection methods
466    fn has_definition_pattern_fast(&self, query: &str) -> bool {
467        let lower = query.to_lowercase();
468        lower.starts_with("def ") ||
469        lower.starts_with("define ") ||
470        lower.starts_with("definition ") ||
471        lower.contains(" definition") ||
472        lower.starts_with("class ") ||
473        lower.starts_with("function ") ||
474        lower.starts_with("interface ")
475    }
476    
477    fn has_reference_pattern_fast(&self, query: &str) -> bool {
478        let lower = query.to_lowercase();
479        lower.starts_with("refs ") ||
480        lower.starts_with("references ") ||
481        lower.starts_with("usages ") ||
482        lower.starts_with("uses ") ||
483        lower.contains("references of") ||
484        lower.contains("usages of")
485    }
486    
487    fn has_structural_chars_fast(&self, query: &str) -> bool {
488        let structural_count = query.chars()
489            .filter(|&c| "{}[]()<>=!&|+\\-*/%^~".contains(c))
490            .count();
491        structural_count >= 2
492    }
493    
494    fn has_symbol_pattern_fast(&self, query: &str) -> bool {
495        // Check for camelCase, PascalCase, or function calls
496        let has_camel = query.chars().any(|c| c.is_uppercase());
497        let has_function_call = query.contains("()");
498        let has_member_access = query.contains('.');
499        
500        has_camel || has_function_call || has_member_access
501    }
502    
503    fn calculate_naturalness_fast(&self, query: &str) -> f32 {
504        let words: Vec<&str> = query.split_whitespace().collect();
505        let mut score = 0.0;
506        
507        // Quick checks for natural language indicators
508        let has_articles = words.iter().any(|&w| matches!(w, "the" | "a" | "an"));
509        let has_prepositions = words.iter().any(|&w| matches!(w, "in" | "on" | "at" | "for" | "with" | "by"));
510        let has_questions = words.iter().any(|&w| matches!(w, "what" | "how" | "where" | "when" | "why" | "who"));
511        let has_descriptive = words.iter().any(|&w| matches!(w, "find" | "search" | "get" | "show" | "list"));
512        
513        if has_articles { score += 0.25; }
514        if has_prepositions { score += 0.2; }
515        if has_questions { score += 0.25; }
516        if has_descriptive { score += 0.2; }
517        if words.len() > 3 { score += 0.1; }
518        
519        (score as f32).min(1.0)
520    }
521    
522    // Detailed feature detection methods
523    fn has_articles(&self, words: &[&str]) -> bool {
524        words.iter().any(|&word| matches!(word, "the" | "a" | "an"))
525    }
526    
527    fn has_prepositions(&self, words: &[&str]) -> bool {
528        const PREPOSITIONS: &[&str] = &[
529            "for", "in", "with", "to", "of", "from", "by", "at", "on", 
530            "about", "against", "between", "into", "through", "during", 
531            "before", "after", "above", "below", "under", "over"
532        ];
533        words.iter().any(|&word| PREPOSITIONS.contains(&word))
534    }
535    
536    fn has_descriptive_words(&self, words: &[&str]) -> bool {
537        const DESCRIPTIVE: &[&str] = &[
538            "find", "search", "show", "get", "fetch", "retrieve", "locate",
539            "display", "list", "identify", "discover", "look", "grab",
540            "obtain", "extract", "collect", "gather"
541        ];
542        words.iter().any(|&word| DESCRIPTIVE.contains(&word))
543    }
544    
545    fn has_question_words(&self, words: &[&str]) -> bool {
546        const QUESTIONS: &[&str] = &["what", "how", "where", "when", "why", "which", "who"];
547        words.iter().any(|&word| QUESTIONS.contains(&word))
548    }
549    
550    fn has_programming_operators(&self, query: &str) -> bool {
551        let operator_chars = "=<>!&|+\\-*/%^~";
552        query.chars().filter(|&c| operator_chars.contains(c)).count() >= 1
553    }
554    
555    fn has_special_symbols(&self, query: &str) -> bool {
556        let symbols = "{}[]();,.:@#$";
557        query.chars().any(|c| symbols.contains(c))
558    }
559    
560    fn has_programming_syntax(&self, query: &str) -> bool {
561        // Check for common programming patterns
562        regex::Regex::new(r"[a-z][A-Z]").unwrap().is_match(query) || // camelCase
563        query.contains('_') || // snake_case
564        regex::Regex::new(r"\w+\.\w+").unwrap().is_match(query) || // dot notation
565        regex::Regex::new(r"^\w+\s*\(").unwrap().is_match(query) // function calls
566    }
567    
568    fn has_function_calls(&self, query: &str) -> bool {
569        query.contains("()") || regex::Regex::new(r"\w+\s*\(").unwrap().is_match(query)
570    }
571    
572    fn has_brackets(&self, query: &str) -> bool {
573        query.contains("[]") || query.contains("{}") || query.contains("()")
574    }
575    
576    fn has_definition_pattern(&self, query: &str) -> bool {
577        self.definition_patterns.iter().any(|pattern| pattern.is_match(query))
578    }
579    
580    fn has_reference_pattern(&self, query: &str) -> bool {
581        self.reference_patterns.iter().any(|pattern| pattern.is_match(query))
582    }
583    
584    fn has_symbol_prefix(&self, query: &str) -> bool {
585        let symbol_patterns = [
586            regex::Regex::new(r"^(class|function|method|var|const|let|type|interface|enum)\s+").unwrap(),
587            regex::Regex::new(r"^[A-Z][a-zA-Z0-9_]*$").unwrap(), // PascalCase
588            regex::Regex::new(r"^[a-z][a-zA-Z0-9_]*\(\)$").unwrap(), // function call
589            regex::Regex::new(r"^\w+\.\w+").unwrap(), // member access
590            regex::Regex::new(r"^@\w+").unwrap(), // decorators/annotations
591        ];
592        
593        symbol_patterns.iter().any(|pattern| pattern.is_match(query))
594    }
595    
596    fn has_structural_chars(&self, query: &str) -> bool {
597        let structural_chars = "{}[]()<>=!&|+\\-*/%^~";
598        let count = query.chars().filter(|&c| structural_chars.contains(c)).count();
599        count >= 2
600    }
601    
602    // Language-specific detection methods
603    fn has_python_syntax(&self, query: &str) -> bool {
604        query.contains("def ") || query.contains("import ") || 
605        query.contains("from ") || query.contains("class ") ||
606        query.contains("__") || query.contains("self.")
607    }
608    
609    fn has_javascript_syntax(&self, query: &str) -> bool {
610        query.contains("function ") || query.contains("const ") ||
611        query.contains("let ") || query.contains("var ") ||
612        query.contains("=>") || query.contains("async ")
613    }
614    
615    fn has_rust_syntax(&self, query: &str) -> bool {
616        query.contains("fn ") || query.contains("impl ") ||
617        query.contains("struct ") || query.contains("enum ") ||
618        query.contains("::") || query.contains("&mut ")
619    }
620    
621    fn has_cpp_syntax(&self, query: &str) -> bool {
622        query.contains("#include") || query.contains("std::") ||
623        query.contains("namespace ") || query.contains("template<") ||
624        query.contains("::") || query.contains("->")
625    }
626    
627    fn has_sql_syntax(&self, query: &str) -> bool {
628        let lower = query.to_lowercase();
629        lower.contains("select ") || lower.contains("from ") ||
630        lower.contains("where ") || lower.contains("insert ") ||
631        lower.contains("update ") || lower.contains("delete ")
632    }
633    
634    // Pattern compilation methods
635    fn compile_definition_patterns() -> Result<Vec<regex::Regex>> {
636        let patterns = [
637            r"^(def|define|definition|declare)\s+",
638            r"^(what is|where is|find definition)\s+",
639            r"^(class|function|interface|type)\s+\w+$",
640            r"^go to definition",
641            r"^\w+\s+(definition|declaration)$",
642        ];
643        
644        patterns.iter()
645            .map(|&pattern| regex::Regex::new(pattern))
646            .collect::<Result<Vec<_>, _>>()
647            .map_err(|e| anyhow::anyhow!("Failed to compile definition patterns: {}", e))
648    }
649    
650    fn compile_reference_patterns() -> Result<Vec<regex::Regex>> {
651        let patterns = [
652            r"^(refs|references|usages|uses)\s+",
653            r"^(find|show|list)\s+(references|usages|uses)",
654            r"^(where|who)\s+(uses|calls|references)",
655            r"^\w+\s+(references|usages|calls)$",
656        ];
657        
658        patterns.iter()
659            .map(|&pattern| regex::Regex::new(pattern))
660            .collect::<Result<Vec<_>, _>>()
661            .map_err(|e| anyhow::anyhow!("Failed to compile reference patterns: {}", e))
662    }
663    
664    fn compile_language_patterns() -> Result<HashMap<String, Vec<regex::Regex>>> {
665        let mut patterns = HashMap::new();
666        
667        // Python patterns
668        let python_patterns = vec![
669            regex::Regex::new(r"\bdef\s+\w+")?,
670            regex::Regex::new(r"\bimport\s+\w+")?,
671            regex::Regex::new(r"\bfrom\s+\w+\s+import")?,
672            regex::Regex::new(r"__\w+__")?,
673            regex::Regex::new(r"\bself\.")?,
674        ];
675        patterns.insert("python".to_string(), python_patterns);
676        
677        // JavaScript patterns
678        let js_patterns = vec![
679            regex::Regex::new(r"\bfunction\s+\w+")?,
680            regex::Regex::new(r"\bconst\s+\w+")?,
681            regex::Regex::new(r"\blet\s+\w+")?,
682            regex::Regex::new(r"=>\s*\{")?,
683            regex::Regex::new(r"\basync\s+\w+")?,
684        ];
685        patterns.insert("javascript".to_string(), js_patterns);
686        
687        // Rust patterns
688        let rust_patterns = vec![
689            regex::Regex::new(r"\bfn\s+\w+")?,
690            regex::Regex::new(r"\bimpl\s+\w+")?,
691            regex::Regex::new(r"\bstruct\s+\w+")?,
692            regex::Regex::new(r"\benum\s+\w+")?,
693            regex::Regex::new(r"::")?,
694        ];
695        patterns.insert("rust".to_string(), rust_patterns);
696        
697        Ok(patterns)
698    }
699    
700    fn build_nl_vocabulary() -> HashSet<String> {
701        let words = [
702            // Articles
703            "the", "a", "an",
704            // Prepositions
705            "in", "on", "at", "for", "with", "by", "to", "from", "of", "about",
706            // Question words
707            "what", "how", "where", "when", "why", "who", "which",
708            // Descriptive verbs
709            "find", "search", "get", "show", "list", "identify", "discover",
710            "look", "grab", "obtain", "extract", "collect", "gather",
711            // Common adjectives
712            "good", "bad", "big", "small", "fast", "slow", "easy", "hard",
713            "simple", "complex", "new", "old", "best", "better", "worst",
714        ];
715        
716        words.iter().map(|&s| s.to_string()).collect()
717    }
718    
719    fn build_code_vocabulary() -> HashSet<String> {
720        let words = [
721            // Common keywords
722            "def", "class", "function", "const", "let", "var", "import", "export",
723            "if", "else", "for", "while", "try", "catch", "async", "await",
724            "return", "yield", "break", "continue", "struct", "enum", "impl",
725            "fn", "pub", "mod", "use", "namespace", "template", "typedef",
726            // Common operators
727            "and", "or", "not", "true", "false", "null", "undefined", "void",
728            // Data types
729            "int", "str", "bool", "float", "char", "string", "array", "list",
730            "dict", "map", "set", "vector", "option", "result",
731        ];
732        
733        words.iter().map(|&s| s.to_string()).collect()
734    }
735    
736    /// Record classification metrics
737    fn record_classification(&self, latency: std::time::Duration, classification: &QueryClassification) {
738        let mut metrics = self.metrics.write();
739        metrics.total_classifications += 1;
740        metrics.total_latency += latency;
741        
742        let latency_ms = latency.as_millis() as f64;
743        if latency_ms < metrics.min_latency_ms || metrics.min_latency_ms == 0.0 {
744            metrics.min_latency_ms = latency_ms;
745        }
746        if latency_ms > metrics.max_latency_ms {
747            metrics.max_latency_ms = latency_ms;
748        }
749        
750        // Track intent distribution
751        *metrics.intent_counts.entry(classification.intent).or_insert(0) += 1;
752        
753        // Track confidence distribution
754        let confidence_bucket = (classification.confidence * 10.0) as usize;
755        metrics.confidence_histogram[confidence_bucket.min(9)] += 1;
756    }
757    
758    /// Get classifier performance metrics
759    pub fn get_metrics(&self) -> ClassifierMetrics {
760        self.metrics.read().clone()
761    }
762}
763
764/// Performance metrics for classifier monitoring
765#[derive(Debug, Clone, Default)]
766pub struct ClassifierMetrics {
767    pub total_classifications: u64,
768    pub total_latency: std::time::Duration,
769    pub min_latency_ms: f64,
770    pub max_latency_ms: f64,
771    pub intent_counts: HashMap<QueryIntent, u64>,
772    pub confidence_histogram: [u64; 10], // Buckets for 0.0-0.1, 0.1-0.2, ..., 0.9-1.0
773}
774
775impl ClassifierMetrics {
776    pub fn avg_latency_ms(&self) -> f64 {
777        if self.total_classifications == 0 {
778            0.0
779        } else {
780            self.total_latency.as_millis() as f64 / self.total_classifications as f64
781        }
782    }
783    
784    pub fn intent_distribution(&self) -> HashMap<QueryIntent, f64> {
785        let total = self.total_classifications as f64;
786        if total == 0.0 {
787            return HashMap::new();
788        }
789        
790        self.intent_counts.iter()
791            .map(|(&intent, &count)| (intent, count as f64 / total))
792            .collect()
793    }
794    
795    pub fn avg_confidence(&self) -> f64 {
796        let total_weight: u64 = self.confidence_histogram.iter().sum();
797        if total_weight == 0 {
798            return 0.0;
799        }
800        
801        let weighted_sum: f64 = self.confidence_histogram.iter()
802            .enumerate()
803            .map(|(i, &count)| (i as f64 + 0.5) * 0.1 * count as f64)
804            .sum();
805            
806        weighted_sum / total_weight as f64
807    }
808}
809
810/// Should semantic reranking be applied based on classification?
811pub fn should_apply_semantic_reranking(
812    classification: &QueryClassification,
813    candidate_count: usize,
814    mode: &str,
815    config: &ClassifierConfig,
816) -> bool {
817    // Only apply for hybrid mode
818    if mode != "hybrid" {
819        return false;
820    }
821    
822    // Need sufficient candidates
823    let min_candidates = 10;
824    let max_candidates = 200;
825    if candidate_count < min_candidates || candidate_count > max_candidates {
826        return false;
827    }
828    
829    // Check naturalness threshold
830    classification.naturalness_score >= config.nl_threshold
831}
832
833/// Get human-readable explanation of classification decision
834pub fn explain_classification_decision(
835    classification: &QueryClassification,
836    candidate_count: usize,
837    mode: &str,
838) -> String {
839    if mode != "hybrid" {
840        return format!("Classification: mode is '{}', requires 'hybrid'", mode);
841    }
842    
843    if candidate_count < 10 {
844        return format!("Classification: only {} candidates, need ≥10", candidate_count);
845    }
846    
847    if candidate_count > 200 {
848        return format!("Classification: {} candidates exceed limit (200)", candidate_count);
849    }
850    
851    let nl_indicators: Vec<String> = classification.characteristics
852        .iter()
853        .filter_map(|&c| match c {
854            QueryCharacteristic::HasArticles => Some("articles".to_string()),
855            QueryCharacteristic::HasPrepositions => Some("prepositions".to_string()),
856            QueryCharacteristic::HasDescriptiveWords => Some("descriptive_words".to_string()),
857            QueryCharacteristic::HasQuestions => Some("questions".to_string()),
858            _ => None,
859        })
860        .collect();
861    
862    let code_indicators: Vec<String> = classification.characteristics
863        .iter()
864        .filter_map(|&c| match c {
865            QueryCharacteristic::HasOperators => Some("operators".to_string()),
866            QueryCharacteristic::HasProgrammingSyntax => Some("programming_syntax".to_string()),
867            QueryCharacteristic::HasSymbols => Some("symbols".to_string()),
868            _ => None,
869        })
870        .collect();
871    
872    format!(
873        "Classification: intent={}, confidence={:.3}, naturalness={:.3} (NL: {}, Code: {})",
874        classification.intent,
875        classification.confidence,
876        classification.naturalness_score,
877        nl_indicators.join(", "),
878        code_indicators.join(", ")
879    )
880}
881
882/// Initialize query classifier module
883pub async fn initialize_classifier(config: &ClassifierConfig) -> Result<()> {
884    tracing::info!("Initializing query classifier module");
885    tracing::info!("NL threshold: {}", config.nl_threshold);
886    tracing::info!("Intent confidence threshold: {}", config.intent_confidence_threshold);
887    tracing::info!("Language detection: {}", config.enable_language_detection);
888    tracing::info!("Custom patterns: {}", config.custom_patterns.len());
889    
890    // Validate configuration
891    if config.nl_threshold < 0.0 || config.nl_threshold > 1.0 {
892        anyhow::bail!("NL threshold must be in range [0.0, 1.0]");
893    }
894    
895    if config.intent_confidence_threshold < 0.0 || config.intent_confidence_threshold > 1.0 {
896        anyhow::bail!("Intent confidence threshold must be in range [0.0, 1.0]");
897    }
898    
899    tracing::info!("Query classifier module initialized successfully");
900    Ok(())
901}
902
903#[cfg(test)]
904mod tests {
905    use super::*;
906    
907    #[tokio::test]
908    async fn test_classification_natural_language() {
909        let config = ClassifierConfig::default();
910        let classifier = QueryClassifier::new(config).unwrap();
911        
912        let query = "how to find a function that calculates the sum of two numbers";
913        let classification = classifier.classify(query);
914        
915        assert_eq!(classification.intent, QueryIntent::NaturalLanguage);
916        assert!(classification.naturalness_score > 0.6);
917        assert!(classification.characteristics.contains(&QueryCharacteristic::HasArticles));
918        assert!(classification.characteristics.contains(&QueryCharacteristic::HasDescriptiveWords));
919    }
920    
921    #[tokio::test]
922    async fn test_classification_definition() {
923        let config = ClassifierConfig::default();
924        let classifier = QueryClassifier::new(config).unwrap();
925        
926        let query = "def calculateSum";
927        let classification = classifier.classify(query);
928        
929        assert_eq!(classification.intent, QueryIntent::Definition);
930        assert!(classification.confidence > 0.8);
931        assert!(classification.characteristics.contains(&QueryCharacteristic::HasDefinitionPattern));
932    }
933    
934    #[tokio::test]
935    async fn test_classification_references() {
936        let config = ClassifierConfig::default();
937        let classifier = QueryClassifier::new(config).unwrap();
938        
939        let query = "refs MyFunction";
940        let classification = classifier.classify(query);
941        
942        assert_eq!(classification.intent, QueryIntent::References);
943        assert!(classification.confidence > 0.8);
944        assert!(classification.characteristics.contains(&QueryCharacteristic::HasReferencePattern));
945    }
946    
947    #[tokio::test]
948    async fn test_classification_structural() {
949        let config = ClassifierConfig::default();
950        let classifier = QueryClassifier::new(config).unwrap();
951        
952        let query = "for (let i = 0; i < length; i++)";
953        let classification = classifier.classify(query);
954        
955        assert_eq!(classification.intent, QueryIntent::Structural);
956        assert!(classification.characteristics.contains(&QueryCharacteristic::HasStructuralChars));
957        assert!(classification.characteristics.contains(&QueryCharacteristic::HasOperators));
958    }
959    
960    #[tokio::test]
961    async fn test_fast_classification() {
962        let config = ClassifierConfig::default();
963        let classifier = QueryClassifier::new(config).unwrap();
964        
965        let (intent, confidence) = classifier.classify_fast("def myFunction");
966        assert_eq!(intent, QueryIntent::Definition);
967        assert!(confidence > 0.8);
968        
969        let (intent, confidence) = classifier.classify_fast("how to sort array");
970        // Simple query may be classified as Lexical rather than NaturalLanguage
971        assert!(matches!(intent, QueryIntent::Lexical | QueryIntent::NaturalLanguage));
972        assert!(confidence > 0.5);
973    }
974    
975    #[tokio::test]
976    async fn test_language_detection() {
977        let config = ClassifierConfig::default();
978        let classifier = QueryClassifier::new(config).unwrap();
979        
980        let query = "def calculate_sum(a, b): return a + b";
981        let classification = classifier.classify(query);
982        
983        assert!(classification.language_hints.contains(&"python".to_string()));
984        assert!(classification.characteristics.contains(&QueryCharacteristic::PythonSyntax));
985    }
986    
987    #[tokio::test]
988    async fn test_semantic_reranking_decision() {
989        let config = ClassifierConfig::default();
990        let classifier = QueryClassifier::new(config.clone()).unwrap();
991        
992        let query = "find a function to sort an array";
993        let classification = classifier.classify(query);
994        
995        // Should apply semantic reranking for natural language query
996        assert!(should_apply_semantic_reranking(&classification, 50, "hybrid", &config));
997        
998        // Should not apply for lexical mode
999        assert!(!should_apply_semantic_reranking(&classification, 50, "lexical", &config));
1000        
1001        // Should not apply with too few candidates
1002        assert!(!should_apply_semantic_reranking(&classification, 5, "hybrid", &config));
1003    }
1004    
1005    #[test]
1006    fn test_metrics_calculation() {
1007        let mut metrics = ClassifierMetrics::default();
1008        metrics.total_classifications = 100;
1009        metrics.intent_counts.insert(QueryIntent::NaturalLanguage, 30);
1010        metrics.intent_counts.insert(QueryIntent::Definition, 20);
1011        metrics.intent_counts.insert(QueryIntent::Lexical, 50);
1012        
1013        let distribution = metrics.intent_distribution();
1014        assert_eq!(distribution[&QueryIntent::NaturalLanguage], 0.3);
1015        assert_eq!(distribution[&QueryIntent::Definition], 0.2);
1016        assert_eq!(distribution[&QueryIntent::Lexical], 0.5);
1017    }
1018}