Skip to main content

do_memory_core/embeddings/
simple.rs

1//! Simple semantic embeddings implementation
2//!
3//! **⚠️ WARNING: This module contains mock/test-only implementations**
4//!
5//! This is a simplified version that demonstrates the concept
6//! without all the complex integrations that cause compilation issues.
7//! The `text_to_embedding` function uses hash-based pseudo-embeddings
8//! that are NOT semantically meaningful and should only be used for:
9//! - Unit testing
10//! - Development/demonstration purposes
11//! - Fallback when real embeddings are unavailable
12//!
13//! **Production Use:** Use `memory-core/src/embeddings/` module with real
14//! embedding models (gte-rs, ONNX runtime) for actual semantic search.
15
16use anyhow::Result;
17use serde::{Deserialize, Serialize};
18use tracing;
19
20/// Configuration for embeddings
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EmbeddingConfig {
23    /// Similarity threshold for search (0.0 to 1.0)
24    pub similarity_threshold: f32,
25    /// Maximum batch size for embedding generation
26    pub batch_size: usize,
27    /// Cache embeddings to avoid regeneration
28    pub cache_embeddings: bool,
29}
30
31impl Default for EmbeddingConfig {
32    fn default() -> Self {
33        Self {
34            similarity_threshold: 0.7,
35            batch_size: 32,
36            cache_embeddings: true,
37        }
38    }
39}
40
41/// Calculate cosine similarity between two vectors
42#[must_use]
43pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
44    crate::embeddings::cosine_similarity(a, b)
45}
46
47/// Simple text to embedding converter (mock implementation)
48///
49/// **⚠️ PRODUCTION WARNING: This is a mock/test-only implementation**
50///
51/// This function generates deterministic hash-based "embeddings" that are
52/// NOT semantically meaningful. The similarity between these vectors is
53/// essentially random and does not reflect actual semantic similarity.
54///
55/// **Use Cases:**
56/// - Unit testing (deterministic, fast)
57/// - Development/demonstration
58/// - Fallback when real embeddings unavailable
59///
60/// **Do NOT Use For:**
61/// - Production semantic search
62/// - Real similarity calculations
63/// - User-facing features
64///
65/// **For Production:** Use `memory-core::embeddings::LocalEmbeddingProvider`
66/// with the `local-embeddings` feature enabled and real ONNX models.
67pub fn text_to_embedding(text: &str) -> Vec<f32> {
68    use std::collections::hash_map::DefaultHasher;
69    use std::hash::{Hash, Hasher};
70
71    // Emit production warning
72    tracing::warn!(
73        "PRODUCTION WARNING: Using hash-based pseudo-embeddings - semantic search will not work correctly! \
74         Text: '{}'. Use real embedding models for production.",
75        text.chars().take(20).collect::<String>()
76    );
77
78    // Create a deterministic embedding based on text hash
79    let mut hasher = DefaultHasher::new();
80    text.hash(&mut hasher);
81    let hash = hasher.finish();
82
83    let dimension = 384; // Standard sentence transformer dimension
84    let mut embedding = Vec::with_capacity(dimension);
85    let mut seed = hash;
86
87    for _ in 0..dimension {
88        // Simple PRNG to generate values
89        seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12345);
90        let value = ((seed >> 16) as f32) / 32768.0 - 1.0; // Range [-1, 1]
91        embedding.push(value);
92    }
93
94    // Normalize the vector
95    let magnitude = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
96    if magnitude > 0.0 {
97        for x in &mut embedding {
98            *x /= magnitude;
99        }
100    }
101
102    embedding
103}
104
105/// Test-only text to embedding converter
106///
107/// This function is identical to `text_to_embedding` but without the warning
108/// for use in tests and internal testing scenarios.
109#[cfg(test)]
110#[must_use]
111pub fn text_to_embedding_test(text: &str) -> Vec<f32> {
112    use std::collections::hash_map::DefaultHasher;
113    use std::hash::{Hash, Hasher};
114
115    // Create a deterministic embedding based on text hash
116    let mut hasher = DefaultHasher::new();
117    text.hash(&mut hasher);
118    let hash = hasher.finish();
119
120    let dimension = 384; // Standard sentence transformer dimension
121    let mut embedding = Vec::with_capacity(dimension);
122    let mut seed = hash;
123
124    for _ in 0..dimension {
125        // Simple PRNG to generate values
126        seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12345);
127        let value = ((seed >> 16) as f32) / 32768.0 - 1.0; // Range [-1, 1]
128        embedding.push(value);
129    }
130
131    // Normalize the vector
132    let magnitude = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
133    if magnitude > 0.0 {
134        for x in &mut embedding {
135            *x /= magnitude;
136        }
137    }
138
139    embedding
140}
141
142/// Find most similar texts from a collection
143///
144/// **⚠️ Uses Mock Embeddings:** This function uses hash-based pseudo-embeddings
145/// that are NOT semantically meaningful. The "similarity" results are
146/// essentially random and should not be used for production semantic search.
147///
148/// For production semantic search, use `memory-core::embeddings::SemanticService`
149/// with real embedding models.
150pub fn find_similar_texts(
151    query: &str,
152    candidates: &[String],
153    limit: usize,
154    threshold: f32,
155) -> Vec<(usize, f32, String)> {
156    tracing::warn!(
157        "Using mock embeddings for semantic search - results are not semantically meaningful!"
158    );
159
160    let query_embedding = text_to_embedding(query);
161
162    let mut similarities: Vec<(usize, f32, String)> = candidates
163        .iter()
164        .enumerate()
165        .map(|(i, text)| {
166            let embedding = text_to_embedding(text);
167            let similarity = cosine_similarity(&query_embedding, &embedding);
168            (i, similarity, text.clone())
169        })
170        .filter(|(_, similarity, _)| *similarity >= threshold)
171        .collect();
172
173    // Sort by similarity (highest first)
174    similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
175
176    // Return top results
177    similarities.into_iter().take(limit).collect()
178}
179
180/// Simple semantic search demonstration
181///
182/// **⚠️ DEMONSTRATION ONLY:** This function uses mock hash-based embeddings
183/// that are NOT semantically meaningful. The results shown here are for
184/// demonstration purposes only and should NOT be used to evaluate the
185/// effectiveness of semantic search.
186///
187/// For real semantic search demonstrations, use the proper embeddings module
188/// with `cargo run --features local-embeddings`.
189pub fn demonstrate_semantic_search() -> Result<()> {
190    tracing::warn!("🧠 Semantic Search Demonstration (Mock Embeddings)");
191    tracing::warn!("WARNING: This demonstration uses hash-based pseudo-embeddings");
192    tracing::warn!("that are NOT semantically meaningful. Similarity scores are");
193    tracing::warn!("essentially random and do not reflect actual semantic similarity.");
194    tracing::warn!("For production semantic search, use real embedding models.");
195    tracing::info!("Enable with: cargo run --features local-embeddings");
196
197    // Sample episode descriptions
198    let episodes = vec![
199        "Implement user authentication with JWT tokens".to_string(),
200        "Build REST API endpoints for user management".to_string(),
201        "Create data validation middleware for API requests".to_string(),
202        "Add rate limiting to prevent API abuse".to_string(),
203        "Implement OAuth2 authentication flow".to_string(),
204        "Design database schema for user profiles".to_string(),
205        "Write unit tests for authentication module".to_string(),
206        "Deploy API to production with Docker".to_string(),
207        "Monitor API performance and error rates".to_string(),
208        "Document API endpoints with OpenAPI spec".to_string(),
209    ];
210
211    // Test queries
212    let queries = vec![
213        "How to secure API with authentication?",
214        "Need to create user management endpoints",
215        "Add validation to API requests",
216        "Prevent API abuse and rate limiting",
217    ];
218
219    for query in queries {
220        tracing::debug!("Query: \"{}\"", query);
221        let results = find_similar_texts(query, &episodes, 3, 0.5);
222
223        tracing::debug!("Top {} similar episodes:", results.len());
224        for (i, (idx, similarity, text)) in results.iter().enumerate() {
225            tracing::debug!(
226                "  {}. [{}] {} (similarity: {:.3})",
227                i + 1,
228                idx,
229                text,
230                similarity
231            );
232        }
233    }
234
235    // Demonstrate similarity calculation
236    tracing::debug!("Direct Similarity Examples:");
237    let pairs = vec![
238        ("user authentication", "login system"),
239        ("REST API", "web service endpoints"),
240        ("data validation", "input verification"),
241        ("rate limiting", "API throttling"),
242    ];
243
244    for (text1, text2) in pairs {
245        let emb1 = text_to_embedding(text1);
246        let emb2 = text_to_embedding(text2);
247        let similarity = cosine_similarity(&emb1, &emb2);
248        tracing::debug!("  \"{}\" <-> \"{}\" = {:.3}", text1, text2, similarity);
249    }
250
251    tracing::info!("For real semantic search, use memory-core::embeddings modules");
252    tracing::info!("with proper ONNX models and sentence transformers.");
253
254    Ok(())
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_cosine_similarity() {
263        // Identical vectors should have similarity 1.0
264        let vec1 = vec![1.0, 2.0, 3.0];
265        let vec2 = vec![1.0, 2.0, 3.0];
266        let similarity = cosine_similarity(&vec1, &vec2);
267        assert!((similarity - 1.0).abs() < 0.001);
268
269        // Orthogonal vectors should have similarity 0.5 (normalized from 0)
270        let vec3 = vec![1.0, 0.0];
271        let vec4 = vec![0.0, 1.0];
272        let similarity = cosine_similarity(&vec3, &vec4);
273        assert!((similarity - 0.5).abs() < 0.001);
274    }
275
276    #[test]
277    fn test_text_to_embedding() {
278        let embedding1 = text_to_embedding("hello world");
279        let embedding2 = text_to_embedding("hello world");
280        let embedding3 = text_to_embedding("different text");
281
282        // Same text should produce same embedding
283        assert_eq!(embedding1, embedding2);
284
285        // Different text should produce different embedding
286        assert_ne!(embedding1, embedding3);
287
288        // All embeddings should be unit vectors (magnitude ≈ 1.0)
289        let magnitude1: f32 = embedding1.iter().map(|x| x * x).sum::<f32>().sqrt();
290        assert!((magnitude1 - 1.0).abs() < 0.001);
291    }
292
293    #[test]
294    fn test_find_similar_texts() {
295        let candidates = vec![
296            "implement user authentication".to_string(),
297            "create REST API endpoints".to_string(),
298            "add input validation".to_string(),
299            "deploy with Docker".to_string(),
300        ];
301
302        let results = find_similar_texts("user login system", &candidates, 2, 0.0);
303
304        // Should return at most 2 results
305        assert!(results.len() <= 2);
306
307        // Results should be sorted by similarity (highest first)
308        if results.len() > 1 {
309            assert!(results[0].1 >= results[1].1);
310        }
311    }
312
313    #[test]
314    fn test_embedding_config() {
315        let config = EmbeddingConfig::default();
316        assert_eq!(config.similarity_threshold, 0.7);
317        assert_eq!(config.batch_size, 32);
318        assert!(config.cache_embeddings);
319    }
320}