oxirs_embed/
adaptive_learning.rs

1//! Adaptive Learning System for Real-Time Embedding Enhancement
2//!
3//! This module implements an advanced adaptive learning system that continuously
4//! improves embedding quality through online learning, feedback mechanisms,
5//! and dynamic model adaptation based on usage patterns.
6
7use anyhow::Result;
8use chrono::{DateTime, Utc};
9use nalgebra::{DMatrix, DVector};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12use std::sync::{Arc, RwLock};
13use std::time::{Duration, Instant};
14use tokio::sync::mpsc;
15use tracing::{debug, info, warn};
16
17/// Configuration for adaptive learning system
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AdaptiveLearningConfig {
20    /// Learning rate for adaptation
21    pub learning_rate: f64,
22    /// Size of the experience buffer
23    pub buffer_size: usize,
24    /// Minimum samples before adaptation
25    pub min_samples_for_adaptation: usize,
26    /// Maximum adaptation frequency (per second)
27    pub max_adaptation_frequency: f64,
28    /// Quality threshold for positive feedback
29    pub quality_threshold: f64,
30    /// Enable meta-learning adaptation
31    pub enable_meta_learning: bool,
32    /// Batch size for adaptation updates
33    pub adaptation_batch_size: usize,
34}
35
36impl Default for AdaptiveLearningConfig {
37    fn default() -> Self {
38        Self {
39            learning_rate: 0.001,
40            buffer_size: 10000,
41            min_samples_for_adaptation: 100,
42            max_adaptation_frequency: 1.0,
43            quality_threshold: 0.8,
44            enable_meta_learning: true,
45            adaptation_batch_size: 32,
46        }
47    }
48}
49
50/// Feedback signal for embedding quality
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct QualityFeedback {
53    /// Query that generated the embedding
54    pub query: String,
55    /// Generated embedding
56    pub embedding: Vec<f64>,
57    /// Quality score (0.0 to 1.0)
58    pub quality_score: f64,
59    /// Timestamp of feedback  
60    #[serde(with = "chrono::serde::ts_seconds")]
61    pub timestamp: DateTime<Utc>,
62    /// User-provided relevance score
63    pub relevance: Option<f64>,
64    /// Task context
65    pub task_context: Option<String>,
66}
67
68/// Experience sample for adaptive learning
69#[derive(Debug, Clone)]
70pub struct ExperienceSample {
71    /// Input query/text
72    pub input: String,
73    /// Target embedding (from positive feedback)
74    pub target: Vec<f64>,
75    /// Current embedding (what model produced)
76    pub current: Vec<f64>,
77    /// Quality improvement needed
78    pub improvement_target: f64,
79    /// Context information
80    pub context: HashMap<String, String>,
81}
82
83/// Adaptation strategy for different learning scenarios
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub enum AdaptationStrategy {
86    /// Gradient-based fine-tuning
87    GradientDescent { momentum: f64, weight_decay: f64 },
88    /// Evolutionary adaptation
89    Evolutionary {
90        mutation_rate: f64,
91        population_size: usize,
92    },
93    /// Meta-learning adaptation (MAML-style)
94    MetaLearning {
95        inner_steps: usize,
96        outer_learning_rate: f64,
97    },
98    /// Bayesian optimization
99    BayesianOptimization {
100        exploration_factor: f64,
101        kernel_bandwidth: f64,
102    },
103}
104
105impl Default for AdaptationStrategy {
106    fn default() -> Self {
107        Self::GradientDescent {
108            momentum: 0.9,
109            weight_decay: 0.0001,
110        }
111    }
112}
113
114/// Performance metrics for adaptation tracking
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct AdaptationMetrics {
117    /// Number of adaptations performed
118    pub adaptations_count: usize,
119    /// Average quality improvement per adaptation
120    pub avg_quality_improvement: f64,
121    /// Current adaptation rate (adaptations per minute)
122    pub adaptation_rate: f64,
123    /// Experience buffer utilization
124    pub buffer_utilization: f64,
125    /// Model performance drift detection
126    pub performance_drift: f64,
127    /// Last adaptation timestamp
128    #[serde(with = "chrono::serde::ts_seconds_option")]
129    pub last_adaptation: Option<DateTime<Utc>>,
130}
131
132impl Default for AdaptationMetrics {
133    fn default() -> Self {
134        Self {
135            adaptations_count: 0,
136            avg_quality_improvement: 0.0,
137            adaptation_rate: 0.0,
138            buffer_utilization: 0.0,
139            performance_drift: 0.0,
140            last_adaptation: None,
141        }
142    }
143}
144
145/// Adaptive learning system for continuous embedding improvement
146pub struct AdaptiveLearningSystem {
147    /// Configuration
148    config: AdaptiveLearningConfig,
149    /// Experience buffer for storing feedback
150    experience_buffer: Arc<RwLock<VecDeque<ExperienceSample>>>,
151    /// Quality feedback receiver
152    feedback_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<QualityFeedback>>>>,
153    /// Quality feedback sender
154    feedback_sender: mpsc::UnboundedSender<QualityFeedback>,
155    /// Adaptation strategy
156    strategy: AdaptationStrategy,
157    /// Current metrics
158    metrics: Arc<RwLock<AdaptationMetrics>>,
159    /// Model parameters for adaptation
160    model_parameters: Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
161    /// Learning state
162    learning_state: Arc<RwLock<LearningState>>,
163}
164
165/// Internal learning state
166#[derive(Debug, Clone)]
167struct LearningState {
168    /// Momentum vectors for gradient descent
169    momentum: HashMap<String, DMatrix<f64>>,
170    /// Adaptation history
171    adaptation_history: VecDeque<AdaptationRecord>,
172    /// Current learning rate (adaptive)
173    current_learning_rate: f64,
174    /// Performance baseline
175    performance_baseline: f64,
176}
177
178/// Record of adaptation for analysis
179#[derive(Debug, Clone)]
180#[allow(dead_code)]
181struct AdaptationRecord {
182    /// Timestamp of adaptation
183    timestamp: DateTime<Utc>,
184    /// Quality before adaptation
185    quality_before: f64,
186    /// Quality after adaptation
187    quality_after: f64,
188    /// Number of samples used
189    samples_used: usize,
190    /// Strategy used
191    strategy: AdaptationStrategy,
192}
193
194impl AdaptiveLearningSystem {
195    /// Create new adaptive learning system
196    pub fn new(config: AdaptiveLearningConfig) -> Self {
197        let (sender, receiver) = mpsc::unbounded_channel();
198        let learning_rate = config.learning_rate;
199
200        Self {
201            config,
202            experience_buffer: Arc::new(RwLock::new(VecDeque::new())),
203            feedback_receiver: Arc::new(RwLock::new(Some(receiver))),
204            feedback_sender: sender,
205            strategy: AdaptationStrategy::default(),
206            metrics: Arc::new(RwLock::new(AdaptationMetrics::default())),
207            model_parameters: Arc::new(RwLock::new(HashMap::new())),
208            learning_state: Arc::new(RwLock::new(LearningState {
209                momentum: HashMap::new(),
210                adaptation_history: VecDeque::new(),
211                current_learning_rate: learning_rate,
212                performance_baseline: 0.5,
213            })),
214        }
215    }
216
217    /// Create system with custom strategy
218    pub fn with_strategy(config: AdaptiveLearningConfig, strategy: AdaptationStrategy) -> Self {
219        let mut system = Self::new(config);
220        system.strategy = strategy;
221        system
222    }
223
224    /// Submit quality feedback
225    pub fn submit_feedback(&self, feedback: QualityFeedback) -> Result<()> {
226        self.feedback_sender.send(feedback)?;
227        Ok(())
228    }
229
230    /// Start the adaptive learning process
231    pub async fn start_learning(&self) -> Result<()> {
232        let mut receiver = self
233            .feedback_receiver
234            .write()
235            .expect("lock poisoned")
236            .take()
237            .ok_or_else(|| anyhow::anyhow!("Learning already started"))?;
238
239        info!("Starting adaptive learning system");
240
241        // Spawn feedback processing task
242        let experience_buffer = Arc::clone(&self.experience_buffer);
243        let metrics = Arc::clone(&self.metrics);
244        let config = self.config.clone();
245
246        tokio::spawn(async move {
247            while let Some(feedback) = receiver.recv().await {
248                if let Err(e) =
249                    Self::process_feedback(feedback, &experience_buffer, &metrics, &config).await
250                {
251                    warn!("Error processing feedback: {}", e);
252                }
253            }
254        });
255
256        // Spawn adaptation task
257        let buffer = Arc::clone(&self.experience_buffer);
258        let metrics = Arc::clone(&self.metrics);
259        let parameters = Arc::clone(&self.model_parameters);
260        let learning_state = Arc::clone(&self.learning_state);
261        let config = self.config.clone();
262        let strategy = self.strategy.clone();
263
264        tokio::spawn(async move {
265            let mut last_adaptation = Instant::now();
266
267            loop {
268                tokio::time::sleep(Duration::from_millis(100)).await;
269
270                // Check if we should perform adaptation
271                let should_adapt = {
272                    let buffer_guard = buffer.read().expect("lock poisoned");
273                    let _metrics_guard = metrics.read().expect("lock poisoned");
274
275                    buffer_guard.len() >= config.min_samples_for_adaptation
276                        && last_adaptation.elapsed().as_secs_f64()
277                            >= 1.0 / config.max_adaptation_frequency
278                };
279
280                if should_adapt {
281                    match Self::perform_adaptation(
282                        &buffer,
283                        &metrics,
284                        &parameters,
285                        &learning_state,
286                        &config,
287                        &strategy,
288                    )
289                    .await
290                    {
291                        Err(e) => {
292                            warn!("Error during adaptation: {}", e);
293                        }
294                        _ => {
295                            last_adaptation = Instant::now();
296                        }
297                    }
298                }
299            }
300        });
301
302        Ok(())
303    }
304
305    /// Process incoming feedback
306    async fn process_feedback(
307        feedback: QualityFeedback,
308        buffer: &Arc<RwLock<VecDeque<ExperienceSample>>>,
309        metrics: &Arc<RwLock<AdaptationMetrics>>,
310        config: &AdaptiveLearningConfig,
311    ) -> Result<()> {
312        // Convert feedback to experience sample
313        if feedback.quality_score > config.quality_threshold {
314            let sample = ExperienceSample {
315                input: feedback.query.clone(),
316                target: feedback.embedding.clone(),
317                current: feedback.embedding.clone(), // This would be the current model output
318                improvement_target: 1.0 - feedback.quality_score,
319                context: feedback
320                    .task_context
321                    .map(|ctx| [("task".to_string(), ctx)].into())
322                    .unwrap_or_default(),
323            };
324
325            // Add to buffer
326            {
327                let mut buffer_guard = buffer.write().expect("lock poisoned");
328                buffer_guard.push_back(sample);
329
330                // Maintain buffer size
331                while buffer_guard.len() > config.buffer_size {
332                    buffer_guard.pop_front();
333                }
334            }
335
336            // Update metrics
337            {
338                let mut metrics_guard = metrics.write().expect("lock poisoned");
339                let buffer_guard = buffer.read().expect("lock poisoned");
340                metrics_guard.buffer_utilization =
341                    buffer_guard.len() as f64 / config.buffer_size as f64;
342            }
343
344            debug!(
345                "Processed feedback with quality score: {}",
346                feedback.quality_score
347            );
348        }
349
350        Ok(())
351    }
352
353    /// Perform model adaptation
354    async fn perform_adaptation(
355        buffer: &Arc<RwLock<VecDeque<ExperienceSample>>>,
356        metrics: &Arc<RwLock<AdaptationMetrics>>,
357        parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
358        learning_state: &Arc<RwLock<LearningState>>,
359        config: &AdaptiveLearningConfig,
360        strategy: &AdaptationStrategy,
361    ) -> Result<()> {
362        let samples = {
363            let buffer_guard = buffer.read().expect("lock poisoned");
364            buffer_guard
365                .iter()
366                .take(config.adaptation_batch_size)
367                .cloned()
368                .collect::<Vec<_>>()
369        };
370
371        if samples.is_empty() {
372            return Ok(());
373        }
374
375        info!("Performing adaptation with {} samples", samples.len());
376
377        // Calculate quality before adaptation
378        let quality_before = Self::calculate_current_quality(&samples)?;
379
380        // Perform adaptation based on strategy
381        match strategy {
382            AdaptationStrategy::GradientDescent {
383                momentum,
384                weight_decay,
385            } => {
386                Self::gradient_descent_adaptation(
387                    &samples,
388                    parameters,
389                    learning_state,
390                    *momentum,
391                    *weight_decay,
392                    config.learning_rate,
393                )?;
394            }
395            AdaptationStrategy::MetaLearning {
396                inner_steps,
397                outer_learning_rate,
398            } => {
399                Self::meta_learning_adaptation(
400                    &samples,
401                    parameters,
402                    learning_state,
403                    *inner_steps,
404                    *outer_learning_rate,
405                )?;
406            }
407            AdaptationStrategy::Evolutionary {
408                mutation_rate,
409                population_size,
410            } => {
411                Self::evolutionary_adaptation(
412                    &samples,
413                    parameters,
414                    *mutation_rate,
415                    *population_size,
416                )?;
417            }
418            AdaptationStrategy::BayesianOptimization {
419                exploration_factor,
420                kernel_bandwidth,
421            } => {
422                Self::bayesian_optimization_adaptation(
423                    &samples,
424                    parameters,
425                    *exploration_factor,
426                    *kernel_bandwidth,
427                )?;
428            }
429        }
430
431        // Calculate quality after adaptation
432        let quality_after = Self::calculate_current_quality(&samples)?;
433
434        // Update metrics
435        {
436            let mut metrics_guard = metrics.write().expect("lock poisoned");
437            metrics_guard.adaptations_count += 1;
438            let improvement = quality_after - quality_before;
439            metrics_guard.avg_quality_improvement = (metrics_guard.avg_quality_improvement
440                * (metrics_guard.adaptations_count - 1) as f64
441                + improvement)
442                / metrics_guard.adaptations_count as f64;
443            metrics_guard.last_adaptation = Some(Utc::now());
444        }
445
446        // Update learning state
447        {
448            let mut state_guard = learning_state.write().expect("lock poisoned");
449            state_guard.adaptation_history.push_back(AdaptationRecord {
450                timestamp: Utc::now(),
451                quality_before,
452                quality_after,
453                samples_used: samples.len(),
454                strategy: strategy.clone(),
455            });
456
457            // Maintain history size
458            while state_guard.adaptation_history.len() > 1000 {
459                state_guard.adaptation_history.pop_front();
460            }
461
462            // Adaptive learning rate
463            if quality_after > quality_before {
464                state_guard.current_learning_rate *= 1.01; // Increase slightly
465            } else {
466                state_guard.current_learning_rate *= 0.95; // Decrease
467            }
468            state_guard.current_learning_rate = state_guard
469                .current_learning_rate
470                .max(config.learning_rate * 0.1)
471                .min(config.learning_rate * 10.0);
472        }
473
474        info!(
475            "Adaptation completed: quality improved by {:.4}",
476            quality_after - quality_before
477        );
478
479        Ok(())
480    }
481
482    /// Calculate current quality based on samples
483    fn calculate_current_quality(samples: &[ExperienceSample]) -> Result<f64> {
484        if samples.is_empty() {
485            return Ok(0.0);
486        }
487
488        let total_quality: f64 = samples
489            .iter()
490            .map(|sample| {
491                // Calculate similarity between current and target embeddings
492                let current = DVector::from_vec(sample.current.clone());
493                let target = DVector::from_vec(sample.target.clone());
494
495                if current.len() != target.len() {
496                    return 0.0;
497                }
498
499                // Cosine similarity
500                let dot_product = current.dot(&target);
501                let norm_current = current.norm();
502                let norm_target = target.norm();
503
504                if norm_current == 0.0 || norm_target == 0.0 {
505                    return 0.0;
506                }
507
508                (dot_product / (norm_current * norm_target)).max(0.0)
509            })
510            .sum();
511
512        Ok(total_quality / samples.len() as f64)
513    }
514
515    /// Gradient descent adaptation
516    fn gradient_descent_adaptation(
517        samples: &[ExperienceSample],
518        parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
519        learning_state: &Arc<RwLock<LearningState>>,
520        momentum: f64,
521        weight_decay: f64,
522        learning_rate: f64,
523    ) -> Result<()> {
524        // Simplified gradient descent implementation
525        // In a real implementation, this would compute gradients based on the loss
526        // between current embeddings and target embeddings
527
528        let mut params_guard = parameters.write().expect("lock poisoned");
529        let mut state_guard = learning_state.write().expect("lock poisoned");
530
531        for (param_name, param_matrix) in params_guard.iter_mut() {
532            // Compute pseudo-gradient (simplified for demonstration)
533            let gradient = Self::compute_gradient(samples, param_matrix)?;
534
535            // Update momentum
536            let momentum_entry = state_guard
537                .momentum
538                .entry(param_name.clone())
539                .or_insert_with(|| DMatrix::zeros(param_matrix.nrows(), param_matrix.ncols()));
540
541            *momentum_entry = momentum_entry.clone() * momentum + &gradient;
542
543            // Apply weight decay
544            let decay_term = param_matrix.clone() * weight_decay;
545
546            // Update parameters
547            *param_matrix -= &(momentum_entry.clone() * learning_rate + decay_term * learning_rate);
548        }
549
550        Ok(())
551    }
552
553    /// Compute gradient (simplified placeholder)
554    fn compute_gradient(
555        _samples: &[ExperienceSample],
556        param_matrix: &DMatrix<f64>,
557    ) -> Result<DMatrix<f64>> {
558        // Simplified gradient computation
559        // In practice, this would involve backpropagation through the embedding model
560        let mut gradient = DMatrix::zeros(param_matrix.nrows(), param_matrix.ncols());
561
562        // Add small random perturbations as a placeholder
563        for i in 0..gradient.nrows() {
564            for j in 0..gradient.ncols() {
565                gradient[(i, j)] = ({
566                    use scirs2_core::random::{Random, Rng};
567                    let mut random = Random::default();
568                    random.random::<f64>()
569                } - 0.5)
570                    * 0.001;
571            }
572        }
573
574        Ok(gradient)
575    }
576
577    /// Meta-learning adaptation (MAML-style)
578    fn meta_learning_adaptation(
579        samples: &[ExperienceSample],
580        parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
581        _learning_state: &Arc<RwLock<LearningState>>,
582        inner_steps: usize,
583        outer_learning_rate: f64,
584    ) -> Result<()> {
585        let mut params_guard = parameters.write().expect("lock poisoned");
586
587        // MAML inner loop
588        for _ in 0..inner_steps {
589            for (_, param_matrix) in params_guard.iter_mut() {
590                let gradient = Self::compute_gradient(samples, param_matrix)?;
591                *param_matrix -= &(gradient * outer_learning_rate);
592            }
593        }
594
595        Ok(())
596    }
597
598    /// Evolutionary adaptation
599    fn evolutionary_adaptation(
600        samples: &[ExperienceSample],
601        parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
602        mutation_rate: f64,
603        population_size: usize,
604    ) -> Result<()> {
605        let mut params_guard = parameters.write().expect("lock poisoned");
606
607        // Simple evolutionary strategy
608        for (_, param_matrix) in params_guard.iter_mut() {
609            let mut best_fitness = Self::evaluate_fitness(samples, param_matrix)?;
610            let mut best_params = param_matrix.clone();
611
612            // Generate population
613            for _ in 0..population_size {
614                let mut mutated = param_matrix.clone();
615
616                // Apply mutations
617                for i in 0..mutated.nrows() {
618                    for j in 0..mutated.ncols() {
619                        if {
620                            use scirs2_core::random::{Random, Rng};
621                            let mut random = Random::default();
622                            random.random::<f64>()
623                        } < mutation_rate
624                        {
625                            mutated[(i, j)] += ({
626                                use scirs2_core::random::{Random, Rng};
627                                let mut random = Random::default();
628                                random.random::<f64>()
629                            } - 0.5)
630                                * 0.01;
631                        }
632                    }
633                }
634
635                let fitness = Self::evaluate_fitness(samples, &mutated)?;
636                if fitness > best_fitness {
637                    best_fitness = fitness;
638                    best_params = mutated;
639                }
640            }
641
642            *param_matrix = best_params;
643        }
644
645        Ok(())
646    }
647
648    /// Evaluate fitness for evolutionary adaptation
649    fn evaluate_fitness(
650        _samples: &[ExperienceSample],
651        _param_matrix: &DMatrix<f64>,
652    ) -> Result<f64> {
653        // Simplified fitness evaluation
654        // In practice, this would evaluate how well the parameters perform on the samples
655        Ok({
656            use scirs2_core::random::{Random, Rng};
657            let mut random = Random::default();
658            random.random::<f64>()
659        })
660    }
661
662    /// Bayesian optimization adaptation
663    fn bayesian_optimization_adaptation(
664        samples: &[ExperienceSample],
665        parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
666        exploration_factor: f64,
667        _kernel_bandwidth: f64,
668    ) -> Result<()> {
669        let mut params_guard = parameters.write().expect("lock poisoned");
670
671        // Simplified Bayesian optimization
672        for (_, param_matrix) in params_guard.iter_mut() {
673            let current_fitness = Self::evaluate_fitness(samples, param_matrix)?;
674
675            // Generate candidate solutions
676            let mut best_candidate = param_matrix.clone();
677            let mut best_acquisition = 0.0;
678
679            for _ in 0..10 {
680                let mut candidate = param_matrix.clone();
681
682                // Add exploration noise
683                for i in 0..candidate.nrows() {
684                    for j in 0..candidate.ncols() {
685                        candidate[(i, j)] += ({
686                            use scirs2_core::random::{Random, Rng};
687                            let mut random = Random::default();
688                            random.random::<f64>()
689                        } - 0.5)
690                            * exploration_factor;
691                    }
692                }
693
694                let fitness = Self::evaluate_fitness(samples, &candidate)?;
695                let acquisition = fitness
696                    + exploration_factor * {
697                        use scirs2_core::random::{Random, Rng};
698                        let mut random = Random::default();
699                        random.random::<f64>()
700                    };
701
702                if acquisition > best_acquisition {
703                    best_acquisition = acquisition;
704                    best_candidate = candidate;
705                }
706            }
707
708            // Update only if improvement is significant
709            if best_acquisition > current_fitness + 0.01 {
710                *param_matrix = best_candidate;
711            }
712        }
713
714        Ok(())
715    }
716
717    /// Get current metrics
718    pub fn get_metrics(&self) -> AdaptationMetrics {
719        self.metrics.read().expect("lock poisoned").clone()
720    }
721
722    /// Get feedback sender for external use
723    pub fn get_feedback_sender(&self) -> mpsc::UnboundedSender<QualityFeedback> {
724        self.feedback_sender.clone()
725    }
726
727    /// Update adaptation strategy
728    pub fn set_strategy(&mut self, strategy: AdaptationStrategy) {
729        self.strategy = strategy;
730    }
731
732    /// Reset learning state
733    pub fn reset_learning_state(&self) {
734        let mut state_guard = self.learning_state.write().expect("lock poisoned");
735        state_guard.momentum.clear();
736        state_guard.adaptation_history.clear();
737        state_guard.current_learning_rate = self.config.learning_rate;
738        state_guard.performance_baseline = 0.5;
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745    use tokio::time::{sleep, Duration};
746
747    #[tokio::test]
748    async fn test_adaptive_learning_system_creation() {
749        let config = AdaptiveLearningConfig::default();
750        let system = AdaptiveLearningSystem::new(config);
751
752        let metrics = system.get_metrics();
753        assert_eq!(metrics.adaptations_count, 0);
754        assert_eq!(metrics.avg_quality_improvement, 0.0);
755    }
756
757    #[tokio::test]
758    async fn test_feedback_submission() {
759        let config = AdaptiveLearningConfig::default();
760        let system = AdaptiveLearningSystem::new(config);
761
762        let feedback = QualityFeedback {
763            query: "test query".to_string(),
764            embedding: vec![0.1, 0.2, 0.3],
765            quality_score: 0.9,
766            timestamp: Utc::now(),
767            relevance: Some(0.8),
768            task_context: Some("similarity".to_string()),
769        };
770
771        assert!(system.submit_feedback(feedback).is_ok());
772    }
773
774    #[tokio::test]
775    async fn test_adaptive_learning_config_default() {
776        let config = AdaptiveLearningConfig::default();
777
778        assert_eq!(config.learning_rate, 0.001);
779        assert_eq!(config.buffer_size, 10000);
780        assert_eq!(config.min_samples_for_adaptation, 100);
781        assert_eq!(config.quality_threshold, 0.8);
782        assert!(config.enable_meta_learning);
783    }
784
785    #[tokio::test]
786    async fn test_adaptation_strategies() {
787        let config = AdaptiveLearningConfig::default();
788
789        // Test different strategies
790        let strategies = vec![
791            AdaptationStrategy::GradientDescent {
792                momentum: 0.9,
793                weight_decay: 0.0001,
794            },
795            AdaptationStrategy::MetaLearning {
796                inner_steps: 3,
797                outer_learning_rate: 0.01,
798            },
799            AdaptationStrategy::Evolutionary {
800                mutation_rate: 0.1,
801                population_size: 20,
802            },
803            AdaptationStrategy::BayesianOptimization {
804                exploration_factor: 0.1,
805                kernel_bandwidth: 1.0,
806            },
807        ];
808
809        for strategy in strategies {
810            let system = AdaptiveLearningSystem::with_strategy(config.clone(), strategy);
811            assert!(system.start_learning().await.is_ok());
812
813            // Give some time for initialization
814            sleep(Duration::from_millis(10)).await;
815        }
816    }
817
818    #[tokio::test]
819    async fn test_quality_calculation() {
820        let samples = vec![
821            ExperienceSample {
822                input: "test1".to_string(),
823                target: vec![1.0, 0.0, 0.0],
824                current: vec![0.9, 0.1, 0.0],
825                improvement_target: 0.1,
826                context: HashMap::new(),
827            },
828            ExperienceSample {
829                input: "test2".to_string(),
830                target: vec![0.0, 1.0, 0.0],
831                current: vec![0.0, 0.8, 0.2],
832                improvement_target: 0.2,
833                context: HashMap::new(),
834            },
835        ];
836
837        let quality = AdaptiveLearningSystem::calculate_current_quality(&samples).unwrap();
838        assert!(quality > 0.0 && quality <= 1.0);
839    }
840}