//! Example demonstrating local text embedding usage. //! //! This example shows how to: //! - Create a local text embedder with default configuration (BGE-small) //! - Generate embeddings for sample texts //! - Compute cosine similarity between embeddings //! - Batch process multiple texts //! - Use different models (BGE-base, Nomic, GTE-large) //! //! ## Prerequisites //! //! Before running this example, download the BGE-small model: //! //! ```bash //! mkdir -p ~/.cache/memvid/text-models //! curl -L 'https://huggingface.co/BAAI/bge-small-en-v1.5/resolve/main/onnx/model.onnx' \ //! -o ~/.cache/memvid/text-models/bge-small-en-v1.5.onnx //! curl -L 'https://huggingface.co/BAAI/bge-small-en-v1.5/resolve/main/tokenizer.json' \ //! -o ~/.cache/memvid/text-models/bge-small-en-v1.5_tokenizer.json //! ``` //! //! ## Run //! //! ```bash //! cargo run --example text_embedding --features vec //! ``` use memvid_core::Result; #[cfg(feature = "vec")] use memvid_core::text_embed::{LocalTextEmbedder, TextEmbedConfig}; #[cfg(feature = "vec")] use memvid_core::types::embedding::EmbeddingProvider; /// Compute cosine similarity between two vectors fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 { assert_eq!(a.len(), b.len(), "Vectors must have same length"); let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(); let norm_a: f32 = a.iter().map(|x| x * x).sum::().sqrt(); let norm_b: f32 = b.iter().map(|x| x * x).sum::().sqrt(); if norm_a > 0.0 && norm_b > 0.0 { dot_product / (norm_a * norm_b) } else { 0.0 } } fn main() -> Result<()> { println!("=== Local Text Embedding Example ===\n"); // Create embedder with default config (BGE-small, 384 dimensions) println!("Creating local text embedder (BGE-small-en-v1.5)..."); let config = TextEmbedConfig::default(); let embedder = LocalTextEmbedder::new(config)?; println!("Model: {}", embedder.model()); println!("Kind: {}", embedder.kind()); println!("Dimensions: {}", embedder.dimension()); println!("Ready: {}\n", embedder.is_ready()); // Example 1: Generate embeddings for sample texts println!("--- Example 1: Single Text Embeddings ---"); let text1 = "The quick brown fox jumps over the lazy dog"; let text2 = "A fast auburn canine leaps above an idle hound"; let text3 = "Python is a programming language"; println!("Generating embeddings..."); let emb1 = embedder.embed_text(text1)?; let emb2 = embedder.embed_text(text2)?; let emb3 = embedder.embed_text(text3)?; println!("✓ Generated {} embeddings of dimension {}\n", 3, emb1.len()); // Example 2: Compute semantic similarity println!("--- Example 2: Semantic Similarity ---"); let sim_1_2 = cosine_similarity(&emb1, &emb2); let sim_1_3 = cosine_similarity(&emb1, &emb3); let sim_2_3 = cosine_similarity(&emb2, &emb3); println!("Text 1: \"{}\"", text1); println!("Text 2: \"{}\"", text2); println!("Text 3: \"{}\"", text3); println!(); println!("Similarity (1 ↔ 2): {:.4}", sim_1_2); println!("Similarity (1 ↔ 3): {:.4}", sim_1_3); println!("Similarity (2 ↔ 3): {:.4}", sim_2_3); println!(); if sim_1_2 > sim_1_3 { println!("✓ As expected, similar texts (1 & 2) have higher similarity!"); } println!(); // Example 3: Batch processing println!("--- Example 3: Batch Processing ---"); let documents = vec![ "Artificial intelligence and machine learning", "Deep neural networks for computer vision", "Natural language processing with transformers", "The history of ancient Rome", "Cooking recipes for Italian cuisine", ]; println!("Processing {} documents in batch...", documents.len()); let batch_embeddings = embedder.embed_batch(&documents)?; println!("✓ Generated {} embeddings\n", batch_embeddings.len()); // Find most similar pair println!("Finding most similar document pair..."); let mut max_sim = 0.0; let mut max_pair = (0, 0); for i in 0..batch_embeddings.len() { for j in (i + 1)..batch_embeddings.len() { let sim = cosine_similarity(&batch_embeddings[i], &batch_embeddings[j]); if sim > max_sim { max_sim = sim; max_pair = (i, j); } } } println!("Most similar pair (similarity: {:.4}):", max_sim); println!(" [{}] \"{}\"", max_pair.0, documents[max_pair.0]); println!(" [{}] \"{}\"\n", max_pair.1, documents[max_pair.1]); // Example 4: Search query use case println!("--- Example 4: Search Query ---"); let query = "machine learning algorithms"; let query_emb = embedder.embed_text(query)?; println!("Query: \"{}\"", query); println!("\nRanked results:"); let mut scores: Vec<(usize, f32)> = batch_embeddings .iter() .enumerate() .map(|(i, emb)| (i, cosine_similarity(&query_emb, emb))) .collect(); scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); for (rank, (idx, score)) in scores.iter().take(3).enumerate() { println!(" {}. [{:.4}] \"{}\"", rank + 1, score, documents[*idx]); } println!(); // Example 5: Model unloading (memory management) println!("--- Example 5: Memory Management ---"); println!("Model loaded: {}", embedder.is_loaded()); embedder.unload()?; println!("After unload: {}", embedder.is_loaded()); println!("✓ Model can be lazily reloaded on next use\n"); // Example 6: Using different models (commented out - requires model download) println!("--- Example 6: Different Models ---"); println!("Available models:"); println!(" - bge-small-en-v1.5 (384d) - Default, fast"); println!(" - bge-base-en-v1.5 (768d) - Better quality"); println!(" - nomic-embed-text-v1.5 (768d) - Versatile"); println!(" - gte-large (1024d) - Highest quality"); println!(); println!("To use a different model:"); println!(" let config = TextEmbedConfig::bge_base();"); println!(" let embedder = LocalTextEmbedder::new(config)?;"); println!(); println!("=== Example Complete ==="); println!("\nKey takeaways:"); println!("✓ Local embeddings run entirely offline (no API calls)"); println!("✓ Models are lazy-loaded on first use"); println!("✓ Embeddings are L2-normalized for cosine similarity"); println!("✓ Batch processing is efficient for multiple texts"); println!("✓ Similar texts have higher cosine similarity scores"); Ok(()) }