1use crate::{EmbeddingModel, Vector};
4use anyhow::{anyhow, Result};
5use oxirs_vec::index::{AdvancedVectorIndex, IndexConfig, IndexType};
6use oxirs_vec::VectorIndex as OxirsVectorIndex;
7use std::collections::HashMap;
8use tracing::{debug, info, warn};
9
10pub struct VectorStoreBridge {
12 entity_mappings: HashMap<String, String>,
13 relation_mappings: HashMap<String, String>,
14 prefix_config: PrefixConfig,
15 entity_index: AdvancedVectorIndex,
19 relation_index: AdvancedVectorIndex,
22}
23
24fn flat_index() -> AdvancedVectorIndex {
25 AdvancedVectorIndex::new(IndexConfig {
26 index_type: IndexType::Flat,
27 ..IndexConfig::default()
28 })
29}
30
31#[derive(Debug, Clone)]
33pub struct PrefixConfig {
34 pub entity_prefix: String,
35 pub relation_prefix: String,
36 pub use_namespaces: bool,
37}
38
39impl Default for PrefixConfig {
40 fn default() -> Self {
41 Self {
42 entity_prefix: "kg:entity:".to_string(),
43 relation_prefix: "kg:relation:".to_string(),
44 use_namespaces: true,
45 }
46 }
47}
48
49impl VectorStoreBridge {
50 pub fn new() -> Self {
52 Self {
53 entity_mappings: HashMap::new(),
54 relation_mappings: HashMap::new(),
55 prefix_config: PrefixConfig::default(),
56 entity_index: flat_index(),
57 relation_index: flat_index(),
58 }
59 }
60
61 pub fn with_prefix_config(prefix_config: PrefixConfig) -> Self {
63 Self {
64 entity_mappings: HashMap::new(),
65 relation_mappings: HashMap::new(),
66 prefix_config,
67 entity_index: flat_index(),
68 relation_index: flat_index(),
69 }
70 }
71
72 pub fn sync_model_embeddings(&mut self, model: &dyn EmbeddingModel) -> Result<SyncStats> {
74 let start_time = std::time::Instant::now();
75 let mut sync_stats = SyncStats::default();
76
77 info!("Starting embedding synchronization to vector store");
78
79 let entities = model.get_entities();
81 for entity in &entities {
82 match model.get_entity_embedding(entity) {
83 Ok(embedding) => {
84 let uri = self.generate_entity_uri(entity);
85 if let Err(e) = self
86 .entity_index
87 .insert(uri.clone(), embedding.into_inner())
88 {
89 warn!("Failed to index embedding for entity {}: {}", entity, e);
90 sync_stats
91 .errors
92 .push(format!("Entity {entity} (indexing): {e}"));
93 continue;
94 }
95 self.entity_mappings.insert(entity.clone(), uri);
96 sync_stats.entities_synced += 1;
97 }
98 Err(e) => {
99 warn!("Failed to get embedding for entity {}: {}", entity, e);
100 sync_stats.errors.push(format!("Entity {entity}: {e}"));
101 }
102 }
103 }
104
105 let relations = model.get_relations();
107 for relation in &relations {
108 match model.get_relation_embedding(relation) {
109 Ok(embedding) => {
110 let uri = self.generate_relation_uri(relation);
111 if let Err(e) = self
112 .relation_index
113 .insert(uri.clone(), embedding.into_inner())
114 {
115 warn!("Failed to index embedding for relation {}: {}", relation, e);
116 sync_stats
117 .errors
118 .push(format!("Relation {relation} (indexing): {e}"));
119 continue;
120 }
121 self.relation_mappings.insert(relation.clone(), uri);
122 sync_stats.relations_synced += 1;
123 }
124 Err(e) => {
125 warn!("Failed to get embedding for relation {}: {}", relation, e);
126 sync_stats.errors.push(format!("Relation {relation}: {e}"));
127 }
128 }
129 }
130
131 sync_stats.sync_duration = start_time.elapsed();
132 info!(
133 "Embedding sync completed: {} entities, {} relations, {} errors",
134 sync_stats.entities_synced,
135 sync_stats.relations_synced,
136 sync_stats.errors.len()
137 );
138
139 Ok(sync_stats)
140 }
141
142 pub fn find_similar_entities(&self, entity: &str, k: usize) -> Result<Vec<(String, f32)>> {
146 let uri = self
147 .entity_mappings
148 .get(entity)
149 .ok_or_else(|| anyhow!("Entity not found in mappings: {}", entity))?;
150 let query = self
151 .entity_index
152 .get_vector(uri)
153 .ok_or_else(|| anyhow!("Entity '{}' is mapped but has no indexed embedding", entity))?;
154
155 debug!("Searching for entities similar to: {}", entity);
156 let results = self.entity_index.search_knn(query, k + 1)?;
157 Ok(results
158 .into_iter()
159 .filter(|(result_uri, _)| result_uri != uri)
160 .take(k)
161 .map(|(result_uri, distance)| (self.entity_name_from_uri(&result_uri), distance))
162 .collect())
163 }
164
165 pub fn find_similar_relations(&self, relation: &str, k: usize) -> Result<Vec<(String, f32)>> {
168 let uri = self
169 .relation_mappings
170 .get(relation)
171 .ok_or_else(|| anyhow!("Relation not found in mappings: {}", relation))?;
172 let query = self.relation_index.get_vector(uri).ok_or_else(|| {
173 anyhow!(
174 "Relation '{}' is mapped but has no indexed embedding",
175 relation
176 )
177 })?;
178
179 debug!("Searching for relations similar to: {}", relation);
180 let results = self.relation_index.search_knn(query, k + 1)?;
181 Ok(results
182 .into_iter()
183 .filter(|(result_uri, _)| result_uri != uri)
184 .take(k)
185 .map(|(result_uri, distance)| (self.relation_name_from_uri(&result_uri), distance))
186 .collect())
187 }
188
189 fn entity_name_from_uri(&self, uri: &str) -> String {
192 uri.strip_prefix(&self.prefix_config.entity_prefix)
193 .unwrap_or(uri)
194 .to_string()
195 }
196
197 fn relation_name_from_uri(&self, uri: &str) -> String {
200 uri.strip_prefix(&self.prefix_config.relation_prefix)
201 .unwrap_or(uri)
202 .to_string()
203 }
204
205 fn generate_entity_uri(&self, entity: &str) -> String {
207 if self.prefix_config.use_namespaces {
208 format!("{}{}", self.prefix_config.entity_prefix, entity)
209 } else {
210 entity.to_string()
211 }
212 }
213
214 fn generate_relation_uri(&self, relation: &str) -> String {
216 if self.prefix_config.use_namespaces {
217 format!("{}{}", self.prefix_config.relation_prefix, relation)
218 } else {
219 relation.to_string()
220 }
221 }
222
223 pub fn get_sync_info(&self) -> SyncInfo {
225 SyncInfo {
226 entities_mapped: self.entity_mappings.len(),
227 relations_mapped: self.relation_mappings.len(),
228 vector_store_stats: None,
229 }
230 }
231
232 pub fn clear_mappings(&mut self) {
234 self.entity_mappings.clear();
235 self.relation_mappings.clear();
236 info!("Cleared all entity and relation mappings");
237 }
238}
239
240impl Default for VectorStoreBridge {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246#[derive(Debug, Clone, Default)]
248pub struct SyncStats {
249 pub entities_synced: usize,
250 pub relations_synced: usize,
251 pub errors: Vec<String>,
252 pub sync_duration: std::time::Duration,
253}
254
255#[derive(Debug, Clone)]
257pub struct SyncInfo {
258 pub entities_mapped: usize,
259 pub relations_mapped: usize,
260 pub vector_store_stats: Option<(usize, usize)>,
261}
262
263pub struct ChatIntegration {
265 model: Box<dyn EmbeddingModel>,
266 context_window: usize,
267 similarity_threshold: f32,
268 personalization: PersonalizationEngine,
269 multilingual: MultilingualSupport,
270}
271
272impl ChatIntegration {
273 pub fn new(model: Box<dyn EmbeddingModel>) -> Self {
275 Self {
276 model,
277 context_window: 10,
278 similarity_threshold: 0.7,
279 personalization: PersonalizationEngine::new(),
280 multilingual: MultilingualSupport::new(),
281 }
282 }
283
284 pub fn with_context_window(mut self, window_size: usize) -> Self {
286 self.context_window = window_size;
287 self
288 }
289
290 pub fn with_similarity_threshold(mut self, threshold: f32) -> Self {
292 self.similarity_threshold = threshold;
293 self
294 }
295
296 pub fn extract_relevant_entities(&self, query: &str) -> Result<Vec<String>> {
304 let entities = self.model.get_entities();
305 if entities.is_empty() {
306 return Ok(Vec::new());
307 }
308
309 let query_lower = query.to_lowercase();
310
311 let mut entity_index: HashMap<String, String> = HashMap::new();
312 let mut max_entity_chars = 0usize;
313 for entity in entities {
314 let lower = entity.to_lowercase();
315 max_entity_chars = max_entity_chars.max(lower.chars().count());
316 entity_index.insert(lower, entity);
317 }
318 if max_entity_chars == 0 {
319 return Ok(Vec::new());
320 }
321
322 let boundaries: Vec<usize> = query_lower
325 .char_indices()
326 .map(|(byte_idx, _)| byte_idx)
327 .chain(std::iter::once(query_lower.len()))
328 .collect();
329 let num_chars = boundaries.len() - 1;
330
331 let mut relevant = Vec::new();
332 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
333
334 for start in 0..num_chars {
335 let start_byte = boundaries[start];
336 let max_len = max_entity_chars.min(num_chars - start);
337 for len in 1..=max_len {
338 let end_byte = boundaries[start + len];
339 let window = &query_lower[start_byte..end_byte];
340 if let Some(original) = entity_index.get(window) {
341 if seen.insert(original.clone()) {
342 relevant.push(original.clone());
343 }
344 }
345 }
346 }
347
348 Ok(relevant)
349 }
350
351 pub async fn generate_context_embedding(&self, messages: &[String]) -> Result<Vector> {
355 if messages.is_empty() {
356 return Err(anyhow!("No messages provided"));
357 }
358
359 let recent_messages: Vec<String> = messages
361 .iter()
362 .rev()
363 .take(self.context_window)
364 .cloned()
365 .collect();
366
367 let encoded = self.model.encode(&recent_messages).await?;
368 let dim = encoded
369 .iter()
370 .map(|v| v.len())
371 .find(|&len| len > 0)
372 .ok_or_else(|| anyhow!("Model returned no non-empty encodings for these messages"))?;
373
374 let mut combined = vec![0.0f32; dim];
375 let mut count = 0usize;
376 for vector in &encoded {
377 if vector.len() != dim {
378 continue;
381 }
382 for (acc, value) in combined.iter_mut().zip(vector.iter()) {
383 *acc += value;
384 }
385 count += 1;
386 }
387
388 for value in combined.iter_mut() {
389 *value /= count as f32;
390 }
391
392 Ok(Vector::new(combined))
393 }
394
395 pub async fn generate_personalized_embedding(
397 &mut self,
398 user_id: &str,
399 query: &str,
400 conversation_history: &[String],
401 ) -> Result<Vector> {
402 let user_profile = self.personalization.get_user_profile(user_id)?.clone();
404
405 let embeddings = self.model.encode(&[query.to_string()]).await?;
407 let base_embedding = Vector::new(embeddings[0].clone());
408 let personalized_embedding = self.personalization.apply_user_preferences(
409 &base_embedding,
410 &user_profile,
411 conversation_history,
412 )?;
413
414 Ok(personalized_embedding)
415 }
416
417 pub fn update_user_profile(
419 &mut self,
420 user_id: &str,
421 query: &str,
422 response_feedback: Option<f32>,
423 interaction_type: InteractionType,
424 ) -> Result<()> {
425 self.personalization.update_user_profile(
426 user_id,
427 query,
428 response_feedback,
429 interaction_type,
430 )
431 }
432
433 pub async fn translate_query(
435 &self,
436 query: &str,
437 source_lang: &str,
438 target_lang: &str,
439 ) -> Result<String> {
440 self.multilingual
441 .translate_text(query, source_lang, target_lang)
442 .await
443 }
444
445 pub async fn detect_language(&self, text: &str) -> Result<LanguageDetection> {
447 self.multilingual.detect_language(text).await
448 }
449
450 pub async fn generate_cross_lingual_embedding(
452 &self,
453 text: &str,
454 source_lang: &str,
455 target_lang: &str,
456 ) -> Result<Vector> {
457 self.multilingual
458 .generate_cross_lingual_embedding(text, source_lang, target_lang, &*self.model)
459 .await
460 }
461
462 pub async fn align_entities_across_languages(
464 &self,
465 entity: &str,
466 source_lang: &str,
467 target_langs: &[String],
468 ) -> Result<HashMap<String, String>> {
469 self.multilingual
470 .align_entities(entity, source_lang, target_langs)
471 .await
472 }
473}
474
475pub struct SparqlIntegration {
477 #[allow(dead_code)]
478 model: Box<dyn EmbeddingModel>,
479 #[allow(dead_code)]
480 similarity_boost: f32,
481}
482
483impl SparqlIntegration {
484 pub fn new(model: Box<dyn EmbeddingModel>) -> Self {
486 Self {
487 model,
488 similarity_boost: 0.1,
489 }
490 }
491
492 pub fn enhance_query(&self, sparql_query: &str) -> Result<EnhancedQuery> {
494 let entities = self.extract_entities_from_sparql(sparql_query)?;
496 let relations = self.extract_relations_from_sparql(sparql_query)?;
497
498 let mut suggestions = Vec::new();
499
500 for entity in &entities {
502 suggestions.push(QuerySuggestion {
504 suggestion_type: SuggestionType::SimilarEntity,
505 original: entity.clone(),
506 suggested: format!("similar_to_{entity}"),
507 confidence: 0.8,
508 });
509 }
510
511 for relation in &relations {
513 suggestions.push(QuerySuggestion {
514 suggestion_type: SuggestionType::SimilarRelation,
515 original: relation.clone(),
516 suggested: format!("similar_to_{relation}"),
517 confidence: 0.7,
518 });
519 }
520
521 Ok(EnhancedQuery {
522 original_query: sparql_query.to_string(),
523 entities_found: entities,
524 relations_found: relations,
525 suggestions,
526 })
527 }
528
529 fn extract_entities_from_sparql(&self, query: &str) -> Result<Vec<String>> {
531 let mut entities = Vec::new();
534
535 for line in query.lines() {
536 if line.contains("http://") {
537 if let Some(start) = line.find("http://") {
539 if let Some(end) = line[start..].find(' ') {
540 let uri = &line[start..start + end];
541 entities.push(uri.to_string());
542 }
543 }
544 }
545 }
546
547 Ok(entities)
548 }
549
550 fn extract_relations_from_sparql(&self, query: &str) -> Result<Vec<String>> {
552 let mut relations = Vec::new();
554
555 for line in query.lines() {
556 if line.contains("?") && line.contains("http://") {
557 if let Some(start) = line.find('<') {
559 if let Some(end) = line.find('>') {
560 let relation = &line[start + 1..end];
561 relations.push(relation.to_string());
562 }
563 }
564 }
565 }
566
567 Ok(relations)
568 }
569}
570
571#[derive(Debug, Clone)]
573pub struct EnhancedQuery {
574 pub original_query: String,
575 pub entities_found: Vec<String>,
576 pub relations_found: Vec<String>,
577 pub suggestions: Vec<QuerySuggestion>,
578}
579
580#[derive(Debug, Clone)]
582pub struct QuerySuggestion {
583 pub suggestion_type: SuggestionType,
584 pub original: String,
585 pub suggested: String,
586 pub confidence: f32,
587}
588
589#[derive(Debug, Clone)]
591pub enum SuggestionType {
592 SimilarEntity,
593 SimilarRelation,
594 AlternativePattern,
595 ExpansionSuggestion,
596}
597
598pub struct PersonalizationEngine {
600 user_profiles: HashMap<String, UserProfile>,
601 interaction_history: HashMap<String, Vec<UserInteraction>>,
602 preference_weights: PreferenceWeights,
603}
604
605impl Default for PersonalizationEngine {
606 fn default() -> Self {
607 Self::new()
608 }
609}
610
611impl PersonalizationEngine {
612 pub fn new() -> Self {
613 Self {
614 user_profiles: HashMap::new(),
615 interaction_history: HashMap::new(),
616 preference_weights: PreferenceWeights::default(),
617 }
618 }
619
620 pub fn get_user_profile(&mut self, user_id: &str) -> Result<&UserProfile> {
622 if !self.user_profiles.contains_key(user_id) {
623 let profile = UserProfile::new(user_id.to_string());
624 self.user_profiles.insert(user_id.to_string(), profile);
625 }
626
627 self.user_profiles
628 .get(user_id)
629 .ok_or_else(|| anyhow!("Failed to get user profile for {}", user_id))
630 }
631
632 pub fn apply_user_preferences(
634 &self,
635 base_embedding: &Vector,
636 user_profile: &UserProfile,
637 conversation_history: &[String],
638 ) -> Result<Vector> {
639 let mut personalized = base_embedding.clone();
640
641 for (domain, weight) in &user_profile.domain_preferences {
643 if conversation_history.iter().any(|msg| msg.contains(domain)) {
644 for i in 0..personalized.values.len() {
646 personalized.values[i] *= 1.0 + (weight * self.preference_weights.domain_boost);
647 }
648 }
649 }
650
651 let recent_interactions = self.get_recent_interactions(&user_profile.user_id, 10);
653 if !recent_interactions.is_empty() {
654 let avg_sentiment = recent_interactions
655 .iter()
656 .map(|i| i.sentiment_score.unwrap_or(0.0))
657 .sum::<f32>()
658 / recent_interactions.len() as f32;
659
660 for i in 0..personalized.values.len() {
662 personalized.values[i] *=
663 1.0 + (avg_sentiment * self.preference_weights.sentiment_influence);
664 }
665 }
666
667 Ok(personalized)
668 }
669
670 pub fn update_user_profile(
672 &mut self,
673 user_id: &str,
674 query: &str,
675 response_feedback: Option<f32>,
676 interaction_type: InteractionType,
677 ) -> Result<()> {
678 let interaction = UserInteraction {
679 timestamp: chrono::Utc::now(),
680 query: query.to_string(),
681 interaction_type,
682 response_feedback,
683 sentiment_score: self.analyze_query_sentiment(query),
684 };
685
686 self.interaction_history
688 .entry(user_id.to_string())
689 .or_default()
690 .push(interaction.clone());
691
692 if let Some(profile) = self.user_profiles.get_mut(user_id) {
694 profile.update_from_interaction(&interaction);
695 }
696
697 Ok(())
698 }
699
700 fn get_recent_interactions(&self, user_id: &str, limit: usize) -> Vec<&UserInteraction> {
702 self.interaction_history
703 .get(user_id)
704 .map(|history| history.iter().rev().take(limit).collect())
705 .unwrap_or_default()
706 }
707
708 fn analyze_query_sentiment(&self, query: &str) -> Option<f32> {
710 let positive_words = ["good", "great", "excellent", "amazing", "wonderful"];
711 let negative_words = ["bad", "terrible", "awful", "horrible", "disappointing"];
712
713 let query_lower = query.to_lowercase();
714 let positive_count = positive_words
715 .iter()
716 .filter(|&&word| query_lower.contains(word))
717 .count();
718 let negative_count = negative_words
719 .iter()
720 .filter(|&&word| query_lower.contains(word))
721 .count();
722
723 if positive_count + negative_count == 0 {
724 return None;
725 }
726
727 let sentiment = (positive_count as f32 - negative_count as f32)
728 / (positive_count + negative_count) as f32;
729 Some(sentiment)
730 }
731}
732
733#[derive(Debug, Clone)]
735pub struct UserProfile {
736 pub user_id: String,
737 pub domain_preferences: HashMap<String, f32>,
738 pub entity_preferences: HashMap<String, f32>,
739 pub interaction_patterns: InteractionPatterns,
740 pub language_preferences: Vec<String>,
741 pub created_at: chrono::DateTime<chrono::Utc>,
742 pub last_updated: chrono::DateTime<chrono::Utc>,
743}
744
745impl UserProfile {
746 pub fn new(user_id: String) -> Self {
747 let now = chrono::Utc::now();
748 Self {
749 user_id,
750 domain_preferences: HashMap::new(),
751 entity_preferences: HashMap::new(),
752 interaction_patterns: InteractionPatterns::default(),
753 language_preferences: vec!["en".to_string()],
754 created_at: now,
755 last_updated: now,
756 }
757 }
758
759 pub fn update_from_interaction(&mut self, interaction: &UserInteraction) {
761 self.last_updated = chrono::Utc::now();
762
763 self.interaction_patterns.total_interactions += 1;
765 match interaction.interaction_type {
766 InteractionType::Query => self.interaction_patterns.query_count += 1,
767 InteractionType::Feedback => self.interaction_patterns.feedback_count += 1,
768 InteractionType::EntityLookup => self.interaction_patterns.entity_lookup_count += 1,
769 }
770
771 if let Some(sentiment) = interaction.sentiment_score {
773 let current_avg = self.interaction_patterns.average_sentiment;
774 let total = self.interaction_patterns.total_interactions as f32;
775 self.interaction_patterns.average_sentiment =
776 (current_avg * (total - 1.0) + sentiment) / total;
777 }
778
779 self.extract_domain_preferences(&interaction.query);
781 }
782
783 fn extract_domain_preferences(&mut self, query: &str) {
785 let domains = [
786 "science",
787 "technology",
788 "medicine",
789 "business",
790 "education",
791 "sports",
792 "entertainment",
793 "politics",
794 "history",
795 "art",
796 ];
797
798 for domain in &domains {
799 if query.to_lowercase().contains(domain) {
800 #[allow(clippy::unnecessary_to_owned)]
801 let current = self.domain_preferences.get(*domain).copied().unwrap_or(0.0);
802 self.domain_preferences
803 .insert(domain.to_string(), current + 0.1);
804 }
805 }
806 }
807}
808
809#[derive(Debug, Clone, Default)]
811pub struct InteractionPatterns {
812 pub total_interactions: u32,
813 pub query_count: u32,
814 pub feedback_count: u32,
815 pub entity_lookup_count: u32,
816 pub average_sentiment: f32,
817 pub preferred_response_length: Option<usize>,
818}
819
820#[derive(Debug, Clone)]
822pub enum InteractionType {
823 Query,
824 Feedback,
825 EntityLookup,
826}
827
828#[derive(Debug, Clone)]
830pub struct UserInteraction {
831 pub timestamp: chrono::DateTime<chrono::Utc>,
832 pub query: String,
833 pub interaction_type: InteractionType,
834 pub response_feedback: Option<f32>,
835 pub sentiment_score: Option<f32>,
836}
837
838#[derive(Debug, Clone)]
840pub struct PreferenceWeights {
841 pub domain_boost: f32,
842 pub entity_boost: f32,
843 pub sentiment_influence: f32,
844 pub recency_decay: f32,
845}
846
847impl Default for PreferenceWeights {
848 fn default() -> Self {
849 Self {
850 domain_boost: 0.1,
851 entity_boost: 0.15,
852 sentiment_influence: 0.05,
853 recency_decay: 0.95,
854 }
855 }
856}
857
858pub struct MultilingualSupport {
860 supported_languages: Vec<String>,
861 translation_cache: HashMap<String, String>,
862 language_models: HashMap<String, LanguageModel>,
863}
864
865impl Default for MultilingualSupport {
866 fn default() -> Self {
867 Self::new()
868 }
869}
870
871impl MultilingualSupport {
872 pub fn new() -> Self {
873 Self {
874 supported_languages: vec![
875 "en".to_string(),
876 "es".to_string(),
877 "fr".to_string(),
878 "de".to_string(),
879 "it".to_string(),
880 "pt".to_string(),
881 "zh".to_string(),
882 "ja".to_string(),
883 "ko".to_string(),
884 "ar".to_string(),
885 "hi".to_string(),
886 "ru".to_string(),
887 ],
888 translation_cache: HashMap::new(),
889 language_models: HashMap::new(),
890 }
891 }
892
893 pub async fn translate_text(
895 &self,
896 text: &str,
897 source_lang: &str,
898 target_lang: &str,
899 ) -> Result<String> {
900 if source_lang == target_lang {
901 return Ok(text.to_string());
902 }
903
904 let cache_key = format!("{source_lang}:{target_lang}:{text}");
905 if let Some(cached) = self.translation_cache.get(&cache_key) {
906 return Ok(cached.clone());
907 }
908
909 let translated = match target_lang {
912 "es" => format!("[ES] {text}"),
913 "fr" => format!("[FR] {text}"),
914 "de" => format!("[DE] {text}"),
915 "zh" => format!("[ZH] {text}"),
916 _ => format!("[{}] {}", target_lang.to_uppercase(), text),
917 };
918
919 Ok(translated)
920 }
921
922 pub async fn detect_language(&self, text: &str) -> Result<LanguageDetection> {
924 let text_lower = text.to_lowercase();
926
927 let mut scores = HashMap::new();
928
929 let en_words = ["the", "and", "is", "hello", "world", "of", "to", "in"];
931 let en_score = en_words
932 .iter()
933 .filter(|&&word| text_lower.contains(word))
934 .count();
935 scores.insert("en", en_score);
936
937 let es_words = ["el", "y", "es", "hola", "buenos", "dias", "de", "en", "la"];
939 let es_score = es_words
940 .iter()
941 .filter(|&&word| text_lower.contains(word))
942 .count();
943 scores.insert("es", es_score);
944
945 let fr_words = ["le", "et", "est", "bonjour", "de", "la", "les"];
947 let fr_score = fr_words
948 .iter()
949 .filter(|&&word| text_lower.contains(word))
950 .count();
951 scores.insert("fr", fr_score);
952
953 let de_words = ["der", "und", "ist", "hallo", "von", "die", "das"];
955 let de_score = de_words
956 .iter()
957 .filter(|&&word| text_lower.contains(word))
958 .count();
959 scores.insert("de", de_score);
960
961 let detected_lang = scores
963 .iter()
964 .max_by_key(|&(_, &score)| score)
965 .map(|(lang, _)| *lang)
966 .unwrap_or("en");
967
968 Ok(LanguageDetection {
969 language_code: detected_lang.to_string(),
970 confidence: 0.85,
971 alternatives: vec![
972 ("en".to_string(), 0.7),
973 ("es".to_string(), 0.2),
974 ("fr".to_string(), 0.1),
975 ],
976 })
977 }
978
979 pub async fn generate_cross_lingual_embedding(
981 &self,
982 text: &str,
983 source_lang: &str,
984 target_lang: &str,
985 model: &dyn EmbeddingModel,
986 ) -> Result<Vector> {
987 let translated_text = self.translate_text(text, source_lang, target_lang).await?;
993 let embeddings = model.encode(&[translated_text]).await?;
994 Ok(Vector::new(embeddings[0].clone()))
995 }
996
997 pub async fn align_entities(
999 &self,
1000 entity: &str,
1001 source_lang: &str,
1002 target_langs: &[String],
1003 ) -> Result<HashMap<String, String>> {
1004 let mut alignments = HashMap::new();
1005
1006 for target_lang in target_langs {
1007 if target_lang == source_lang {
1008 alignments.insert(target_lang.clone(), entity.to_string());
1009 continue;
1010 }
1011
1012 let aligned_entity = match target_lang.as_str() {
1014 "es" => format!("{entity}_es"),
1015 "fr" => format!("{entity}_fr"),
1016 "de" => format!("{entity}_de"),
1017 "zh" => format!("{entity}_zh"),
1018 _ => format!("{entity}_{target_lang}"),
1019 };
1020
1021 alignments.insert(target_lang.clone(), aligned_entity);
1022 }
1023
1024 Ok(alignments)
1025 }
1026}
1027
1028#[derive(Debug, Clone)]
1030pub struct LanguageDetection {
1031 pub language_code: String,
1032 pub confidence: f32,
1033 pub alternatives: Vec<(String, f32)>,
1034}
1035
1036#[derive(Debug, Clone)]
1038pub struct LanguageModel {
1039 pub model_id: String,
1040 pub language_code: String,
1041 pub model_type: String,
1042 pub embedding_dimension: usize,
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::models::TransE;
1049 use crate::ModelConfig;
1050
1051 #[test]
1052 fn test_vector_store_bridge() {
1053 let config = ModelConfig::default().with_dimensions(10);
1054 let _model = TransE::new(config);
1055
1056 let bridge = VectorStoreBridge::new();
1057
1058 let entity_uri = bridge.generate_entity_uri("test_entity");
1060 assert!(entity_uri.starts_with("kg:entity:"));
1061
1062 let relation_uri = bridge.generate_relation_uri("test_relation");
1063 assert!(relation_uri.starts_with("kg:relation:"));
1064 }
1065
1066 #[tokio::test]
1070 async fn test_vector_store_bridge_find_similar_returns_real_results() -> Result<()> {
1071 let config = ModelConfig::default().with_dimensions(8);
1072 let mut model = TransE::new(config);
1073
1074 for (s, p, o) in [
1075 ("alice", "knows", "bob"),
1076 ("bob", "knows", "carol"),
1077 ("carol", "knows", "alice"),
1078 ] {
1079 model.add_triple(crate::Triple::new(
1080 crate::NamedNode::new(s)?,
1081 crate::NamedNode::new(p)?,
1082 crate::NamedNode::new(o)?,
1083 ))?;
1084 }
1085 model.train(Some(1)).await?;
1086
1087 let mut bridge = VectorStoreBridge::new();
1088 let stats = bridge.sync_model_embeddings(&model)?;
1089 assert_eq!(stats.entities_synced, 3);
1090 assert_eq!(stats.relations_synced, 1);
1091 assert!(stats.errors.is_empty(), "errors = {:?}", stats.errors);
1092
1093 let similar = bridge.find_similar_entities("alice", 2)?;
1095 assert!(!similar.is_empty(), "expected at least one similar entity");
1096 assert!(
1097 similar.iter().all(|(name, _)| name != "alice"),
1098 "the query entity itself must not appear in its own results: {:?}",
1099 similar
1100 );
1101
1102 assert!(bridge.find_similar_entities("nobody", 2).is_err());
1104
1105 Ok(())
1106 }
1107
1108 struct MockTextModel {
1113 config: ModelConfig,
1114 model_id: uuid::Uuid,
1115 entities: Vec<String>,
1116 }
1117
1118 impl MockTextModel {
1119 fn new(dimensions: usize, entities: Vec<String>) -> Self {
1120 Self {
1121 config: ModelConfig::default().with_dimensions(dimensions),
1122 model_id: uuid::Uuid::new_v4(),
1123 entities,
1124 }
1125 }
1126 }
1127
1128 #[async_trait::async_trait]
1129 impl EmbeddingModel for MockTextModel {
1130 fn config(&self) -> &ModelConfig {
1131 &self.config
1132 }
1133 fn model_id(&self) -> &uuid::Uuid {
1134 &self.model_id
1135 }
1136 fn model_type(&self) -> &'static str {
1137 "MockText"
1138 }
1139 fn add_triple(&mut self, _triple: crate::Triple) -> Result<()> {
1140 Ok(())
1141 }
1142 async fn train(&mut self, _epochs: Option<usize>) -> Result<crate::TrainingStats> {
1143 Ok(crate::TrainingStats {
1144 epochs_completed: 1,
1145 final_loss: 0.0,
1146 training_time_seconds: 0.0,
1147 convergence_achieved: true,
1148 loss_history: vec![0.0],
1149 })
1150 }
1151 fn get_entity_embedding(&self, entity: &str) -> Result<Vector> {
1152 Ok(Vector::new(vec![
1153 entity.len() as f32;
1154 self.config.dimensions
1155 ]))
1156 }
1157 fn get_relation_embedding(&self, relation: &str) -> Result<Vector> {
1158 Ok(Vector::new(vec![
1159 relation.len() as f32;
1160 self.config.dimensions
1161 ]))
1162 }
1163 fn score_triple(&self, _subject: &str, _predicate: &str, _object: &str) -> Result<f64> {
1164 Ok(0.0)
1165 }
1166 fn predict_objects(
1167 &self,
1168 _subject: &str,
1169 _predicate: &str,
1170 _k: usize,
1171 ) -> Result<Vec<(String, f64)>> {
1172 Ok(vec![])
1173 }
1174 fn predict_subjects(
1175 &self,
1176 _predicate: &str,
1177 _object: &str,
1178 _k: usize,
1179 ) -> Result<Vec<(String, f64)>> {
1180 Ok(vec![])
1181 }
1182 fn predict_relations(
1183 &self,
1184 _subject: &str,
1185 _object: &str,
1186 _k: usize,
1187 ) -> Result<Vec<(String, f64)>> {
1188 Ok(vec![])
1189 }
1190 fn get_entities(&self) -> Vec<String> {
1191 self.entities.clone()
1192 }
1193 fn get_relations(&self) -> Vec<String> {
1194 vec![]
1195 }
1196 fn get_stats(&self) -> crate::ModelStats {
1197 crate::ModelStats {
1198 num_entities: self.entities.len(),
1199 dimensions: self.config.dimensions,
1200 is_trained: true,
1201 ..Default::default()
1202 }
1203 }
1204 fn save(&self, _path: &str) -> Result<()> {
1205 Ok(())
1206 }
1207 fn load(&mut self, _path: &str) -> Result<()> {
1208 Ok(())
1209 }
1210 fn clear(&mut self) {}
1211 fn is_trained(&self) -> bool {
1212 true
1213 }
1214 async fn encode(&self, texts: &[String]) -> Result<Vec<Vec<f32>>> {
1215 Ok(texts
1216 .iter()
1217 .map(|t| vec![t.len() as f32; self.config.dimensions])
1218 .collect())
1219 }
1220 }
1221
1222 #[test]
1225 fn test_extract_relevant_entities_finds_substring_matches() -> Result<()> {
1226 let model = MockTextModel::new(
1227 4,
1228 vec!["alice".to_string(), "bob".to_string(), "carol".to_string()],
1229 );
1230 let integration = ChatIntegration::new(Box::new(model));
1231
1232 let relevant = integration.extract_relevant_entities("Alice met Bob yesterday.")?;
1233 assert!(relevant.contains(&"alice".to_string()));
1234 assert!(relevant.contains(&"bob".to_string()));
1235 assert!(!relevant.contains(&"carol".to_string()));
1236
1237 Ok(())
1238 }
1239
1240 #[tokio::test]
1244 async fn test_generate_context_embedding_reflects_message_content() -> Result<()> {
1245 let model = MockTextModel::new(4, vec![]);
1246 let integration = ChatIntegration::new(Box::new(model));
1247
1248 let short = integration
1249 .generate_context_embedding(&["hi".to_string()])
1250 .await?;
1251 let long = integration
1252 .generate_context_embedding(&["a much longer message here".to_string()])
1253 .await?;
1254
1255 assert_eq!(short.values.len(), 4);
1256 assert_ne!(
1257 short.values, long.values,
1258 "different message content should produce different context embeddings"
1259 );
1260
1261 assert!(integration.generate_context_embedding(&[]).await.is_err());
1262
1263 Ok(())
1264 }
1265
1266 #[test]
1267 fn test_sparql_integration() -> Result<()> {
1268 let config = ModelConfig::default().with_dimensions(10);
1269 let model = TransE::new(config);
1270
1271 let integration = SparqlIntegration::new(Box::new(model));
1272
1273 let test_query = "SELECT ?s ?o WHERE { ?s <http://example.org/knows> ?o }";
1274 let enhanced = integration.enhance_query(test_query)?;
1275
1276 assert_eq!(enhanced.original_query, test_query);
1277 assert!(!enhanced.suggestions.is_empty());
1278
1279 Ok(())
1280 }
1281
1282 #[test]
1283 fn test_personalization_engine() {
1284 let mut engine = PersonalizationEngine::new();
1285 let user_id = "test_user";
1286
1287 let profile = engine.get_user_profile(user_id).expect("should succeed");
1289 assert_eq!(profile.user_id, user_id);
1290
1291 engine
1293 .update_user_profile(
1294 user_id,
1295 "What is machine learning?",
1296 Some(0.9),
1297 InteractionType::Query,
1298 )
1299 .expect("should succeed");
1300
1301 let history = engine.get_recent_interactions(user_id, 5);
1302 assert_eq!(history.len(), 1);
1303 }
1304
1305 #[tokio::test]
1306 async fn test_multilingual_support() -> Result<()> {
1307 let multilingual = MultilingualSupport::new();
1308
1309 let detection_en = multilingual.detect_language("Hello world").await?;
1311 assert_eq!(detection_en.language_code, "en");
1312
1313 let detection_es = multilingual.detect_language("Hola y buenos dias").await?;
1315 assert_eq!(detection_es.language_code, "es");
1316
1317 let translated = multilingual
1319 .translate_text("Hello world", "en", "es")
1320 .await?;
1321 assert!(translated.contains("[ES]"));
1322
1323 let alignments = multilingual
1325 .align_entities("person", "en", &["es".to_string(), "fr".to_string()])
1326 .await?;
1327 assert_eq!(alignments.len(), 2);
1328
1329 Ok(())
1330 }
1331}