1use 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#[derive(Clone)]
26pub struct SemanticSearchIntegration {
27 encoder: Arc<SemanticEncoder>,
29 classifier: Arc<QueryClassifier>,
31 intent_router: Arc<IntentRouter>,
33 conformal_router: Arc<ConformalRouter>,
35 config: SemanticIntegrationConfig,
37 metrics: Arc<RwLock<IntegrationMetrics>>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SemanticIntegrationConfig {
44 pub enabled: bool,
46 pub nl_upshift_threshold: f32,
48 pub max_processing_time_ms: u64,
50 pub enable_conformal_routing: bool,
52 pub fallback_on_error: bool,
54 pub enable_result_caching: bool,
56 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, enable_conformal_routing: true,
67 fallback_on_error: true,
68 enable_result_caching: true,
69 similarity_threshold: 0.5,
70 }
71 }
72}
73
74#[derive(Debug, Default, Clone, Serialize, Deserialize)]
76pub struct IntegrationMetrics {
77 pub total_requests: u64,
79 pub successful_enhancements: u64,
81 pub fallback_count: u64,
83 pub avg_processing_time_ms: f64,
85 pub classification_accuracy: f64,
87 pub upshift_decisions: u64,
89 pub conformal_routing_decisions: u64,
91 pub avg_relevance_improvement: f64,
93 pub cache_hit_rate: f64,
95}
96
97#[derive(Debug, Clone)]
99pub struct SemanticSearchRequest {
100 pub base_request: SearchRequest,
102 pub force_semantic: bool,
104 pub intent_override: Option<QueryIntent>,
106 pub skip_conformal: bool,
108 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#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct SemanticSearchResponse {
139 pub base_response: SearchResponse,
141 pub classification: Option<QueryClassification>,
143 pub routing_decision: Option<RoutingDecision>,
145 pub upshift_decision: Option<RoutingDecision>,
147 pub semantic_metrics: SemanticProcessingMetrics,
149 pub semantic_enhanced: bool,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct SemanticProcessingMetrics {
156 pub classification_time_ms: u64,
158 pub routing_time_ms: u64,
160 pub encoding_time_ms: u64,
162 pub conformal_time_ms: u64,
164 pub total_processing_time_ms: u64,
166 pub results_reranked: usize,
168 pub avg_similarity_score: f32,
170 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 pub async fn new(config: SemanticIntegrationConfig) -> Result<Self> {
192 info!("Initializing semantic search integration system");
193
194 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 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 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 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 let conformal_config = ConformalRouterConfig {
236 risk_threshold: 0.6,
237 daily_budget_percent: 5.0, confidence_level: 0.95, 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 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, 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 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 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 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 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 let original_query = request.base_request.query.clone();
308
309 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 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 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 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 async fn classify_query(&self, request: &SemanticSearchRequest) -> Result<QueryClassification> {
350 if let Some(intent_override) = &request.intent_override {
351 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 async fn route_intent(
367 &self,
368 request: &SemanticSearchRequest,
369 classification: &QueryClassification
370 ) -> Result<RoutingDecision> {
371 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(), }
384 }),
385 user_preferences: None,
386 };
387
388 let intent_result = self.intent_router.route_query(&context).await?;
389
390 Ok(RoutingDecision {
392 should_upshift: intent_result.routing_path.contains(&"lsp_routing".to_string()),
393 upshift_type: UpshiftType::LSPIntegration, budget_consumed: 1.0, 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, confidence_interval: (0.4, 0.6), nonconformity_score: 0.3, calibrated: true, risk_factors: Vec::new(), },
404 })
405 }
406
407 async fn conformal_route(
409 &self,
410 request: &SemanticSearchRequest,
411 classification: &QueryClassification,
412 routing_decision: &RoutingDecision,
413 ) -> Result<RoutingDecision> {
414 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 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, 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, user_satisfaction_history: 0.75, };
442
443 self.conformal_router.make_routing_decision(&input, classification).await
444 }
445
446 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 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, _ => SearchMethod::Hybrid, });
463
464 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 }
477 }
478 }
479 }
480
481 if classification.complexity_score > 0.8 {
483 base_request.timeout_ms = (base_request.timeout_ms as f32 * 1.2) as u64; }
485
486 base_request
487 }
488
489 fn should_apply_semantic_reranking(
491 &self,
492 classification: &QueryClassification,
493 routing_decision: &RoutingDecision,
494 upshift_decision: &Option<RoutingDecision>,
495 ) -> bool {
496 if classification.intent == QueryIntent::NaturalLanguage && classification.confidence > 0.7 {
498 return true;
499 }
500
501 if let Some(upshift) = upshift_decision {
503 if upshift.should_upshift && upshift.upshift_type == UpshiftType::SemanticReranking {
504 return true;
505 }
506 }
507
508 if matches!(routing_decision.upshift_type, UpshiftType::SemanticReranking) {
510 return true;
511 }
512
513 false
514 }
515
516 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 let query_embedding = self.encoder.encode_query(query).await?;
528
529 let mut similarities = Vec::new();
531
532 for result in &response.results {
533 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); }
545 }
546 }
547
548 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 result.score = 0.7 * semantic_score as f64 + 0.3 * result.score;
556 }
557 }
559
560 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 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 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 if !processing_metrics.within_time_budget {
584 debug!("Semantic processing exceeded time budget");
585 }
586 }
587
588 pub async fn get_metrics(&self) -> IntegrationMetrics {
590 self.metrics.read().await.clone()
591 }
592
593 pub async fn health_check(&self) -> Result<SemanticHealthStatus> {
595 let mut status = SemanticHealthStatus::default();
596
597 status.encoder_healthy = self.encoder.health_check().await.is_ok();
599
600 status.classifier_healthy = true;
603
604 status.intent_router_healthy = true;
607
608 status.conformal_router_healthy = true;
611
612 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 pub async fn shutdown(&self) -> Result<()> {
623 info!("Shutting down semantic search integration");
624
625 info!("Semantic search integration shutdown complete");
630 Ok(())
631 }
632}
633
634#[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
658pub trait SearchEngineSemanticExt {
660 async fn search_semantic(&self, request: SemanticSearchRequest, integration: &SemanticSearchIntegration) -> Result<SemanticSearchResponse>;
662
663 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, enable_conformal_routing: false, 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 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), ("SearchEngine", QueryIntent::Symbol), ];
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 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 let failed_metrics = SemanticProcessingMetrics {
770 total_processing_time_ms: 2000, 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 let config = SemanticIntegrationConfig {
788 enabled: false, nl_upshift_threshold: 1.1, max_processing_time_ms: 0, enable_conformal_routing: true,
792 fallback_on_error: false, enable_result_caching: true,
794 similarity_threshold: -0.1, };
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 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 let result = integration_clone.classify_query(&request).await;
821
822 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 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 let initial_health = integration.health_check().await.unwrap();
862 assert!(initial_health.overall_healthy);
863
864 for _ in 0..10 {
866 let failed_metrics = SemanticProcessingMetrics {
867 total_processing_time_ms: 5000, within_time_budget: false,
869 results_reranked: 0,
870 ..Default::default()
871 };
872 integration.update_metrics(&failed_metrics, false).await;
873 }
874
875 let degraded_health = integration.health_check().await.unwrap();
877 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, ..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 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 match classification.intent {
933 QueryIntent::NaturalLanguage | QueryIntent::Symbol |
934 QueryIntent::Structural | QueryIntent::Definition |
935 QueryIntent::References | QueryIntent::Lexical => {
936 }
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), (150, true, 7), (1200, false, 0), (75, true, 5), ];
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 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 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 let edge_config = SemanticIntegrationConfig {
993 enabled: true,
994 nl_upshift_threshold: 0.0, max_processing_time_ms: 1, enable_conformal_routing: false,
997 fallback_on_error: false,
998 enable_result_caching: false,
999 similarity_threshold: 1.0, };
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 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, ..Default::default()
1019 };
1020
1021 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 if let (Ok(c1), Ok(c2)) = (classification1, classification2) {
1030 assert_eq!(c1.intent, c2.intent);
1031 }
1032 }
1033}