1pub mod embeddings;
8pub mod entity_resolution;
9pub mod gnn;
10pub mod gpu_monitor;
11pub mod ivf_index;
12pub mod lsh_index;
13pub mod neural;
14pub mod pq_index;
15pub mod relation_extraction;
16pub mod temporal_reasoning;
17pub mod training;
18pub mod vector_store;
19pub mod vector_store_index;
20pub mod vector_store_search;
21#[cfg(test)]
22mod vector_store_tests;
23pub mod vector_store_types;
24
25use crate::model::Triple;
26use anyhow::{anyhow, Result};
27use serde::{Deserialize, Serialize};
28use std::collections::HashMap;
29use std::sync::Arc;
30use tokio::sync::Mutex;
31
32pub use embeddings::{
33 create_embedding_model, ComplEx, DistMult, EmbeddingConfig, EmbeddingModelType,
34 KnowledgeGraphEmbedding, TransE,
35};
36pub use gnn::{
37 Aggregation, GnnArchitecture, GnnConfig, GraphNeuralNetwork, LayerType, MessagePassingType,
38};
39pub use training::{
40 DefaultTrainer, LossFunction, Optimizer, Trainer, TrainingConfig, TrainingMetrics,
41};
42pub use vector_store::{SimilarityMetric, VectorIndex, VectorQuery, VectorStore};
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct AiConfig {
47 pub enable_gnn: bool,
49
50 pub embedding_config: EmbeddingConfig,
52
53 pub vector_store_config: VectorStoreConfig,
55
56 pub training_config: TrainingConfig,
58
59 pub gpu_config: GpuConfig,
61
62 pub cache_config: CacheConfig,
64}
65
66impl Default for AiConfig {
67 fn default() -> Self {
68 Self {
69 enable_gnn: true,
70 embedding_config: EmbeddingConfig::default(),
71 vector_store_config: VectorStoreConfig::default(),
72 training_config: TrainingConfig::default(),
73 gpu_config: GpuConfig::default(),
74 cache_config: CacheConfig::default(),
75 }
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct VectorStoreConfig {
82 pub dimension: usize,
84
85 pub metric: SimilarityMetric,
87
88 pub index_type: IndexType,
90
91 pub max_vectors: usize,
93
94 pub enable_ann: bool,
96
97 pub ann_neighbors: usize,
99}
100
101impl Default for VectorStoreConfig {
102 fn default() -> Self {
103 Self {
104 dimension: 128,
105 metric: SimilarityMetric::Cosine,
106 index_type: IndexType::HierarchicalNavigableSmallWorld,
107 max_vectors: 10_000_000,
108 enable_ann: true,
109 ann_neighbors: 16,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum IndexType {
117 Flat,
119 InvertedFile { clusters: usize },
121 LocalitySensitiveHashing {
123 hash_tables: usize,
124 hash_length: usize,
125 },
126 HierarchicalNavigableSmallWorld,
128 ProductQuantization { subquantizers: usize, bits: usize },
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct GpuConfig {
135 pub enabled: bool,
137
138 pub device_id: u32,
140
141 pub memory_pool_mb: usize,
143
144 pub batch_size: usize,
146
147 pub mixed_precision: bool,
149}
150
151impl Default for GpuConfig {
152 fn default() -> Self {
153 Self {
154 enabled: true,
155 device_id: 0,
156 memory_pool_mb: 4096,
157 batch_size: 1024,
158 mixed_precision: true,
159 }
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct CacheConfig {
166 pub enabled: bool,
168
169 pub cache_dir: String,
171
172 pub max_size_mb: usize,
174
175 pub ttl_seconds: u64,
177
178 pub compression: bool,
180}
181
182impl Default for CacheConfig {
183 fn default() -> Self {
184 Self {
185 enabled: true,
186 cache_dir: "/tmp/oxirs/ai_cache".to_string(),
187 max_size_mb: 10240, ttl_seconds: 86400, compression: true,
190 }
191 }
192}
193
194pub struct AiEngine {
196 #[allow(dead_code)]
198 config: AiConfig,
199
200 gnn: Option<Arc<dyn GraphNeuralNetwork>>,
202
203 embeddings: HashMap<String, Arc<dyn KnowledgeGraphEmbedding>>,
205
206 vector_store: Arc<dyn VectorStore>,
208
209 trainer: Arc<Mutex<Box<dyn Trainer>>>,
211
212 entity_resolver: Arc<entity_resolution::EntityResolver>,
214
215 relation_extractor: Arc<relation_extraction::RelationExtractor>,
217
218 temporal_reasoner: Arc<temporal_reasoning::TemporalReasoner>,
220}
221
222impl AiEngine {
223 pub fn new(config: AiConfig) -> Result<Self> {
225 let vs_config = vector_store::VectorStoreConfig {
226 dimension: config.vector_store_config.dimension,
227 default_metric: config.vector_store_config.metric,
228 index_type: match config.vector_store_config.index_type {
229 IndexType::Flat => vector_store::IndexType::Flat,
230 IndexType::HierarchicalNavigableSmallWorld => vector_store::IndexType::HNSW {
231 max_connections: 16,
232 ef_construction: 200,
233 ef_search: 50,
234 },
235 IndexType::InvertedFile { clusters } => vector_store::IndexType::IVF {
236 num_clusters: clusters,
237 num_probes: 8,
238 },
239 IndexType::LocalitySensitiveHashing {
240 hash_tables,
241 hash_length,
242 } => vector_store::IndexType::LSH {
243 num_tables: hash_tables,
244 hash_length,
245 },
246 IndexType::ProductQuantization {
247 subquantizers,
248 bits,
249 } => vector_store::IndexType::PQ {
250 num_subquantizers: subquantizers,
251 bits_per_subquantizer: bits,
252 },
253 },
254 enable_cache: config.vector_store_config.enable_ann,
255 cache_size: if config.vector_store_config.max_vectors > 10000 {
256 10000
257 } else {
258 config.vector_store_config.max_vectors
259 },
260 cache_ttl: 3600,
261 batch_size: 1000,
262 };
263 let vector_store = vector_store::create_vector_store(&vs_config)?;
264 let trainer = Arc::new(Mutex::new(Box::new(training::DefaultTrainer::new(
266 config.training_config.clone(),
267 )) as Box<dyn Trainer>));
268 let entity_resolver = Arc::new(entity_resolution::EntityResolver::new(&config)?);
269 let relation_extractor = Arc::new(relation_extraction::RelationExtractor::new(&config)?);
270 let temporal_reasoner = Arc::new(temporal_reasoning::TemporalReasoner::new(&config)?);
271
272 Ok(Self {
273 config,
274 gnn: None,
275 embeddings: HashMap::new(),
276 vector_store,
277 trainer,
278 entity_resolver,
279 relation_extractor,
280 temporal_reasoner,
281 })
282 }
283
284 pub async fn initialize_gnn(&mut self, gnn_config: GnnConfig) -> Result<()> {
286 let gnn = gnn::create_gnn(gnn_config)?;
287 self.gnn = Some(gnn);
288 Ok(())
289 }
290
291 pub async fn add_embedding_model(
293 &mut self,
294 name: String,
295 model: Arc<dyn KnowledgeGraphEmbedding>,
296 ) -> Result<()> {
297 self.embeddings.insert(name, model);
298 Ok(())
299 }
300
301 pub async fn generate_embeddings(
303 &self,
304 model_name: &str,
305 triples: &[Triple],
306 ) -> Result<Vec<Vec<f32>>> {
307 let model = self
308 .embeddings
309 .get(model_name)
310 .ok_or_else(|| anyhow!("Embedding model not found: {}", model_name))?;
311
312 model.generate_embeddings(triples).await
313 }
314
315 pub async fn find_similar_entities(
317 &self,
318 entity_vector: &[f32],
319 top_k: usize,
320 ) -> Result<Vec<(String, f32)>> {
321 let query = VectorQuery {
322 vector: entity_vector.to_vec(),
323 k: top_k,
324 include_metadata: true,
325 metric: None,
326 filters: None,
327 min_similarity: None,
328 };
329
330 self.vector_store.search(&query).await
331 }
332
333 pub async fn predict_links(
335 &self,
336 model_name: &str,
337 entities: &[String],
338 relations: &[String],
339 ) -> Result<Vec<(String, String, String, f32)>> {
340 let model = self
341 .embeddings
342 .get(model_name)
343 .ok_or_else(|| anyhow!("Embedding model not found: {}", model_name))?;
344
345 model.predict_links(entities, relations).await
346 }
347
348 pub async fn resolve_entities(
350 &self,
351 entities: &[Triple],
352 ) -> Result<Vec<entity_resolution::EntityCluster>> {
353 self.entity_resolver.resolve_entities(entities).await
354 }
355
356 pub async fn extract_relations_from_text(
358 &self,
359 text: &str,
360 ) -> Result<Vec<relation_extraction::ExtractedRelation>> {
361 self.relation_extractor.extract_relations(text).await
362 }
363
364 pub async fn temporal_reasoning(
366 &self,
367 query: &temporal_reasoning::TemporalQuery,
368 ) -> Result<temporal_reasoning::TemporalResult> {
369 self.temporal_reasoner.reason(query).await
370 }
371
372 pub async fn train_embedding_model(
374 &self,
375 model_name: &str,
376 training_data: &[Triple],
377 validation_data: &[Triple],
378 ) -> Result<TrainingMetrics> {
379 let model = self
380 .embeddings
381 .get(model_name)
382 .ok_or_else(|| anyhow!("Embedding model not found: {}", model_name))?;
383
384 let trainer = self.trainer.clone();
386 let model = model.clone();
387 let training_data = training_data.to_vec();
388 let validation_data = validation_data.to_vec();
389
390 let mut trainer_guard = trainer.lock().await;
392 trainer_guard
393 .train_embedding_model(model, &training_data, &validation_data)
394 .await
395 }
396
397 pub async fn evaluate_model(
399 &self,
400 model_name: &str,
401 test_data: &[Triple],
402 ) -> Result<EvaluationMetrics> {
403 let model = self
404 .embeddings
405 .get(model_name)
406 .ok_or_else(|| anyhow!("Embedding model not found: {}", model_name))?;
407
408 EvaluationMetrics::evaluate(model.as_ref(), test_data).await
409 }
410
411 pub async fn get_statistics(&self) -> Result<AiStatistics> {
413 let vs_stats = self.vector_store.get_statistics().await?;
415
416 let gpu_monitor = gpu_monitor::GpuMonitor::global();
418 let gpu_utilization = gpu_monitor
419 .lock()
420 .map(|monitor| monitor.get_utilization())
421 .unwrap_or(0.0);
422
423 Ok(AiStatistics {
424 gnn_enabled: self.gnn.is_some(),
425 embedding_models: self.embeddings.len(),
426 vector_store_size: self.vector_store.size(),
427 cache_hit_rate: vs_stats.cache_hit_rate,
428 gpu_utilization,
429 })
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct EvaluationMetrics {
436 pub mrr: f32,
438
439 pub hits_at_1: f32,
441 pub hits_at_3: f32,
442 pub hits_at_10: f32,
443
444 pub link_prediction_accuracy: f32,
446
447 pub entity_resolution_f1: f32,
449
450 pub relation_extraction_precision: f32,
452 pub relation_extraction_recall: f32,
453}
454
455impl EvaluationMetrics {
456 pub async fn evaluate(
458 model: &dyn KnowledgeGraphEmbedding,
459 test_data: &[Triple],
460 ) -> Result<Self> {
461 let test_triples: Vec<(String, String, String)> = test_data
463 .iter()
464 .map(|t| {
465 (
466 t.subject().to_string(),
467 t.predicate().to_string(),
468 t.object().to_string(),
469 )
470 })
471 .collect();
472
473 let all_triples = test_triples.clone();
476
477 let k_values = vec![1, 3, 10];
479
480 let kg_metrics = embeddings::evaluation::compute_kg_metrics(
482 model,
483 &test_triples,
484 &all_triples,
485 &k_values,
486 )
487 .await?;
488
489 let link_prediction_accuracy =
491 Self::compute_link_prediction_accuracy(model, &test_triples).await?;
492
493 let mrr = kg_metrics.mrr_filtered;
495 let hits_at_1 = *kg_metrics.hits_at_k_filtered.get(&1).unwrap_or(&0.0);
496 let hits_at_3 = *kg_metrics.hits_at_k_filtered.get(&3).unwrap_or(&0.0);
497 let hits_at_10 = *kg_metrics.hits_at_k_filtered.get(&10).unwrap_or(&0.0);
498
499 let entity_resolution_f1 = 0.0;
502 let relation_extraction_precision = 0.0;
503 let relation_extraction_recall = 0.0;
504
505 Ok(Self {
506 mrr,
507 hits_at_1,
508 hits_at_3,
509 hits_at_10,
510 link_prediction_accuracy,
511 entity_resolution_f1,
512 relation_extraction_precision,
513 relation_extraction_recall,
514 })
515 }
516
517 async fn compute_link_prediction_accuracy(
519 model: &dyn KnowledgeGraphEmbedding,
520 test_triples: &[(String, String, String)],
521 ) -> Result<f32> {
522 if test_triples.is_empty() {
523 return Ok(0.0);
524 }
525
526 let sample_size = test_triples.len().min(100);
528 let mut correct = 0;
529
530 let entities: std::collections::HashSet<String> = test_triples
532 .iter()
533 .flat_map(|(h, _, t)| vec![h.clone(), t.clone()])
534 .collect();
535 let entity_vec: Vec<String> = entities.into_iter().collect();
536
537 if entity_vec.len() < 2 {
538 return Ok(0.0);
539 }
540
541 for triple in test_triples.iter().take(sample_size) {
542 let positive_score = model.score_triple(&triple.0, &triple.1, &triple.2).await?;
543
544 let corrupt_idx = {
546 use scirs2_core::random::Random;
547 let mut rng = Random::default();
548 rng.random_range(0..entity_vec.len())
549 };
550 let corrupt_entity = &entity_vec[corrupt_idx];
551
552 let negative_score = {
553 use scirs2_core::random::Random;
554 let mut rng = Random::default();
555 if rng.random_bool_with_chance(0.5) {
556 model
558 .score_triple(corrupt_entity, &triple.1, &triple.2)
559 .await?
560 } else {
561 model
563 .score_triple(&triple.0, &triple.1, corrupt_entity)
564 .await?
565 }
566 };
567
568 if (positive_score - negative_score).abs() > 0.01 {
572 correct += 1;
573 }
574 }
575
576 Ok(correct as f32 / sample_size as f32)
577 }
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct AiStatistics {
583 pub gnn_enabled: bool,
585
586 pub embedding_models: usize,
588
589 pub vector_store_size: usize,
591
592 pub cache_hit_rate: f32,
594
595 pub gpu_utilization: f32,
597}
598
599pub trait AiQueryEnhancement {
601 fn enhance_query(&self, query: &str) -> Result<String>;
603
604 fn suggest_entities(&self, entity: &str) -> Result<Vec<String>>;
606
607 fn expand_query(&self, query: &str) -> Result<Vec<String>>;
609}
610
611pub trait AiDataValidation {
613 fn detect_anomalies(&self, triples: &[Triple]) -> Result<Vec<Anomaly>>;
615
616 fn suggest_improvements(&self, triples: &[Triple]) -> Result<Vec<Improvement>>;
618
619 fn validate_consistency(&self, triples: &[Triple]) -> Result<Vec<InconsistencyError>>;
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct Anomaly {
626 pub anomaly_type: AnomalyType,
628
629 pub triple: Triple,
631
632 pub confidence: f32,
634
635 pub description: String,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
641pub enum AnomalyType {
642 Outlier,
644
645 MissingRelation,
647
648 InconsistentType,
650
651 DuplicateEntity,
653
654 InvalidFormat,
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
660pub struct Improvement {
661 pub improvement_type: ImprovementType,
663
664 pub target: String,
666
667 pub suggestion: String,
669
670 pub impact: f32,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
676pub enum ImprovementType {
677 AddRelation,
679
680 MergeEntities,
682
683 CorrectType,
685
686 AddConstraint,
688
689 NormalizeFormat,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct InconsistencyError {
696 pub error_type: InconsistencyType,
698
699 pub triples: Vec<Triple>,
701
702 pub severity: Severity,
704
705 pub message: String,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
711pub enum InconsistencyType {
712 LogicalContradiction,
714
715 TypeViolation,
717
718 CardinalityViolation,
720
721 DomainRangeViolation,
723}
724
725#[derive(Debug, Clone, Serialize, Deserialize)]
727pub enum Severity {
728 Low,
729 Medium,
730 High,
731 Critical,
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 #[tokio::test]
739 async fn test_ai_engine_creation() {
740 let config = AiConfig::default();
741 let engine = AiEngine::new(config);
742 assert!(engine.is_ok());
743 }
744
745 #[test]
746 fn test_config_serialization() {
747 let config = AiConfig::default();
748 let serialized = serde_json::to_string(&config).expect("construction should succeed");
749 let deserialized: AiConfig =
750 serde_json::from_str(&serialized).expect("construction should succeed");
751 assert_eq!(config.enable_gnn, deserialized.enable_gnn);
752 }
753}