rexis_rag/
system.rs

1//! # RRAG System Integration
2//!
3//! High-level system components for orchestrating complete RAG applications.
4//! Designed for production deployment with monitoring, configuration, and lifecycle management.
5
6use crate::{
7    Agent, Document, EmbeddingService, MemoryService, Pipeline, RetrievalService, RragError,
8    RragResult, SearchResult, StorageService,
9};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::Arc;
13use std::time::Instant;
14use tokio::sync::RwLock;
15use tracing::info;
16
17/// System configuration for RRAG
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct RragSystemConfig {
20    /// System name/identifier
21    pub name: String,
22
23    /// Version information
24    pub version: String,
25
26    /// Environment (dev, staging, prod)
27    pub environment: String,
28
29    /// Component configurations
30    pub components: ComponentConfigs,
31
32    /// Performance settings
33    pub performance: PerformanceConfig,
34
35    /// Monitoring configuration
36    pub monitoring: MonitoringConfig,
37
38    /// Feature flags
39    pub features: FeatureFlags,
40}
41
42/// Component-specific configurations
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ComponentConfigs {
45    /// Embedding service configuration
46    pub embedding: EmbeddingConfig,
47
48    /// Storage configuration
49    pub storage: StorageConfig,
50
51    /// Retrieval configuration
52    pub retrieval: RetrievalConfig,
53
54    /// Memory configuration
55    pub memory: MemoryConfig,
56
57    /// Agent configuration
58    pub agent: AgentConfig,
59}
60
61/// Embedding configuration
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct EmbeddingConfig {
64    pub provider: String,
65    pub model: String,
66    pub batch_size: usize,
67    pub timeout_seconds: u64,
68    pub max_retries: usize,
69    pub api_key_env: String,
70}
71
72/// Storage configuration
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct StorageConfig {
75    pub backend: String,
76    pub connection_string: Option<String>,
77    pub max_connections: Option<usize>,
78    pub timeout_seconds: u64,
79    pub enable_compression: bool,
80}
81
82/// Retrieval configuration
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct RetrievalConfig {
85    pub index_type: String,
86    pub similarity_threshold: f32,
87    pub max_results: usize,
88    pub enable_reranking: bool,
89    pub cache_results: bool,
90}
91
92/// Memory configuration
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct MemoryConfig {
95    pub memory_type: String,
96    pub max_messages: usize,
97    pub max_tokens: Option<usize>,
98    pub enable_summarization: bool,
99    pub persistence_enabled: bool,
100}
101
102/// Agent configuration
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct AgentConfig {
105    pub model_provider: String,
106    pub model_name: String,
107    pub temperature: f32,
108    pub max_tokens: usize,
109    pub max_tool_calls: usize,
110    pub enable_streaming: bool,
111}
112
113/// Performance configuration
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct PerformanceConfig {
116    /// Maximum concurrent operations
117    pub max_concurrency: usize,
118
119    /// Request timeout in seconds
120    pub request_timeout_seconds: u64,
121
122    /// Connection pool settings
123    pub connection_pool_size: usize,
124
125    /// Cache settings
126    pub cache_size: usize,
127    pub cache_ttl_seconds: u64,
128
129    /// Rate limiting
130    pub rate_limit_per_second: Option<u32>,
131}
132
133/// Monitoring configuration
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct MonitoringConfig {
136    /// Enable metrics collection
137    pub enable_metrics: bool,
138
139    /// Enable distributed tracing
140    pub enable_tracing: bool,
141
142    /// Log level
143    pub log_level: String,
144
145    /// Health check interval in seconds
146    pub health_check_interval_seconds: u64,
147
148    /// Metrics export configuration
149    pub metrics_endpoint: Option<String>,
150    pub tracing_endpoint: Option<String>,
151}
152
153/// Feature flags for system behavior
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct FeatureFlags {
156    /// Enable experimental features
157    pub enable_experimental: bool,
158
159    /// Enable async processing
160    pub enable_async_processing: bool,
161
162    /// Enable automatic retries
163    pub enable_auto_retry: bool,
164
165    /// Enable request validation
166    pub enable_validation: bool,
167
168    /// Enable response caching
169    pub enable_caching: bool,
170}
171
172impl Default for RragSystemConfig {
173    fn default() -> Self {
174        Self {
175            name: "RRAG System".to_string(),
176            version: "1.0.0".to_string(),
177            environment: "development".to_string(),
178            components: ComponentConfigs::default(),
179            performance: PerformanceConfig::default(),
180            monitoring: MonitoringConfig::default(),
181            features: FeatureFlags::default(),
182        }
183    }
184}
185
186impl Default for ComponentConfigs {
187    fn default() -> Self {
188        Self {
189            embedding: EmbeddingConfig::default(),
190            storage: StorageConfig::default(),
191            retrieval: RetrievalConfig::default(),
192            memory: MemoryConfig::default(),
193            agent: AgentConfig::default(),
194        }
195    }
196}
197
198impl Default for EmbeddingConfig {
199    fn default() -> Self {
200        Self {
201            provider: "openai".to_string(),
202            model: "text-embedding-ada-002".to_string(),
203            batch_size: 100,
204            timeout_seconds: 30,
205            max_retries: 3,
206            api_key_env: "OPENAI_API_KEY".to_string(),
207        }
208    }
209}
210
211impl Default for StorageConfig {
212    fn default() -> Self {
213        Self {
214            backend: "in_memory".to_string(),
215            connection_string: None,
216            max_connections: Some(10),
217            timeout_seconds: 30,
218            enable_compression: false,
219        }
220    }
221}
222
223impl Default for RetrievalConfig {
224    fn default() -> Self {
225        Self {
226            index_type: "in_memory".to_string(),
227            similarity_threshold: 0.7,
228            max_results: 10,
229            enable_reranking: true,
230            cache_results: false,
231        }
232    }
233}
234
235impl Default for MemoryConfig {
236    fn default() -> Self {
237        Self {
238            memory_type: "buffer".to_string(),
239            max_messages: 100,
240            max_tokens: Some(4000),
241            enable_summarization: false,
242            persistence_enabled: false,
243        }
244    }
245}
246
247impl Default for AgentConfig {
248    fn default() -> Self {
249        Self {
250            model_provider: "openai".to_string(),
251            model_name: "gpt-3.5-turbo".to_string(),
252            temperature: 0.7,
253            max_tokens: 2048,
254            max_tool_calls: 10,
255            enable_streaming: true,
256        }
257    }
258}
259
260impl Default for PerformanceConfig {
261    fn default() -> Self {
262        Self {
263            max_concurrency: 10,
264            request_timeout_seconds: 300,
265            connection_pool_size: 10,
266            cache_size: 1000,
267            cache_ttl_seconds: 3600,
268            rate_limit_per_second: None,
269        }
270    }
271}
272
273impl Default for MonitoringConfig {
274    fn default() -> Self {
275        Self {
276            enable_metrics: true,
277            enable_tracing: false,
278            log_level: "info".to_string(),
279            health_check_interval_seconds: 30,
280            metrics_endpoint: None,
281            tracing_endpoint: None,
282        }
283    }
284}
285
286impl Default for FeatureFlags {
287    fn default() -> Self {
288        Self {
289            enable_experimental: false,
290            enable_async_processing: true,
291            enable_auto_retry: true,
292            enable_validation: true,
293            enable_caching: true,
294        }
295    }
296}
297
298/// System metrics for monitoring
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct SystemMetrics {
301    /// System uptime in seconds
302    pub uptime_seconds: u64,
303
304    /// Request counts
305    pub request_counts: RequestCounts,
306
307    /// Performance metrics
308    pub performance: PerformanceMetrics,
309
310    /// Component health status
311    pub component_health: HashMap<String, HealthStatus>,
312
313    /// Resource usage
314    pub resource_usage: ResourceUsage,
315
316    /// Last updated timestamp
317    pub last_updated: chrono::DateTime<chrono::Utc>,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct RequestCounts {
322    pub total_requests: u64,
323    pub successful_requests: u64,
324    pub failed_requests: u64,
325    pub embedding_requests: u64,
326    pub retrieval_requests: u64,
327    pub agent_requests: u64,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct PerformanceMetrics {
332    pub average_response_time_ms: f64,
333    pub p95_response_time_ms: f64,
334    pub p99_response_time_ms: f64,
335    pub requests_per_second: f64,
336    pub error_rate: f64,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct ResourceUsage {
341    pub memory_usage_mb: f64,
342    pub cpu_usage_percent: f64,
343    pub storage_usage_mb: f64,
344    pub network_bytes_sent: u64,
345    pub network_bytes_received: u64,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349pub enum HealthStatus {
350    Healthy,
351    Degraded,
352    Unhealthy,
353    Unknown,
354}
355
356/// Main RRAG system orchestrator
357pub struct RragSystem {
358    /// System configuration
359    config: RragSystemConfig,
360
361    /// System services
362    services: SystemServices,
363
364    /// System metrics
365    metrics: Arc<RwLock<SystemMetrics>>,
366
367    /// System start time
368    start_time: Instant,
369
370    /// Component health checkers
371    health_checkers: HashMap<String, Box<dyn HealthChecker>>,
372}
373
374/// System services container
375pub struct SystemServices {
376    /// Embedding service
377    pub embedding: Option<Arc<EmbeddingService>>,
378
379    /// Storage service
380    pub storage: Option<Arc<StorageService>>,
381
382    /// Retrieval service
383    pub retrieval: Option<Arc<RetrievalService>>,
384
385    /// Memory service
386    pub memory: Option<Arc<MemoryService>>,
387
388    /// Agent instances
389    pub agents: HashMap<String, Arc<Agent>>,
390
391    /// Pipeline instances
392    pub pipelines: HashMap<String, Arc<Pipeline>>,
393}
394
395/// Health checker trait for components
396trait HealthChecker: Send + Sync {
397    fn check_health(
398        &self,
399    ) -> Box<dyn std::future::Future<Output = RragResult<HealthStatus>> + Send + '_>;
400}
401
402impl RragSystem {
403    /// Create new RRAG system with configuration
404    pub async fn new(config: RragSystemConfig) -> RragResult<Self> {
405        let services = SystemServices {
406            embedding: None,
407            storage: None,
408            retrieval: None,
409            memory: None,
410            agents: HashMap::new(),
411            pipelines: HashMap::new(),
412        };
413
414        let metrics = Arc::new(RwLock::new(SystemMetrics {
415            uptime_seconds: 0,
416            request_counts: RequestCounts {
417                total_requests: 0,
418                successful_requests: 0,
419                failed_requests: 0,
420                embedding_requests: 0,
421                retrieval_requests: 0,
422                agent_requests: 0,
423            },
424            performance: PerformanceMetrics {
425                average_response_time_ms: 0.0,
426                p95_response_time_ms: 0.0,
427                p99_response_time_ms: 0.0,
428                requests_per_second: 0.0,
429                error_rate: 0.0,
430            },
431            component_health: HashMap::new(),
432            resource_usage: ResourceUsage {
433                memory_usage_mb: 0.0,
434                cpu_usage_percent: 0.0,
435                storage_usage_mb: 0.0,
436                network_bytes_sent: 0,
437                network_bytes_received: 0,
438            },
439            last_updated: chrono::Utc::now(),
440        }));
441
442        Ok(Self {
443            config,
444            services,
445            metrics,
446            start_time: Instant::now(),
447            health_checkers: HashMap::new(),
448        })
449    }
450
451    /// Initialize system components
452    pub async fn initialize(&mut self) -> RragResult<()> {
453        // Initialize components based on configuration
454        // This is a simplified implementation - in production, would create actual service instances
455
456        info!("Initializing RRAG System: {}", self.config.name);
457        info!("Environment: {}", self.config.environment);
458        info!("Version: {}", self.config.version);
459
460        // Update metrics with initial health status
461        let mut metrics = self.metrics.write().await;
462        metrics
463            .component_health
464            .insert("system".to_string(), HealthStatus::Healthy);
465        metrics.last_updated = chrono::Utc::now();
466
467        Ok(())
468    }
469
470    /// Process a document through the system
471    pub async fn process_document(&self, document: Document) -> RragResult<ProcessingResult> {
472        let start_time = Instant::now();
473        let mut result = ProcessingResult::new();
474
475        // Update request metrics
476        {
477            let mut metrics = self.metrics.write().await;
478            metrics.request_counts.total_requests += 1;
479        }
480
481        // In a full implementation, this would:
482        // 1. Use the embedding service to generate embeddings
483        // 2. Store the document and embeddings in storage
484        // 3. Index for retrieval
485        // 4. Update metrics
486
487        result.processing_time_ms = start_time.elapsed().as_millis() as u64;
488        result.success = true;
489        result.metadata.insert(
490            "document_id".to_string(),
491            serde_json::Value::String(document.id.clone()),
492        );
493
494        // Update success metrics
495        {
496            let mut metrics = self.metrics.write().await;
497            metrics.request_counts.successful_requests += 1;
498        }
499
500        Ok(result)
501    }
502
503    /// Perform similarity search
504    pub async fn search(&self, query: String, _limit: Option<usize>) -> RragResult<SearchResponse> {
505        let start_time = Instant::now();
506
507        // Update request metrics
508        {
509            let mut metrics = self.metrics.write().await;
510            metrics.request_counts.total_requests += 1;
511            metrics.request_counts.retrieval_requests += 1;
512        }
513
514        // In a full implementation, this would:
515        // 1. Generate embedding for the query
516        // 2. Perform similarity search
517        // 3. Return ranked results
518
519        let response = SearchResponse {
520            query: query.clone(),
521            results: Vec::new(), // Would contain actual search results
522            processing_time_ms: start_time.elapsed().as_millis() as u64,
523            total_results: 0,
524            metadata: HashMap::new(),
525        };
526
527        // Update metrics
528        {
529            let mut metrics = self.metrics.write().await;
530            metrics.request_counts.successful_requests += 1;
531        }
532
533        Ok(response)
534    }
535
536    /// Chat with an agent
537    pub async fn chat(
538        &self,
539        agent_id: &str,
540        message: String,
541        conversation_id: Option<String>,
542    ) -> RragResult<ChatResponse> {
543        let start_time = Instant::now();
544
545        // Update request metrics
546        {
547            let mut metrics = self.metrics.write().await;
548            metrics.request_counts.total_requests += 1;
549            metrics.request_counts.agent_requests += 1;
550        }
551
552        // In a full implementation, this would:
553        // 1. Get the specified agent
554        // 2. Process the message
555        // 3. Generate response with tool calling if needed
556        // 4. Update conversation memory
557
558        let response = ChatResponse {
559            agent_id: agent_id.to_string(),
560            response: format!("Echo: {}", message), // Mock response
561            conversation_id: conversation_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
562            processing_time_ms: start_time.elapsed().as_millis() as u64,
563            tool_calls: Vec::new(),
564            metadata: HashMap::new(),
565        };
566
567        // Update metrics
568        {
569            let mut metrics = self.metrics.write().await;
570            metrics.request_counts.successful_requests += 1;
571        }
572
573        Ok(response)
574    }
575
576    /// Get system metrics
577    pub async fn get_metrics(&self) -> SystemMetrics {
578        let mut metrics = self.metrics.read().await.clone();
579        metrics.uptime_seconds = self.start_time.elapsed().as_secs();
580        metrics.last_updated = chrono::Utc::now();
581        metrics
582    }
583
584    /// Perform system health check
585    pub async fn health_check(&self) -> RragResult<HealthCheckResult> {
586        let mut result = HealthCheckResult {
587            overall_status: HealthStatus::Healthy,
588            component_status: HashMap::new(),
589            check_time: chrono::Utc::now(),
590            uptime_seconds: self.start_time.elapsed().as_secs(),
591            version: self.config.version.clone(),
592        };
593
594        // Check system components
595        result
596            .component_status
597            .insert("system".to_string(), HealthStatus::Healthy);
598
599        // In a full implementation, would check each service health
600        if let Some(_embedding_service) = &self.services.embedding {
601            result
602                .component_status
603                .insert("embedding".to_string(), HealthStatus::Healthy);
604        }
605
606        if let Some(_storage_service) = &self.services.storage {
607            result
608                .component_status
609                .insert("storage".to_string(), HealthStatus::Healthy);
610        }
611
612        if let Some(_retrieval_service) = &self.services.retrieval {
613            result
614                .component_status
615                .insert("retrieval".to_string(), HealthStatus::Healthy);
616        }
617
618        // Determine overall status
619        let has_unhealthy = result
620            .component_status
621            .values()
622            .any(|status| *status == HealthStatus::Unhealthy);
623        let has_degraded = result
624            .component_status
625            .values()
626            .any(|status| *status == HealthStatus::Degraded);
627
628        result.overall_status = if has_unhealthy {
629            HealthStatus::Unhealthy
630        } else if has_degraded {
631            HealthStatus::Degraded
632        } else {
633            HealthStatus::Healthy
634        };
635
636        Ok(result)
637    }
638
639    /// Shutdown the system gracefully
640    pub async fn shutdown(&self) -> RragResult<()> {
641        info!("Shutting down RRAG System gracefully...");
642
643        // In a full implementation, would:
644        // 1. Stop accepting new requests
645        // 2. Wait for ongoing requests to complete
646        // 3. Shutdown services in reverse dependency order
647        // 4. Persist any necessary state
648        // 5. Close connections and cleanup resources
649
650        info!("RRAG System shutdown complete");
651        Ok(())
652    }
653
654    /// Get system configuration
655    pub fn get_config(&self) -> &RragSystemConfig {
656        &self.config
657    }
658
659    /// Update system configuration (requires restart for some changes)
660    pub async fn update_config(&mut self, new_config: RragSystemConfig) -> RragResult<()> {
661        // Validate configuration
662        self.validate_config(&new_config)?;
663
664        // Update configuration
665        self.config = new_config;
666
667        info!("System configuration updated");
668        Ok(())
669    }
670
671    /// Validate system configuration
672    fn validate_config(&self, config: &RragSystemConfig) -> RragResult<()> {
673        // Basic validation
674        if config.name.is_empty() {
675            return Err(RragError::validation("name", "non-empty", "empty"));
676        }
677
678        if config.version.is_empty() {
679            return Err(RragError::validation("version", "non-empty", "empty"));
680        }
681
682        if config.performance.max_concurrency == 0 {
683            return Err(RragError::validation("max_concurrency", "> 0", "0"));
684        }
685
686        Ok(())
687    }
688}
689
690/// Processing result for document operations
691#[derive(Debug, Clone, Serialize, Deserialize)]
692pub struct ProcessingResult {
693    pub success: bool,
694    pub processing_time_ms: u64,
695    pub items_processed: usize,
696    pub errors: Vec<String>,
697    pub metadata: HashMap<String, serde_json::Value>,
698}
699
700impl ProcessingResult {
701    pub fn new() -> Self {
702        Self {
703            success: false,
704            processing_time_ms: 0,
705            items_processed: 0,
706            errors: Vec::new(),
707            metadata: HashMap::new(),
708        }
709    }
710}
711
712/// Search response
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct SearchResponse {
715    pub query: String,
716    pub results: Vec<SearchResult>,
717    pub processing_time_ms: u64,
718    pub total_results: usize,
719    pub metadata: HashMap<String, serde_json::Value>,
720}
721
722/// Chat response
723#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct ChatResponse {
725    pub agent_id: String,
726    pub response: String,
727    pub conversation_id: String,
728    pub processing_time_ms: u64,
729    pub tool_calls: Vec<String>, // Simplified
730    pub metadata: HashMap<String, serde_json::Value>,
731}
732
733/// Health check result
734#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct HealthCheckResult {
736    pub overall_status: HealthStatus,
737    pub component_status: HashMap<String, HealthStatus>,
738    pub check_time: chrono::DateTime<chrono::Utc>,
739    pub uptime_seconds: u64,
740    pub version: String,
741}
742
743/// System builder for easy setup
744pub struct RragSystemBuilder {
745    config: RragSystemConfig,
746}
747
748impl RragSystemBuilder {
749    pub fn new() -> Self {
750        Self {
751            config: RragSystemConfig::default(),
752        }
753    }
754
755    pub fn with_name(mut self, name: impl Into<String>) -> Self {
756        self.config.name = name.into();
757        self
758    }
759
760    pub fn with_environment(mut self, environment: impl Into<String>) -> Self {
761        self.config.environment = environment.into();
762        self
763    }
764
765    pub fn with_embedding_config(mut self, config: EmbeddingConfig) -> Self {
766        self.config.components.embedding = config;
767        self
768    }
769
770    pub fn with_storage_config(mut self, config: StorageConfig) -> Self {
771        self.config.components.storage = config;
772        self
773    }
774
775    pub fn with_performance_config(mut self, config: PerformanceConfig) -> Self {
776        self.config.performance = config;
777        self
778    }
779
780    pub fn enable_feature(mut self, feature: &str, enabled: bool) -> Self {
781        match feature {
782            "experimental" => self.config.features.enable_experimental = enabled,
783            "async_processing" => self.config.features.enable_async_processing = enabled,
784            "auto_retry" => self.config.features.enable_auto_retry = enabled,
785            "validation" => self.config.features.enable_validation = enabled,
786            "caching" => self.config.features.enable_caching = enabled,
787            _ => {} // Ignore unknown features
788        }
789        self
790    }
791
792    pub async fn build(self) -> RragResult<RragSystem> {
793        let mut system = RragSystem::new(self.config).await?;
794        system.initialize().await?;
795        Ok(system)
796    }
797}
798
799impl Default for RragSystemBuilder {
800    fn default() -> Self {
801        Self::new()
802    }
803}
804
805#[cfg(test)]
806mod tests {
807    use super::*;
808
809    #[tokio::test]
810    async fn test_system_creation() {
811        let config = RragSystemConfig::default();
812        let system = RragSystem::new(config).await.unwrap();
813
814        assert_eq!(system.config.name, "RRAG System");
815        assert_eq!(system.config.environment, "development");
816    }
817
818    #[tokio::test]
819    async fn test_system_builder() {
820        let system = RragSystemBuilder::new()
821            .with_name("Test System")
822            .with_environment("test")
823            .enable_feature("experimental", true)
824            .build()
825            .await
826            .unwrap();
827
828        assert_eq!(system.config.name, "Test System");
829        assert_eq!(system.config.environment, "test");
830        assert!(system.config.features.enable_experimental);
831    }
832
833    #[tokio::test]
834    async fn test_health_check() {
835        let system = RragSystemBuilder::new().build().await.unwrap();
836        let health = system.health_check().await.unwrap();
837
838        assert_eq!(health.overall_status, HealthStatus::Healthy);
839        assert!(health.component_status.contains_key("system"));
840        assert!(health.uptime_seconds >= 0);
841    }
842
843    #[tokio::test]
844    async fn test_metrics() {
845        let system = RragSystemBuilder::new().build().await.unwrap();
846        let metrics = system.get_metrics().await;
847
848        assert_eq!(metrics.request_counts.total_requests, 0);
849        assert!(metrics.uptime_seconds >= 0);
850    }
851
852    #[tokio::test]
853    async fn test_document_processing() {
854        let system = RragSystemBuilder::new().build().await.unwrap();
855        let doc = Document::new("Test document");
856
857        let result = system.process_document(doc).await.unwrap();
858
859        assert!(result.success);
860        assert!(result.processing_time_ms > 0);
861    }
862
863    #[tokio::test]
864    async fn test_search() {
865        let system = RragSystemBuilder::new().build().await.unwrap();
866
867        let response = system
868            .search("test query".to_string(), Some(5))
869            .await
870            .unwrap();
871
872        assert_eq!(response.query, "test query");
873        assert!(response.processing_time_ms > 0);
874    }
875
876    #[tokio::test]
877    async fn test_chat() {
878        let system = RragSystemBuilder::new().build().await.unwrap();
879
880        let response = system
881            .chat("test_agent", "Hello".to_string(), None)
882            .await
883            .unwrap();
884
885        assert_eq!(response.agent_id, "test_agent");
886        assert!(response.response.contains("Hello"));
887        assert!(response.processing_time_ms > 0);
888    }
889
890    #[test]
891    fn test_config_validation() {
892        let system = RragSystemBuilder::new().build();
893        // Test would be async in a real implementation
894
895        let mut invalid_config = RragSystemConfig::default();
896        invalid_config.name = "".to_string();
897
898        // In a real implementation, this would test the validation
899        assert!(invalid_config.name.is_empty());
900    }
901
902    #[test]
903    fn test_feature_flags() {
904        let mut config = RragSystemConfig::default();
905        config.features.enable_experimental = true;
906        config.features.enable_caching = false;
907
908        assert!(config.features.enable_experimental);
909        assert!(!config.features.enable_caching);
910    }
911}