1use crate::{EmbeddingModel, Vector};
4use anyhow::{anyhow, Result};
5use std::collections::HashMap;
6use tracing::{debug, info, warn};
7
8pub struct VectorStoreBridge {
10 entity_mappings: HashMap<String, String>,
11 relation_mappings: HashMap<String, String>,
12 prefix_config: PrefixConfig,
13}
14
15#[derive(Debug, Clone)]
17pub struct PrefixConfig {
18 pub entity_prefix: String,
19 pub relation_prefix: String,
20 pub use_namespaces: bool,
21}
22
23impl Default for PrefixConfig {
24 fn default() -> Self {
25 Self {
26 entity_prefix: "kg:entity:".to_string(),
27 relation_prefix: "kg:relation:".to_string(),
28 use_namespaces: true,
29 }
30 }
31}
32
33impl VectorStoreBridge {
34 pub fn new() -> Self {
36 Self {
37 entity_mappings: HashMap::new(),
38 relation_mappings: HashMap::new(),
39 prefix_config: PrefixConfig::default(),
40 }
41 }
42
43 pub fn with_prefix_config(prefix_config: PrefixConfig) -> Self {
45 Self {
46 entity_mappings: HashMap::new(),
47 relation_mappings: HashMap::new(),
48 prefix_config,
49 }
50 }
51
52 pub fn sync_model_embeddings(&mut self, model: &dyn EmbeddingModel) -> Result<SyncStats> {
54 let start_time = std::time::Instant::now();
55 let mut sync_stats = SyncStats::default();
56
57 info!("Starting embedding synchronization to vector store");
58
59 let entities = model.get_entities();
61 for entity in &entities {
62 match model.get_entity_embedding(entity) {
63 Ok(_embedding) => {
64 let uri = self.generate_entity_uri(entity);
65 self.entity_mappings.insert(entity.clone(), uri);
66 sync_stats.entities_synced += 1;
67 }
68 Err(e) => {
69 warn!("Failed to get embedding for entity {}: {}", entity, e);
70 sync_stats.errors.push(format!("Entity {entity}: {e}"));
71 }
72 }
73 }
74
75 let relations = model.get_relations();
77 for relation in &relations {
78 match model.get_relation_embedding(relation) {
79 Ok(_embedding) => {
80 let uri = self.generate_relation_uri(relation);
81 self.relation_mappings.insert(relation.clone(), uri);
82 sync_stats.relations_synced += 1;
83 }
84 Err(e) => {
85 warn!("Failed to get embedding for relation {}: {}", relation, e);
86 sync_stats.errors.push(format!("Relation {relation}: {e}"));
87 }
88 }
89 }
90
91 sync_stats.sync_duration = start_time.elapsed();
92 info!(
93 "Embedding sync completed: {} entities, {} relations, {} errors",
94 sync_stats.entities_synced,
95 sync_stats.relations_synced,
96 sync_stats.errors.len()
97 );
98
99 Ok(sync_stats)
100 }
101
102 pub fn find_similar_entities(&self, entity: &str, _k: usize) -> Result<Vec<(String, f32)>> {
104 if let Some(_uri) = self.entity_mappings.get(entity) {
105 debug!("Searching for entities similar to: {}", entity);
108 Ok(vec![])
109 } else {
110 Err(anyhow!("Entity not found in mappings: {}", entity))
111 }
112 }
113
114 pub fn find_similar_relations(&self, relation: &str, _k: usize) -> Result<Vec<(String, f32)>> {
116 if let Some(_uri) = self.relation_mappings.get(relation) {
117 debug!("Searching for relations similar to: {}", relation);
118 Ok(vec![])
119 } else {
120 Err(anyhow!("Relation not found in mappings: {}", relation))
121 }
122 }
123
124 fn generate_entity_uri(&self, entity: &str) -> String {
126 if self.prefix_config.use_namespaces {
127 format!("{}{}", self.prefix_config.entity_prefix, entity)
128 } else {
129 entity.to_string()
130 }
131 }
132
133 fn generate_relation_uri(&self, relation: &str) -> String {
135 if self.prefix_config.use_namespaces {
136 format!("{}{}", self.prefix_config.relation_prefix, relation)
137 } else {
138 relation.to_string()
139 }
140 }
141
142 pub fn get_sync_info(&self) -> SyncInfo {
144 SyncInfo {
145 entities_mapped: self.entity_mappings.len(),
146 relations_mapped: self.relation_mappings.len(),
147 vector_store_stats: None,
148 }
149 }
150
151 pub fn clear_mappings(&mut self) {
153 self.entity_mappings.clear();
154 self.relation_mappings.clear();
155 info!("Cleared all entity and relation mappings");
156 }
157}
158
159impl Default for VectorStoreBridge {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[derive(Debug, Clone, Default)]
167pub struct SyncStats {
168 pub entities_synced: usize,
169 pub relations_synced: usize,
170 pub errors: Vec<String>,
171 pub sync_duration: std::time::Duration,
172}
173
174#[derive(Debug, Clone)]
176pub struct SyncInfo {
177 pub entities_mapped: usize,
178 pub relations_mapped: usize,
179 pub vector_store_stats: Option<(usize, usize)>,
180}
181
182pub struct ChatIntegration {
184 model: Box<dyn EmbeddingModel>,
185 context_window: usize,
186 similarity_threshold: f32,
187 personalization: PersonalizationEngine,
188 multilingual: MultilingualSupport,
189}
190
191impl ChatIntegration {
192 pub fn new(model: Box<dyn EmbeddingModel>) -> Self {
194 Self {
195 model,
196 context_window: 10,
197 similarity_threshold: 0.7,
198 personalization: PersonalizationEngine::new(),
199 multilingual: MultilingualSupport::new(),
200 }
201 }
202
203 pub fn with_context_window(mut self, window_size: usize) -> Self {
205 self.context_window = window_size;
206 self
207 }
208
209 pub fn with_similarity_threshold(mut self, threshold: f32) -> Self {
211 self.similarity_threshold = threshold;
212 self
213 }
214
215 pub fn extract_relevant_entities(&self, query: &str) -> Result<Vec<String>> {
217 let entities = self.model.get_entities();
220 let mut relevant = Vec::new();
221
222 for entity in entities {
223 if query.to_lowercase().contains(&entity.to_lowercase()) {
225 relevant.push(entity);
226 }
227 }
228
229 Ok(relevant)
230 }
231
232 pub fn generate_context_embedding(&self, messages: &[String]) -> Result<Vector> {
234 if messages.is_empty() {
235 return Err(anyhow!("No messages provided"));
236 }
237
238 let _recent_messages: Vec<&String> =
240 messages.iter().rev().take(self.context_window).collect();
241
242 let dummy_values = vec![0.0; 100]; Ok(Vector::new(
246 dummy_values.into_iter().map(|x| x as f32).collect(),
247 ))
248 }
249
250 pub async fn generate_personalized_embedding(
252 &mut self,
253 user_id: &str,
254 query: &str,
255 conversation_history: &[String],
256 ) -> Result<Vector> {
257 let user_profile = self.personalization.get_user_profile(user_id)?.clone();
259
260 let embeddings = self.model.encode(&[query.to_string()]).await?;
262 let base_embedding = Vector::new(embeddings[0].clone());
263 let personalized_embedding = self.personalization.apply_user_preferences(
264 &base_embedding,
265 &user_profile,
266 conversation_history,
267 )?;
268
269 Ok(personalized_embedding)
270 }
271
272 pub fn update_user_profile(
274 &mut self,
275 user_id: &str,
276 query: &str,
277 response_feedback: Option<f32>,
278 interaction_type: InteractionType,
279 ) -> Result<()> {
280 self.personalization.update_user_profile(
281 user_id,
282 query,
283 response_feedback,
284 interaction_type,
285 )
286 }
287
288 pub async fn translate_query(
290 &self,
291 query: &str,
292 source_lang: &str,
293 target_lang: &str,
294 ) -> Result<String> {
295 self.multilingual
296 .translate_text(query, source_lang, target_lang)
297 .await
298 }
299
300 pub async fn detect_language(&self, text: &str) -> Result<LanguageDetection> {
302 self.multilingual.detect_language(text).await
303 }
304
305 pub async fn generate_cross_lingual_embedding(
307 &self,
308 text: &str,
309 source_lang: &str,
310 target_lang: &str,
311 ) -> Result<Vector> {
312 self.multilingual
313 .generate_cross_lingual_embedding(text, source_lang, target_lang, &*self.model)
314 .await
315 }
316
317 pub async fn align_entities_across_languages(
319 &self,
320 entity: &str,
321 source_lang: &str,
322 target_langs: &[String],
323 ) -> Result<HashMap<String, String>> {
324 self.multilingual
325 .align_entities(entity, source_lang, target_langs)
326 .await
327 }
328}
329
330pub struct SparqlIntegration {
332 #[allow(dead_code)]
333 model: Box<dyn EmbeddingModel>,
334 #[allow(dead_code)]
335 similarity_boost: f32,
336}
337
338impl SparqlIntegration {
339 pub fn new(model: Box<dyn EmbeddingModel>) -> Self {
341 Self {
342 model,
343 similarity_boost: 0.1,
344 }
345 }
346
347 pub fn enhance_query(&self, sparql_query: &str) -> Result<EnhancedQuery> {
349 let entities = self.extract_entities_from_sparql(sparql_query)?;
351 let relations = self.extract_relations_from_sparql(sparql_query)?;
352
353 let mut suggestions = Vec::new();
354
355 for entity in &entities {
357 suggestions.push(QuerySuggestion {
359 suggestion_type: SuggestionType::SimilarEntity,
360 original: entity.clone(),
361 suggested: format!("similar_to_{entity}"),
362 confidence: 0.8,
363 });
364 }
365
366 for relation in &relations {
368 suggestions.push(QuerySuggestion {
369 suggestion_type: SuggestionType::SimilarRelation,
370 original: relation.clone(),
371 suggested: format!("similar_to_{relation}"),
372 confidence: 0.7,
373 });
374 }
375
376 Ok(EnhancedQuery {
377 original_query: sparql_query.to_string(),
378 entities_found: entities,
379 relations_found: relations,
380 suggestions,
381 })
382 }
383
384 fn extract_entities_from_sparql(&self, query: &str) -> Result<Vec<String>> {
386 let mut entities = Vec::new();
389
390 for line in query.lines() {
391 if line.contains("http://") {
392 if let Some(start) = line.find("http://") {
394 if let Some(end) = line[start..].find(' ') {
395 let uri = &line[start..start + end];
396 entities.push(uri.to_string());
397 }
398 }
399 }
400 }
401
402 Ok(entities)
403 }
404
405 fn extract_relations_from_sparql(&self, query: &str) -> Result<Vec<String>> {
407 let mut relations = Vec::new();
409
410 for line in query.lines() {
411 if line.contains("?") && line.contains("http://") {
412 if let Some(start) = line.find('<') {
414 if let Some(end) = line.find('>') {
415 let relation = &line[start + 1..end];
416 relations.push(relation.to_string());
417 }
418 }
419 }
420 }
421
422 Ok(relations)
423 }
424}
425
426#[derive(Debug, Clone)]
428pub struct EnhancedQuery {
429 pub original_query: String,
430 pub entities_found: Vec<String>,
431 pub relations_found: Vec<String>,
432 pub suggestions: Vec<QuerySuggestion>,
433}
434
435#[derive(Debug, Clone)]
437pub struct QuerySuggestion {
438 pub suggestion_type: SuggestionType,
439 pub original: String,
440 pub suggested: String,
441 pub confidence: f32,
442}
443
444#[derive(Debug, Clone)]
446pub enum SuggestionType {
447 SimilarEntity,
448 SimilarRelation,
449 AlternativePattern,
450 ExpansionSuggestion,
451}
452
453pub struct PersonalizationEngine {
455 user_profiles: HashMap<String, UserProfile>,
456 interaction_history: HashMap<String, Vec<UserInteraction>>,
457 preference_weights: PreferenceWeights,
458}
459
460impl Default for PersonalizationEngine {
461 fn default() -> Self {
462 Self::new()
463 }
464}
465
466impl PersonalizationEngine {
467 pub fn new() -> Self {
468 Self {
469 user_profiles: HashMap::new(),
470 interaction_history: HashMap::new(),
471 preference_weights: PreferenceWeights::default(),
472 }
473 }
474
475 pub fn get_user_profile(&mut self, user_id: &str) -> Result<&UserProfile> {
477 if !self.user_profiles.contains_key(user_id) {
478 let profile = UserProfile::new(user_id.to_string());
479 self.user_profiles.insert(user_id.to_string(), profile);
480 }
481
482 self.user_profiles
483 .get(user_id)
484 .ok_or_else(|| anyhow!("Failed to get user profile for {}", user_id))
485 }
486
487 pub fn apply_user_preferences(
489 &self,
490 base_embedding: &Vector,
491 user_profile: &UserProfile,
492 conversation_history: &[String],
493 ) -> Result<Vector> {
494 let mut personalized = base_embedding.clone();
495
496 for (domain, weight) in &user_profile.domain_preferences {
498 if conversation_history.iter().any(|msg| msg.contains(domain)) {
499 for i in 0..personalized.values.len() {
501 personalized.values[i] *= 1.0 + (weight * self.preference_weights.domain_boost);
502 }
503 }
504 }
505
506 let recent_interactions = self.get_recent_interactions(&user_profile.user_id, 10);
508 if !recent_interactions.is_empty() {
509 let avg_sentiment = recent_interactions
510 .iter()
511 .map(|i| i.sentiment_score.unwrap_or(0.0))
512 .sum::<f32>()
513 / recent_interactions.len() as f32;
514
515 for i in 0..personalized.values.len() {
517 personalized.values[i] *=
518 1.0 + (avg_sentiment * self.preference_weights.sentiment_influence);
519 }
520 }
521
522 Ok(personalized)
523 }
524
525 pub fn update_user_profile(
527 &mut self,
528 user_id: &str,
529 query: &str,
530 response_feedback: Option<f32>,
531 interaction_type: InteractionType,
532 ) -> Result<()> {
533 let interaction = UserInteraction {
534 timestamp: chrono::Utc::now(),
535 query: query.to_string(),
536 interaction_type,
537 response_feedback,
538 sentiment_score: self.analyze_query_sentiment(query),
539 };
540
541 self.interaction_history
543 .entry(user_id.to_string())
544 .or_default()
545 .push(interaction.clone());
546
547 if let Some(profile) = self.user_profiles.get_mut(user_id) {
549 profile.update_from_interaction(&interaction);
550 }
551
552 Ok(())
553 }
554
555 fn get_recent_interactions(&self, user_id: &str, limit: usize) -> Vec<&UserInteraction> {
557 self.interaction_history
558 .get(user_id)
559 .map(|history| history.iter().rev().take(limit).collect())
560 .unwrap_or_default()
561 }
562
563 fn analyze_query_sentiment(&self, query: &str) -> Option<f32> {
565 let positive_words = ["good", "great", "excellent", "amazing", "wonderful"];
566 let negative_words = ["bad", "terrible", "awful", "horrible", "disappointing"];
567
568 let query_lower = query.to_lowercase();
569 let positive_count = positive_words
570 .iter()
571 .filter(|&&word| query_lower.contains(word))
572 .count();
573 let negative_count = negative_words
574 .iter()
575 .filter(|&&word| query_lower.contains(word))
576 .count();
577
578 if positive_count + negative_count == 0 {
579 return None;
580 }
581
582 let sentiment = (positive_count as f32 - negative_count as f32)
583 / (positive_count + negative_count) as f32;
584 Some(sentiment)
585 }
586}
587
588#[derive(Debug, Clone)]
590pub struct UserProfile {
591 pub user_id: String,
592 pub domain_preferences: HashMap<String, f32>,
593 pub entity_preferences: HashMap<String, f32>,
594 pub interaction_patterns: InteractionPatterns,
595 pub language_preferences: Vec<String>,
596 pub created_at: chrono::DateTime<chrono::Utc>,
597 pub last_updated: chrono::DateTime<chrono::Utc>,
598}
599
600impl UserProfile {
601 pub fn new(user_id: String) -> Self {
602 let now = chrono::Utc::now();
603 Self {
604 user_id,
605 domain_preferences: HashMap::new(),
606 entity_preferences: HashMap::new(),
607 interaction_patterns: InteractionPatterns::default(),
608 language_preferences: vec!["en".to_string()],
609 created_at: now,
610 last_updated: now,
611 }
612 }
613
614 pub fn update_from_interaction(&mut self, interaction: &UserInteraction) {
616 self.last_updated = chrono::Utc::now();
617
618 self.interaction_patterns.total_interactions += 1;
620 match interaction.interaction_type {
621 InteractionType::Query => self.interaction_patterns.query_count += 1,
622 InteractionType::Feedback => self.interaction_patterns.feedback_count += 1,
623 InteractionType::EntityLookup => self.interaction_patterns.entity_lookup_count += 1,
624 }
625
626 if let Some(sentiment) = interaction.sentiment_score {
628 let current_avg = self.interaction_patterns.average_sentiment;
629 let total = self.interaction_patterns.total_interactions as f32;
630 self.interaction_patterns.average_sentiment =
631 (current_avg * (total - 1.0) + sentiment) / total;
632 }
633
634 self.extract_domain_preferences(&interaction.query);
636 }
637
638 fn extract_domain_preferences(&mut self, query: &str) {
640 let domains = [
641 "science",
642 "technology",
643 "medicine",
644 "business",
645 "education",
646 "sports",
647 "entertainment",
648 "politics",
649 "history",
650 "art",
651 ];
652
653 for domain in &domains {
654 if query.to_lowercase().contains(domain) {
655 #[allow(clippy::unnecessary_to_owned)]
656 let current = self.domain_preferences.get(*domain).copied().unwrap_or(0.0);
657 self.domain_preferences
658 .insert(domain.to_string(), current + 0.1);
659 }
660 }
661 }
662}
663
664#[derive(Debug, Clone, Default)]
666pub struct InteractionPatterns {
667 pub total_interactions: u32,
668 pub query_count: u32,
669 pub feedback_count: u32,
670 pub entity_lookup_count: u32,
671 pub average_sentiment: f32,
672 pub preferred_response_length: Option<usize>,
673}
674
675#[derive(Debug, Clone)]
677pub enum InteractionType {
678 Query,
679 Feedback,
680 EntityLookup,
681}
682
683#[derive(Debug, Clone)]
685pub struct UserInteraction {
686 pub timestamp: chrono::DateTime<chrono::Utc>,
687 pub query: String,
688 pub interaction_type: InteractionType,
689 pub response_feedback: Option<f32>,
690 pub sentiment_score: Option<f32>,
691}
692
693#[derive(Debug, Clone)]
695pub struct PreferenceWeights {
696 pub domain_boost: f32,
697 pub entity_boost: f32,
698 pub sentiment_influence: f32,
699 pub recency_decay: f32,
700}
701
702impl Default for PreferenceWeights {
703 fn default() -> Self {
704 Self {
705 domain_boost: 0.1,
706 entity_boost: 0.15,
707 sentiment_influence: 0.05,
708 recency_decay: 0.95,
709 }
710 }
711}
712
713pub struct MultilingualSupport {
715 supported_languages: Vec<String>,
716 translation_cache: HashMap<String, String>,
717 language_models: HashMap<String, LanguageModel>,
718}
719
720impl Default for MultilingualSupport {
721 fn default() -> Self {
722 Self::new()
723 }
724}
725
726impl MultilingualSupport {
727 pub fn new() -> Self {
728 Self {
729 supported_languages: vec![
730 "en".to_string(),
731 "es".to_string(),
732 "fr".to_string(),
733 "de".to_string(),
734 "it".to_string(),
735 "pt".to_string(),
736 "zh".to_string(),
737 "ja".to_string(),
738 "ko".to_string(),
739 "ar".to_string(),
740 "hi".to_string(),
741 "ru".to_string(),
742 ],
743 translation_cache: HashMap::new(),
744 language_models: HashMap::new(),
745 }
746 }
747
748 pub async fn translate_text(
750 &self,
751 text: &str,
752 source_lang: &str,
753 target_lang: &str,
754 ) -> Result<String> {
755 if source_lang == target_lang {
756 return Ok(text.to_string());
757 }
758
759 let cache_key = format!("{source_lang}:{target_lang}:{text}");
760 if let Some(cached) = self.translation_cache.get(&cache_key) {
761 return Ok(cached.clone());
762 }
763
764 let translated = match target_lang {
767 "es" => format!("[ES] {text}"),
768 "fr" => format!("[FR] {text}"),
769 "de" => format!("[DE] {text}"),
770 "zh" => format!("[ZH] {text}"),
771 _ => format!("[{}] {}", target_lang.to_uppercase(), text),
772 };
773
774 Ok(translated)
775 }
776
777 pub async fn detect_language(&self, text: &str) -> Result<LanguageDetection> {
779 let text_lower = text.to_lowercase();
781
782 let mut scores = HashMap::new();
783
784 let en_words = ["the", "and", "is", "hello", "world", "of", "to", "in"];
786 let en_score = en_words
787 .iter()
788 .filter(|&&word| text_lower.contains(word))
789 .count();
790 scores.insert("en", en_score);
791
792 let es_words = ["el", "y", "es", "hola", "buenos", "dias", "de", "en", "la"];
794 let es_score = es_words
795 .iter()
796 .filter(|&&word| text_lower.contains(word))
797 .count();
798 scores.insert("es", es_score);
799
800 let fr_words = ["le", "et", "est", "bonjour", "de", "la", "les"];
802 let fr_score = fr_words
803 .iter()
804 .filter(|&&word| text_lower.contains(word))
805 .count();
806 scores.insert("fr", fr_score);
807
808 let de_words = ["der", "und", "ist", "hallo", "von", "die", "das"];
810 let de_score = de_words
811 .iter()
812 .filter(|&&word| text_lower.contains(word))
813 .count();
814 scores.insert("de", de_score);
815
816 let detected_lang = scores
818 .iter()
819 .max_by_key(|&(_, &score)| score)
820 .map(|(lang, _)| *lang)
821 .unwrap_or("en");
822
823 Ok(LanguageDetection {
824 language_code: detected_lang.to_string(),
825 confidence: 0.85,
826 alternatives: vec![
827 ("en".to_string(), 0.7),
828 ("es".to_string(), 0.2),
829 ("fr".to_string(), 0.1),
830 ],
831 })
832 }
833
834 pub async fn generate_cross_lingual_embedding(
836 &self,
837 text: &str,
838 source_lang: &str,
839 target_lang: &str,
840 model: &dyn EmbeddingModel,
841 ) -> Result<Vector> {
842 let translated_text = self.translate_text(text, source_lang, target_lang).await?;
848 let embeddings = model.encode(&[translated_text]).await?;
849 Ok(Vector::new(embeddings[0].clone()))
850 }
851
852 pub async fn align_entities(
854 &self,
855 entity: &str,
856 source_lang: &str,
857 target_langs: &[String],
858 ) -> Result<HashMap<String, String>> {
859 let mut alignments = HashMap::new();
860
861 for target_lang in target_langs {
862 if target_lang == source_lang {
863 alignments.insert(target_lang.clone(), entity.to_string());
864 continue;
865 }
866
867 let aligned_entity = match target_lang.as_str() {
869 "es" => format!("{entity}_es"),
870 "fr" => format!("{entity}_fr"),
871 "de" => format!("{entity}_de"),
872 "zh" => format!("{entity}_zh"),
873 _ => format!("{entity}_{target_lang}"),
874 };
875
876 alignments.insert(target_lang.clone(), aligned_entity);
877 }
878
879 Ok(alignments)
880 }
881}
882
883#[derive(Debug, Clone)]
885pub struct LanguageDetection {
886 pub language_code: String,
887 pub confidence: f32,
888 pub alternatives: Vec<(String, f32)>,
889}
890
891#[derive(Debug, Clone)]
893pub struct LanguageModel {
894 pub model_id: String,
895 pub language_code: String,
896 pub model_type: String,
897 pub embedding_dimension: usize,
898}
899
900#[cfg(test)]
901mod tests {
902 use super::*;
903 use crate::models::TransE;
904 use crate::ModelConfig;
905
906 #[test]
907 fn test_vector_store_bridge() {
908 let config = ModelConfig::default().with_dimensions(10);
909 let _model = TransE::new(config);
910
911 let bridge = VectorStoreBridge::new();
912
913 let entity_uri = bridge.generate_entity_uri("test_entity");
915 assert!(entity_uri.starts_with("kg:entity:"));
916
917 let relation_uri = bridge.generate_relation_uri("test_relation");
918 assert!(relation_uri.starts_with("kg:relation:"));
919 }
920
921 #[test]
922 fn test_sparql_integration() -> Result<()> {
923 let config = ModelConfig::default().with_dimensions(10);
924 let model = TransE::new(config);
925
926 let integration = SparqlIntegration::new(Box::new(model));
927
928 let test_query = "SELECT ?s ?o WHERE { ?s <http://example.org/knows> ?o }";
929 let enhanced = integration.enhance_query(test_query)?;
930
931 assert_eq!(enhanced.original_query, test_query);
932 assert!(!enhanced.suggestions.is_empty());
933
934 Ok(())
935 }
936
937 #[test]
938 fn test_personalization_engine() {
939 let mut engine = PersonalizationEngine::new();
940 let user_id = "test_user";
941
942 let profile = engine.get_user_profile(user_id).unwrap();
944 assert_eq!(profile.user_id, user_id);
945
946 engine
948 .update_user_profile(
949 user_id,
950 "What is machine learning?",
951 Some(0.9),
952 InteractionType::Query,
953 )
954 .unwrap();
955
956 let history = engine.get_recent_interactions(user_id, 5);
957 assert_eq!(history.len(), 1);
958 }
959
960 #[tokio::test]
961 async fn test_multilingual_support() -> Result<()> {
962 let multilingual = MultilingualSupport::new();
963
964 let detection_en = multilingual.detect_language("Hello world").await?;
966 assert_eq!(detection_en.language_code, "en");
967
968 let detection_es = multilingual.detect_language("Hola y buenos dias").await?;
970 assert_eq!(detection_es.language_code, "es");
971
972 let translated = multilingual
974 .translate_text("Hello world", "en", "es")
975 .await?;
976 assert!(translated.contains("[ES]"));
977
978 let alignments = multilingual
980 .align_entities("person", "en", &["es".to_string(), "fr".to_string()])
981 .await?;
982 assert_eq!(alignments.len(), 2);
983
984 Ok(())
985 }
986}