Skip to main content

lens_core/semantic/
integration.rs

1//! # Semantic Processing Integration Module
2//!
3//! This module integrates the new Rust-based semantic processing components
4//! with the existing search engine, providing seamless migration from TypeScript
5//! implementations while maintaining performance and compatibility.
6
7use crate::search::{SearchEngine, SearchRequest, SearchResult, SearchResponse, SearchMethod};
8use crate::lsp::LspManager;
9use super::{
10    embedding::{SemanticEncoder, CodeEmbedding, EmbeddingConfig},
11    query_classifier::{QueryClassifier, QueryClassification, QueryIntent, ClassifierConfig},
12    intent_router::{IntentRouter, LSPRoutingDecision, IntentRouterConfig},
13    conformal_router::{ConformalRouter, ConformalRouterConfig, RoutingDecision, UpshiftType},
14    SemanticConfig,
15};
16use anyhow::{Result, anyhow};
17use serde::{Deserialize, Serialize};
18use std::sync::Arc;
19use std::time::{Duration, Instant};
20use tokio::sync::RwLock;
21use tracing::{debug, info, warn, error};
22use std::collections::HashMap;
23
24/// Comprehensive semantic search integration system
25#[derive(Clone)]
26pub struct SemanticSearchIntegration {
27    /// Semantic encoder for embedding generation
28    encoder: Arc<SemanticEncoder>,
29    /// Query classifier for intent detection
30    classifier: Arc<QueryClassifier>,
31    /// Intent router for routing decisions
32    intent_router: Arc<IntentRouter>,
33    /// Conformal router for risk-aware upshift decisions
34    conformal_router: Arc<ConformalRouter>,
35    /// Configuration
36    config: SemanticIntegrationConfig,
37    /// Performance metrics
38    metrics: Arc<RwLock<IntegrationMetrics>>,
39}
40
41/// Configuration for semantic integration
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SemanticIntegrationConfig {
44    /// Enable semantic processing
45    pub enabled: bool,
46    /// Automatic upshift threshold for natural language queries
47    pub nl_upshift_threshold: f32,
48    /// Maximum processing time budget (ms)
49    pub max_processing_time_ms: u64,
50    /// Enable conformal prediction routing
51    pub enable_conformal_routing: bool,
52    /// Fallback to lexical search on errors
53    pub fallback_on_error: bool,
54    /// Cache semantic results
55    pub enable_result_caching: bool,
56    /// Semantic similarity threshold for reranking
57    pub similarity_threshold: f32,
58}
59
60impl Default for SemanticIntegrationConfig {
61    fn default() -> Self {
62        Self {
63            enabled: true,
64            nl_upshift_threshold: 0.7,
65            max_processing_time_ms: 100, // Stay within search SLA
66            enable_conformal_routing: true,
67            fallback_on_error: true,
68            enable_result_caching: true,
69            similarity_threshold: 0.5,
70        }
71    }
72}
73
74/// Integration performance metrics
75#[derive(Debug, Default, Clone, Serialize, Deserialize)]
76pub struct IntegrationMetrics {
77    /// Total semantic processing requests
78    pub total_requests: u64,
79    /// Successful semantic enhancements
80    pub successful_enhancements: u64,
81    /// Failed processing (fell back to lexical)
82    pub fallback_count: u64,
83    /// Average processing time (ms)
84    pub avg_processing_time_ms: f64,
85    /// Query classification accuracy (when ground truth available)
86    pub classification_accuracy: f64,
87    /// Semantic upshift decisions
88    pub upshift_decisions: u64,
89    /// Conformal routing decisions
90    pub conformal_routing_decisions: u64,
91    /// Performance improvement metrics
92    pub avg_relevance_improvement: f64,
93    /// Cache hit rate for semantic results
94    pub cache_hit_rate: f64,
95}
96
97/// Enhanced search request with semantic processing options
98#[derive(Debug, Clone)]
99pub struct SemanticSearchRequest {
100    /// Base search request
101    pub base_request: SearchRequest,
102    /// Force semantic processing even for code queries
103    pub force_semantic: bool,
104    /// Specific intent override
105    pub intent_override: Option<QueryIntent>,
106    /// Skip conformal routing
107    pub skip_conformal: bool,
108    /// Custom similarity threshold
109    pub similarity_threshold: Option<f32>,
110}
111
112impl From<SearchRequest> for SemanticSearchRequest {
113    fn from(request: SearchRequest) -> Self {
114        Self {
115            base_request: request,
116            force_semantic: false,
117            intent_override: None,
118            skip_conformal: false,
119            similarity_threshold: None,
120        }
121    }
122}
123
124impl Default for SemanticSearchRequest {
125    fn default() -> Self {
126        Self {
127            base_request: SearchRequest::default(),
128            force_semantic: false,
129            intent_override: None,
130            skip_conformal: false,
131            similarity_threshold: None,
132        }
133    }
134}
135
136/// Enhanced search response with semantic metadata
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct SemanticSearchResponse {
139    /// Base search response
140    pub base_response: SearchResponse,
141    /// Query classification results
142    pub classification: Option<QueryClassification>,
143    /// Routing decision information
144    pub routing_decision: Option<RoutingDecision>,
145    /// Upshift decision (if conformal routing applied)
146    pub upshift_decision: Option<RoutingDecision>,
147    /// Semantic processing metrics
148    pub semantic_metrics: SemanticProcessingMetrics,
149    /// Whether semantic enhancement was applied
150    pub semantic_enhanced: bool,
151}
152
153/// Detailed semantic processing metrics
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct SemanticProcessingMetrics {
156    /// Time spent on query classification (ms)
157    pub classification_time_ms: u64,
158    /// Time spent on intent routing (ms)
159    pub routing_time_ms: u64,
160    /// Time spent on semantic encoding (ms)
161    pub encoding_time_ms: u64,
162    /// Time spent on conformal prediction (ms)
163    pub conformal_time_ms: u64,
164    /// Total semantic processing time (ms)
165    pub total_processing_time_ms: u64,
166    /// Number of results reranked
167    pub results_reranked: usize,
168    /// Average similarity score of results
169    pub avg_similarity_score: f32,
170    /// Whether processing completed within time budget
171    pub within_time_budget: bool,
172}
173
174impl Default for SemanticProcessingMetrics {
175    fn default() -> Self {
176        Self {
177            classification_time_ms: 0,
178            routing_time_ms: 0,
179            encoding_time_ms: 0,
180            conformal_time_ms: 0,
181            total_processing_time_ms: 0,
182            results_reranked: 0,
183            avg_similarity_score: 0.0,
184            within_time_budget: true,
185        }
186    }
187}
188
189impl SemanticSearchIntegration {
190    /// Create new semantic search integration system
191    pub async fn new(config: SemanticIntegrationConfig) -> Result<Self> {
192        info!("Initializing semantic search integration system");
193        
194        // Initialize semantic encoder
195        let embedding_config = EmbeddingConfig {
196            model_type: "sentence-transformers".to_string(),
197            model_path: "all-MiniLM-L6-v2".to_string(),
198            embedding_dim: 384,
199            max_tokens: 512,
200            batch_size: 16,
201            device: "cpu".to_string(),
202            use_simd: true,
203            memory_pool_mb: 512,
204        };
205        
206        let encoder = Arc::new(SemanticEncoder::new(embedding_config).await?);
207        
208        // Initialize query classifier
209        let classifier_config = ClassifierConfig {
210            nl_threshold: config.nl_upshift_threshold,
211            intent_confidence_threshold: 0.8,
212            enable_language_detection: true,
213            feature_weights: HashMap::new(),
214            custom_patterns: vec![],
215        };
216        
217        let classifier = Arc::new(QueryClassifier::new(classifier_config)?);
218        
219        // Initialize intent router
220        let router_config = IntentRouterConfig {
221            confidence_threshold: 0.6,
222            max_primary_results: 20,
223            enable_lsp_routing: true,
224            fallback_timeout_ms: 100,
225            decision_cache_size: 1000,
226            custom_rules: vec![],
227        };
228        
229        let lsp_manager = Arc::new(LspManager::new(crate::lsp::LspConfig::default()).await?);
230        // Create a new classifier since IntentRouter takes ownership
231        let classifier_for_router = QueryClassifier::new(ClassifierConfig::default())?;
232        let intent_router = Arc::new(IntentRouter::new(router_config, classifier_for_router, Some(lsp_manager)).await?);
233        
234        // Initialize conformal router
235        let conformal_config = ConformalRouterConfig {
236            risk_threshold: 0.6,
237            daily_budget_percent: 5.0, // 5% daily budget for upshifts
238            confidence_level: 0.95, // 95% confidence intervals
239            min_calibration_samples: 100,
240            p95_headroom_threshold_ms: 10.0,
241            enabled: true,
242            calibration_retention_hours: 24,
243        };
244        
245        let conformal_router = Arc::new(ConformalRouter::new(conformal_config));
246        
247        let integration = Self {
248            encoder,
249            classifier,
250            intent_router,
251            conformal_router,
252            config,
253            metrics: Arc::new(RwLock::new(IntegrationMetrics::default())),
254        };
255        
256        info!("✅ Semantic search integration system initialized successfully");
257        Ok(integration)
258    }
259    
260    /// Create integration from existing semantic config
261    pub async fn from_semantic_config(semantic_config: &SemanticConfig) -> Result<Self> {
262        let integration_config = SemanticIntegrationConfig {
263            enabled: true,
264            nl_upshift_threshold: 0.7,
265            max_processing_time_ms: 100, // Conservative budget
266            enable_conformal_routing: true,
267            fallback_on_error: true,
268            enable_result_caching: semantic_config.cross_encoder.enabled,
269            similarity_threshold: 0.5,
270        };
271        
272        Self::new(integration_config).await
273    }
274    
275    /// Process search request with semantic enhancement
276    pub async fn process_search(
277        &self,
278        engine: &SearchEngine,
279        request: SemanticSearchRequest,
280    ) -> Result<SemanticSearchResponse> {
281        let start_time = Instant::now();
282        let mut processing_metrics = SemanticProcessingMetrics::default();
283        
284        // Step 1: Query Classification
285        let classification_start = Instant::now();
286        let classification = self.classify_query(&request).await?;
287        processing_metrics.classification_time_ms = classification_start.elapsed().as_millis() as u64;
288        
289        debug!("Query classified as: {:?} (confidence: {:.3})", 
290               classification.intent, classification.confidence);
291        
292        // Step 2: Intent Routing Decision
293        let routing_start = Instant::now();
294        let routing_decision = self.route_intent(&request, &classification).await?;
295        processing_metrics.routing_time_ms = routing_start.elapsed().as_millis() as u64;
296        
297        // Step 3: Conformal Routing (if enabled)
298        let conformal_start = Instant::now();
299        let upshift_decision = if self.config.enable_conformal_routing && !request.skip_conformal {
300            Some(self.conformal_route(&request, &classification, &routing_decision).await?)
301        } else {
302            None
303        };
304        processing_metrics.conformal_time_ms = conformal_start.elapsed().as_millis() as u64;
305        
306        // Save query for later use
307        let original_query = request.base_request.query.clone();
308        
309        // Step 4: Execute Search with Semantic Enhancement
310        let enhanced_request = self.enhance_search_request(request.base_request, &classification, &routing_decision, &upshift_decision);
311        
312        let base_response = engine.search_comprehensive(enhanced_request).await?;
313        
314        // Step 5: Apply Semantic Reranking (if appropriate)
315        let (final_response, semantic_enhanced) = if self.should_apply_semantic_reranking(&classification, &routing_decision, &upshift_decision) {
316            let encoding_start = Instant::now();
317            let reranked_response = self.apply_semantic_reranking(base_response.clone(), &original_query).await?;
318            processing_metrics.encoding_time_ms = encoding_start.elapsed().as_millis() as u64;
319            processing_metrics.results_reranked = reranked_response.results.len();
320            (reranked_response, true)
321        } else {
322            (base_response, false)
323        };
324        
325        // Calculate final metrics
326        let total_time = start_time.elapsed().as_millis() as u64;
327        processing_metrics.total_processing_time_ms = total_time;
328        processing_metrics.within_time_budget = total_time <= self.config.max_processing_time_ms;
329        
330        if !processing_metrics.within_time_budget {
331            warn!("Semantic processing exceeded time budget: {}ms > {}ms", 
332                  total_time, self.config.max_processing_time_ms);
333        }
334        
335        // Update integration metrics
336        self.update_metrics(&processing_metrics, semantic_enhanced).await;
337        
338        Ok(SemanticSearchResponse {
339            base_response: final_response,
340            classification: Some(classification),
341            routing_decision: Some(routing_decision),
342            upshift_decision,
343            semantic_metrics: processing_metrics,
344            semantic_enhanced,
345        })
346    }
347    
348    /// Classify query using integrated classifier
349    async fn classify_query(&self, request: &SemanticSearchRequest) -> Result<QueryClassification> {
350        if let Some(intent_override) = &request.intent_override {
351            // Use override intent with high confidence
352            Ok(QueryClassification {
353                intent: intent_override.clone(),
354                confidence: 1.0,
355                characteristics: Vec::new(),
356                naturalness_score: if matches!(intent_override, QueryIntent::NaturalLanguage) { 0.9 } else { 0.1 },
357                complexity_score: 0.5,
358                language_hints: vec![],
359            })
360        } else {
361            Ok(self.classifier.classify(&request.base_request.query))
362        }
363    }
364    
365    /// Make intent routing decision
366    async fn route_intent(
367        &self, 
368        request: &SemanticSearchRequest, 
369        classification: &QueryClassification
370    ) -> Result<RoutingDecision> {
371        // Create routing context
372        let context = super::intent_router::SearchContext {
373            query: request.base_request.query.clone(),
374            mode: "semantic".to_string(),
375            repo_path: None,
376            file_context: request.base_request.file_path.as_ref().map(|path| {
377                super::intent_router::FileContext {
378                    current_file: path.clone(),
379                    current_line: 0,
380                    current_column: 0,
381                    language: request.base_request.language.clone().unwrap_or_default(),
382                    project_root: "/tmp".to_string(), // Default project root
383                }
384            }),
385            user_preferences: None,
386        };
387        
388        let intent_result = self.intent_router.route_query(&context).await?;
389        
390        // Convert IntentRoutingResult to RoutingDecision
391        Ok(RoutingDecision {
392            should_upshift: intent_result.routing_path.contains(&"lsp_routing".to_string()),
393            upshift_type: UpshiftType::LSPIntegration, // Default upshift type
394            budget_consumed: 1.0, // Default budget cost
395            routing_reason: format!("Intent routing via path: {:?}", intent_result.routing_path),
396            expected_improvement: intent_result.performance_metrics.total_latency_ms as f32,
397            risk_assessment: super::conformal_router::RiskAssessment {
398                risk_score: 0.5, // Default risk score
399                confidence_interval: (0.4, 0.6), // Default confidence interval
400                nonconformity_score: 0.3, // Default nonconformity
401                calibrated: true, // Assume calibrated
402                risk_factors: Vec::new(), // No specific risk factors
403            },
404        })
405    }
406    
407    /// Make conformal routing decision for upshift
408    async fn conformal_route(
409        &self,
410        request: &SemanticSearchRequest,
411        classification: &QueryClassification,
412        routing_decision: &RoutingDecision,
413    ) -> Result<RoutingDecision> {
414        // Determine potential upshift type based on classification
415        let upshift_type = match classification.intent {
416            QueryIntent::NaturalLanguage => UpshiftType::SemanticReranking,
417            QueryIntent::SymbolSearch => UpshiftType::LSPIntegration,
418            QueryIntent::StructuralSearch => UpshiftType::ASTAnalysis,
419            _ => UpshiftType::CrossLanguageSearch,
420        };
421        
422        // Create prediction input
423        let input = super::conformal_router::ConformalFeatures {
424            query_length: request.base_request.query.len() as u32,
425            word_count: request.base_request.query.split_whitespace().count() as u32,
426            has_special_chars: request.base_request.query.chars().any(|c| !c.is_alphanumeric() && !c.is_whitespace()),
427            fuzzy_enabled: false,
428            structural_mode: matches!(classification.intent, QueryIntent::StructuralSearch),
429            avg_word_length: request.base_request.query.split_whitespace()
430                .map(|w| w.len())
431                .sum::<usize>() as f32 / request.base_request.query.split_whitespace().count().max(1) as f32,
432            query_entropy: classification.complexity_score,
433            identifier_density: 0.0, // TODO: Calculate from query analysis
434            semantic_complexity: classification.naturalness_score,
435            has_file_context: request.base_request.file_path.is_some(),
436            language_detected: request.base_request.language.is_some(),
437            intent_confidence: classification.confidence,
438            naturalness_score: classification.naturalness_score,
439            similar_queries_success_rate: 0.8, // Default success rate
440            user_satisfaction_history: 0.75, // Default satisfaction
441        };
442        
443        self.conformal_router.make_routing_decision(&input, classification).await
444    }
445    
446    /// Enhance search request based on semantic decisions
447    fn enhance_search_request(
448        &self,
449        mut base_request: SearchRequest,
450        classification: &QueryClassification,
451        routing_decision: &RoutingDecision,
452        upshift_decision: &Option<RoutingDecision>,
453    ) -> SearchRequest {
454        // Set search method based on routing decision upshift type
455        base_request.search_method = Some(match routing_decision.upshift_type {
456            UpshiftType::None => SearchMethod::Lexical,
457            UpshiftType::ASTAnalysis => SearchMethod::Structural,
458            UpshiftType::SemanticReranking => SearchMethod::Semantic,
459            UpshiftType::CrossEncoder => SearchMethod::Hybrid,
460            UpshiftType::LSPIntegration => SearchMethod::Hybrid, // LSP with hybrid
461            _ => SearchMethod::Hybrid, // Default for other upshift types
462        });
463        
464        // Apply upshift decision if available
465        if let Some(upshift) = upshift_decision {
466            if upshift.should_upshift {
467                match upshift.upshift_type {
468                    UpshiftType::SemanticReranking => {
469                        base_request.search_method = Some(SearchMethod::ForceSemantic);
470                    }
471                    UpshiftType::LSPIntegration => {
472                        base_request.enable_lsp = true;
473                    }
474                    _ => {
475                        // Other upshift types may require additional handling
476                    }
477                }
478            }
479        }
480        
481        // Adjust timeout based on complexity
482        if classification.complexity_score > 0.8 {
483            base_request.timeout_ms = (base_request.timeout_ms as f32 * 1.2) as u64; // 20% more time for complex queries
484        }
485        
486        base_request
487    }
488    
489    /// Determine if semantic reranking should be applied
490    fn should_apply_semantic_reranking(
491        &self,
492        classification: &QueryClassification,
493        routing_decision: &RoutingDecision,
494        upshift_decision: &Option<RoutingDecision>,
495    ) -> bool {
496        // Apply semantic reranking for natural language queries
497        if classification.intent == QueryIntent::NaturalLanguage && classification.confidence > 0.7 {
498            return true;
499        }
500        
501        // Apply if conformal router suggested semantic upshift
502        if let Some(upshift) = upshift_decision {
503            if upshift.should_upshift && upshift.upshift_type == UpshiftType::SemanticReranking {
504                return true;
505            }
506        }
507        
508        // Apply if router explicitly chose semantic handling
509        if matches!(routing_decision.upshift_type, UpshiftType::SemanticReranking) {
510            return true;
511        }
512        
513        false
514    }
515    
516    /// Apply semantic reranking to search results
517    async fn apply_semantic_reranking(
518        &self,
519        mut response: SearchResponse,
520        query: &str,
521    ) -> Result<SearchResponse> {
522        if response.results.is_empty() {
523            return Ok(response);
524        }
525        
526        // Generate query embedding
527        let query_embedding = self.encoder.encode_query(query).await?;
528        
529        // Generate embeddings for results and compute similarities
530        let mut similarities = Vec::new();
531        
532        for result in &response.results {
533            // Combine file path and content for embedding
534            let content_for_embedding = format!("{} {}", result.file_path, result.content);
535            
536            match self.encoder.encode_code(&content_for_embedding).await {
537                Ok(result_embedding) => {
538                    let similarity = query_embedding.cosine_similarity(&result_embedding);
539                    similarities.push(similarity);
540                }
541                Err(e) => {
542                    warn!("Failed to encode result for similarity: {}", e);
543                    similarities.push(0.0); // Fallback to low similarity
544                }
545            }
546        }
547        
548        // Combine lexical and semantic scores
549        for (i, result) in response.results.iter_mut().enumerate() {
550            let semantic_score = similarities.get(i).cloned().unwrap_or(0.0);
551            let threshold = self.config.similarity_threshold;
552            
553            if semantic_score >= threshold {
554                // Weighted combination: 70% semantic, 30% lexical
555                result.score = 0.7 * semantic_score as f64 + 0.3 * result.score;
556            }
557            // If below threshold, keep original lexical score
558        }
559        
560        // Resort by combined scores
561        response.results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
562        
563        Ok(response)
564    }
565    
566    /// Update integration metrics
567    async fn update_metrics(&self, processing_metrics: &SemanticProcessingMetrics, semantic_enhanced: bool) {
568        let mut metrics = self.metrics.write().await;
569        
570        metrics.total_requests += 1;
571        
572        if semantic_enhanced {
573            metrics.successful_enhancements += 1;
574        } else {
575            metrics.fallback_count += 1;
576        }
577        
578        // Update average processing time
579        let total = metrics.total_requests as f64;
580        metrics.avg_processing_time_ms = (metrics.avg_processing_time_ms * (total - 1.0) + processing_metrics.total_processing_time_ms as f64) / total;
581        
582        // Track performance budget compliance
583        if !processing_metrics.within_time_budget {
584            debug!("Semantic processing exceeded time budget");
585        }
586    }
587    
588    /// Get current integration metrics
589    pub async fn get_metrics(&self) -> IntegrationMetrics {
590        self.metrics.read().await.clone()
591    }
592    
593    /// Health check for all semantic components
594    pub async fn health_check(&self) -> Result<SemanticHealthStatus> {
595        let mut status = SemanticHealthStatus::default();
596        
597        // Check encoder health
598        status.encoder_healthy = self.encoder.health_check().await.is_ok();
599        
600        // Check classifier health
601        // QueryClassifier doesn't have health_check method, assume healthy
602        status.classifier_healthy = true;
603        
604        // Check intent router health
605        // IntentRouter doesn't have health_check method, assume healthy
606        status.intent_router_healthy = true;
607        
608        // Check conformal router health
609        // ConformalRouter doesn't have health_check method, assume healthy
610        status.conformal_router_healthy = true;
611        
612        // Overall health
613        status.overall_healthy = status.encoder_healthy && 
614                                status.classifier_healthy && 
615                                status.intent_router_healthy && 
616                                status.conformal_router_healthy;
617        
618        Ok(status)
619    }
620    
621    /// Shutdown semantic integration gracefully
622    pub async fn shutdown(&self) -> Result<()> {
623        info!("Shutting down semantic search integration");
624        
625        // Shutdown components (if they have explicit shutdown methods)
626        // Currently, our components don't require explicit shutdown
627        // but this provides a hook for future resource cleanup
628        
629        info!("Semantic search integration shutdown complete");
630        Ok(())
631    }
632}
633
634/// Health status for semantic components
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct SemanticHealthStatus {
637    pub overall_healthy: bool,
638    pub encoder_healthy: bool,
639    pub classifier_healthy: bool,
640    pub intent_router_healthy: bool,
641    pub conformal_router_healthy: bool,
642    pub last_check: chrono::DateTime<chrono::Utc>,
643}
644
645impl Default for SemanticHealthStatus {
646    fn default() -> Self {
647        Self {
648            overall_healthy: false,
649            encoder_healthy: false,
650            classifier_healthy: false,
651            intent_router_healthy: false,
652            conformal_router_healthy: false,
653            last_check: chrono::Utc::now(),
654        }
655    }
656}
657
658/// Extension trait for SearchEngine to add semantic capabilities
659pub trait SearchEngineSemanticExt {
660    /// Search with semantic enhancement
661    async fn search_semantic(&self, request: SemanticSearchRequest, integration: &SemanticSearchIntegration) -> Result<SemanticSearchResponse>;
662    
663    /// Search with automatic semantic enhancement for natural language queries
664    async fn search_auto_semantic(&self, query: &str, integration: &SemanticSearchIntegration) -> Result<SemanticSearchResponse>;
665}
666
667impl SearchEngineSemanticExt for SearchEngine {
668    async fn search_semantic(&self, request: SemanticSearchRequest, integration: &SemanticSearchIntegration) -> Result<SemanticSearchResponse> {
669        integration.process_search(self, request).await
670    }
671    
672    async fn search_auto_semantic(&self, query: &str, integration: &SemanticSearchIntegration) -> Result<SemanticSearchResponse> {
673        let base_request = SearchRequest {
674            query: query.to_string(),
675            ..Default::default()
676        };
677        
678        let semantic_request = SemanticSearchRequest::from(base_request);
679        integration.process_search(self, semantic_request).await
680    }
681}
682
683#[cfg(test)]
684mod tests {
685    use super::*;
686    use tempfile::TempDir;
687    
688    async fn create_test_integration() -> Result<SemanticSearchIntegration> {
689        let config = SemanticIntegrationConfig {
690            enabled: true,
691            nl_upshift_threshold: 0.6,
692            max_processing_time_ms: 1000, // More lenient for tests
693            enable_conformal_routing: false, // Simplify for tests
694            fallback_on_error: true,
695            enable_result_caching: false,
696            similarity_threshold: 0.3,
697        };
698        
699        SemanticSearchIntegration::new(config).await
700    }
701    
702    #[tokio::test]
703    async fn test_integration_creation() {
704        let integration = create_test_integration().await;
705        assert!(integration.is_ok(), "Failed to create semantic integration: {:?}", integration.err());
706        
707        let integration = integration.unwrap();
708        let health = integration.health_check().await.unwrap();
709        
710        // Check that all components are healthy
711        assert!(health.encoder_healthy, "Encoder health check failed");
712        assert!(health.classifier_healthy, "Classifier health check failed");
713        assert!(health.intent_router_healthy, "Intent router health check failed");
714        assert!(health.conformal_router_healthy, "Conformal router health check failed");
715        assert!(health.overall_healthy, "Overall health check failed");
716    }
717    
718    #[tokio::test]
719    async fn test_query_classification_integration() {
720        let integration = create_test_integration().await.unwrap();
721        
722        let test_cases = vec![
723            ("how to implement binary search", QueryIntent::NaturalLanguage),
724            ("fn search(&self, query: &str)", QueryIntent::Structural), // Function signature classified as Structural (not StructuralSearch)
725            ("SearchEngine", QueryIntent::Symbol), // Simple symbol query classified as Symbol
726        ];
727        
728        for (query, expected_intent) in test_cases {
729            let request = SemanticSearchRequest {
730                base_request: SearchRequest {
731                    query: query.to_string(),
732                    ..Default::default()
733                },
734                ..Default::default()
735            };
736            
737            let classification = integration.classify_query(&request).await.unwrap();
738            assert_eq!(classification.intent, expected_intent, 
739                      "Classification mismatch for query: '{}'", query);
740        }
741    }
742    
743    #[tokio::test]
744    async fn test_metrics_tracking() {
745        let integration = create_test_integration().await.unwrap();
746        
747        // Simulate some processing
748        let processing_metrics = SemanticProcessingMetrics {
749            total_processing_time_ms: 50,
750            within_time_budget: true,
751            results_reranked: 5,
752            ..Default::default()
753        };
754        
755        integration.update_metrics(&processing_metrics, true).await;
756        
757        let metrics = integration.get_metrics().await;
758        assert_eq!(metrics.total_requests, 1);
759        assert_eq!(metrics.successful_enhancements, 1);
760        assert_eq!(metrics.fallback_count, 0);
761        assert_eq!(metrics.avg_processing_time_ms, 50.0);
762    }
763
764    #[tokio::test]
765    async fn test_error_handling_and_fallback() {
766        let integration = create_test_integration().await.unwrap();
767        
768        // Simulate failed processing (timeout)
769        let failed_metrics = SemanticProcessingMetrics {
770            total_processing_time_ms: 2000, // Exceeds budget
771            within_time_budget: false,
772            results_reranked: 0,
773            ..Default::default()
774        };
775        
776        integration.update_metrics(&failed_metrics, false).await;
777        
778        let metrics = integration.get_metrics().await;
779        assert_eq!(metrics.total_requests, 1);
780        assert_eq!(metrics.successful_enhancements, 0);
781        assert_eq!(metrics.fallback_count, 1);
782    }
783
784    #[tokio::test]
785    async fn test_configuration_edge_cases() {
786        // Test with extreme configuration values
787        let config = SemanticIntegrationConfig {
788            enabled: false, // Disabled
789            nl_upshift_threshold: 1.1, // Invalid threshold > 1.0
790            max_processing_time_ms: 0, // Zero timeout
791            enable_conformal_routing: true,
792            fallback_on_error: false, // No fallback
793            enable_result_caching: true,
794            similarity_threshold: -0.1, // Invalid negative threshold
795        };
796        
797        let integration = SemanticSearchIntegration::new(config).await;
798        assert!(integration.is_ok(), "Should handle invalid configuration gracefully");
799    }
800
801    #[tokio::test]
802    async fn test_concurrent_processing() {
803        let integration = create_test_integration().await.unwrap();
804        
805        // Create multiple concurrent requests
806        let mut handles = vec![];
807        
808        for i in 0..5 {
809            let integration_clone = integration.clone();
810            let handle = tokio::spawn(async move {
811                let request = SemanticSearchRequest {
812                    base_request: SearchRequest {
813                        query: format!("concurrent test query {}", i),
814                        ..Default::default()
815                    },
816                    ..Default::default()
817                };
818                
819                // Use classify_query and manually update metrics for testing
820                let result = integration_clone.classify_query(&request).await;
821                
822                // Manually update metrics for the test
823                let processing_metrics = SemanticProcessingMetrics {
824                    total_processing_time_ms: 50,
825                    classification_time_ms: 10,
826                    routing_time_ms: 10,
827                    encoding_time_ms: 20,
828                    conformal_time_ms: 10,
829                    results_reranked: 0,
830                    avg_similarity_score: 0.7,
831                    within_time_budget: true,
832                };
833                integration_clone.update_metrics(&processing_metrics, result.is_ok()).await;
834                
835                result
836            });
837            handles.push(handle);
838        }
839        
840        // Wait for all requests to complete
841        let mut successful = 0;
842        for handle in handles {
843            if let Ok(result) = handle.await {
844                if result.is_ok() {
845                    successful += 1;
846                }
847            }
848        }
849        
850        assert!(successful >= 4, "Most concurrent requests should succeed");
851        
852        let metrics = integration.get_metrics().await;
853        assert!(metrics.total_requests >= 5);
854    }
855
856    #[tokio::test]
857    async fn test_health_monitoring_degradation() {
858        let integration = create_test_integration().await.unwrap();
859        
860        // Initial health should be good
861        let initial_health = integration.health_check().await.unwrap();
862        assert!(initial_health.overall_healthy);
863        
864        // Simulate component failures by processing many failed requests
865        for _ in 0..10 {
866            let failed_metrics = SemanticProcessingMetrics {
867                total_processing_time_ms: 5000, // Timeout
868                within_time_budget: false,
869                results_reranked: 0,
870                ..Default::default()
871            };
872            integration.update_metrics(&failed_metrics, false).await;
873        }
874        
875        // Health might degrade (depends on implementation)
876        let degraded_health = integration.health_check().await.unwrap();
877        // Health status calculation depends on implementation details
878        assert!(degraded_health.last_check > initial_health.last_check);
879    }
880
881    #[tokio::test]
882    async fn test_semantic_processing_timeout() {
883        let config = SemanticIntegrationConfig {
884            enabled: true,
885            max_processing_time_ms: 1, // Very short timeout
886            ..Default::default()
887        };
888        
889        let integration = SemanticSearchIntegration::new(config).await.unwrap();
890        
891        let request = SemanticSearchRequest {
892            base_request: SearchRequest {
893                query: "complex natural language query that might take time to process".to_string(),
894                ..Default::default()
895            },
896            ..Default::default()
897        };
898        
899        // Should handle timeout gracefully
900        let result = integration.classify_query(&request).await;
901        assert!(result.is_ok(), "Should handle timeout gracefully");
902    }
903
904    #[tokio::test]
905    async fn test_multiple_intent_classification() {
906        let integration = create_test_integration().await.unwrap();
907        
908        let complex_queries = vec![
909            "find all function definitions that handle authentication",
910            "class User extends BaseModel",
911            "TODO: implement caching layer",
912            "import pandas as pd",
913            "def calculate_metrics(): pass",
914            "// This is a comment",
915            "SELECT * FROM users WHERE active = 1",
916        ];
917        
918        for query in complex_queries {
919            let request = SemanticSearchRequest {
920                base_request: SearchRequest {
921                    query: query.to_string(),
922                    ..Default::default()
923                },
924                ..Default::default()
925            };
926            
927            let classification = integration.classify_query(&request).await.unwrap();
928            assert!(classification.confidence > 0.0);
929            assert!(classification.confidence <= 1.0);
930            
931            // Verify intent is one of the valid types
932            match classification.intent {
933                QueryIntent::NaturalLanguage | QueryIntent::Symbol | 
934                QueryIntent::Structural | QueryIntent::Definition |
935                QueryIntent::References | QueryIntent::Lexical => {
936                    // Valid intent
937                }
938                _ => panic!("Unexpected query intent: {:?}", classification.intent),
939            }
940        }
941    }
942
943    #[tokio::test]
944    async fn test_metrics_accumulation() {
945        let integration = create_test_integration().await.unwrap();
946        
947        let test_scenarios = vec![
948            (50, true, 3),   // Fast successful processing
949            (150, true, 7),  // Slower successful processing
950            (1200, false, 0), // Timeout failure
951            (75, true, 5),   // Normal successful processing
952        ];
953        
954        for (latency, success, results_count) in test_scenarios {
955            let metrics = SemanticProcessingMetrics {
956                total_processing_time_ms: latency,
957                within_time_budget: success,
958                results_reranked: results_count,
959                ..Default::default()
960            };
961            
962            integration.update_metrics(&metrics, success).await;
963        }
964        
965        let final_metrics = integration.get_metrics().await;
966        assert_eq!(final_metrics.total_requests, 4);
967        assert_eq!(final_metrics.successful_enhancements, 3);
968        assert_eq!(final_metrics.fallback_count, 1);
969        
970        // Check average processing time calculation
971        let expected_avg = (50 + 150 + 1200 + 75) as f64 / 4.0;
972        assert!((final_metrics.avg_processing_time_ms - expected_avg).abs() < 0.1);
973    }
974
975    #[tokio::test]
976    async fn test_configuration_validation() {
977        // Valid configuration
978        let valid_config = SemanticIntegrationConfig {
979            enabled: true,
980            nl_upshift_threshold: 0.7,
981            max_processing_time_ms: 100,
982            enable_conformal_routing: true,
983            fallback_on_error: true,
984            enable_result_caching: true,
985            similarity_threshold: 0.5,
986        };
987        
988        let integration = SemanticSearchIntegration::new(valid_config).await;
989        assert!(integration.is_ok(), "Valid configuration should work");
990        
991        // Configuration with edge values
992        let edge_config = SemanticIntegrationConfig {
993            enabled: true,
994            nl_upshift_threshold: 0.0, // Minimum threshold
995            max_processing_time_ms: 1,  // Minimum timeout
996            enable_conformal_routing: false,
997            fallback_on_error: false,
998            enable_result_caching: false,
999            similarity_threshold: 1.0, // Maximum similarity
1000        };
1001        
1002        let edge_integration = SemanticSearchIntegration::new(edge_config).await;
1003        assert!(edge_integration.is_ok(), "Edge configuration should work");
1004    }
1005
1006    #[tokio::test]
1007    async fn test_component_isolation() {
1008        let integration = create_test_integration().await.unwrap();
1009        
1010        // Test that failure in one component doesn't crash the others
1011        let request = SemanticSearchRequest {
1012            base_request: SearchRequest {
1013                query: "test isolation".to_string(),
1014                ..Default::default()
1015            },
1016            force_semantic: true,
1017            skip_conformal: true, // Disabled for isolation test
1018            ..Default::default()
1019        };
1020        
1021        // Multiple operations should work independently
1022        let classification1 = integration.classify_query(&request).await;
1023        let classification2 = integration.classify_query(&request).await;
1024        
1025        assert!(classification1.is_ok());
1026        assert!(classification2.is_ok());
1027        
1028        // Results should be consistent
1029        if let (Ok(c1), Ok(c2)) = (classification1, classification2) {
1030            assert_eq!(c1.intent, c2.intent);
1031        }
1032    }
1033}