Skip to main content

lc_embeddings/
mock.rs

1// lc-embeddings/src/mock.rs
2//! Mock embeddings implementation for testing.
3//!
4//! Generates deterministic pseudo-random embeddings based on text hash.
5
6use crate::{EmbeddingError, Embeddings};
7use async_trait::async_trait;
8
9/// Mock embeddings for testing purposes.
10///
11/// Generates fixed-pattern embedding vectors based on text hash,
12/// useful for unit tests without real API calls.
13pub struct MockEmbeddings {
14    dimension: usize,
15}
16
17impl MockEmbeddings {
18    /// Creates a new MockEmbeddings with specified dimension.
19    pub fn new(dimension: usize) -> Self {
20        Self { dimension }
21    }
22}
23
24impl Default for MockEmbeddings {
25    fn default() -> Self {
26        Self::new(1536)
27    }
28}
29
30#[async_trait]
31impl Embeddings for MockEmbeddings {
32    async fn embed_query(&self, text: &str) -> Result<Vec<f32>, EmbeddingError> {
33        if text.trim().is_empty() {
34            return Err(EmbeddingError::EmptyInput);
35        }
36
37        // Generate pseudo-random vector based on text hash
38        let hash = Self::hash_text(text);
39        let mut embedding = Vec::with_capacity(self.dimension);
40
41        for i in 0..self.dimension {
42            // Use hash and index to generate pseudo-random values
43            let value = ((hash.wrapping_add(i as u64)) % 1000) as f32 / 1000.0 - 0.5;
44            embedding.push(value);
45        }
46
47        // Normalize
48        let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
49        if norm > 0.0 {
50            for v in &mut embedding {
51                *v /= norm;
52            }
53        }
54
55        Ok(embedding)
56    }
57
58    fn dimension(&self) -> usize {
59        self.dimension
60    }
61
62    fn model_name(&self) -> &str {
63        "mock-embeddings"
64    }
65}
66
67impl MockEmbeddings {
68    /// Simple text hash function
69    fn hash_text(text: &str) -> u64 {
70        let mut hash: u64 = 0;
71        for (i, c) in text.chars().enumerate() {
72            hash = hash.wrapping_add((c as u64).wrapping_mul((i + 1) as u64));
73        }
74        hash
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[tokio::test]
83    async fn test_mock_embedding() {
84        let embeddings = MockEmbeddings::new(128);
85
86        let result = embeddings.embed_query("Hello, world!").await.unwrap();
87        assert_eq!(result.len(), 128);
88
89        // Same text should produce the same vector
90        let result2 = embeddings.embed_query("Hello, world!").await.unwrap();
91        assert_eq!(result, result2);
92
93        // Different text should produce a different vector
94        let result3 = embeddings.embed_query("Different text").await.unwrap();
95        assert_ne!(result, result3);
96    }
97
98    #[tokio::test]
99    async fn test_mock_embedding_empty() {
100        let embeddings = MockEmbeddings::new(128);
101
102        let result = embeddings.embed_query("").await;
103        assert!(result.is_err());
104    }
105
106    #[tokio::test]
107    async fn test_mock_embedding_normalized() {
108        let embeddings = MockEmbeddings::new(128);
109
110        let result = embeddings.embed_query("Test normalization").await.unwrap();
111
112        // Vector should be normalized (norm approximately 1)
113        let norm: f32 = result.iter().map(|x| x * x).sum::<f32>().sqrt();
114        assert!((norm - 1.0).abs() < 0.0001);
115    }
116}