mnemo_core/embedding/
mod.rs1pub mod onnx;
2pub mod openai;
3
4use crate::error::Result;
5
6#[async_trait::async_trait]
7pub trait EmbeddingProvider: Send + Sync {
8 async fn embed(&self, text: &str) -> Result<Vec<f32>>;
9 async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>>;
10 fn dimensions(&self) -> usize;
11
12 fn is_semantic_capable(&self) -> bool {
19 true
20 }
21}
22
23pub struct NoopEmbedding {
24 dimensions: usize,
25}
26
27impl NoopEmbedding {
28 pub fn new(dimensions: usize) -> Self {
29 Self { dimensions }
30 }
31}
32
33#[async_trait::async_trait]
34impl EmbeddingProvider for NoopEmbedding {
35 async fn embed(&self, _text: &str) -> Result<Vec<f32>> {
36 Ok(vec![0.0; self.dimensions])
37 }
38
39 async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
40 Ok(texts.iter().map(|_| vec![0.0; self.dimensions]).collect())
41 }
42
43 fn dimensions(&self) -> usize {
44 self.dimensions
45 }
46
47 fn is_semantic_capable(&self) -> bool {
51 false
52 }
53}
54
55pub struct DeterministicEmbedding {
69 dimensions: usize,
70}
71
72impl DeterministicEmbedding {
73 pub fn new(dimensions: usize) -> Self {
74 Self { dimensions }
75 }
76
77 fn embed_one(&self, text: &str) -> Vec<f32> {
78 let mut v = vec![0f32; self.dimensions];
79 for tok in text.split_whitespace() {
80 let mut h = 0xcbf29ce484222325u64;
82 for b in tok.bytes() {
83 h ^= b as u64;
84 h = h.wrapping_mul(0x100000001b3);
85 }
86 let idx = (h as usize) % self.dimensions.max(1);
87 v[idx] += 1.0;
88 }
89 let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
90 if norm > 0.0 {
91 for x in &mut v {
92 *x /= norm;
93 }
94 }
95 v
96 }
97}
98
99#[async_trait::async_trait]
100impl EmbeddingProvider for DeterministicEmbedding {
101 async fn embed(&self, text: &str) -> Result<Vec<f32>> {
102 Ok(self.embed_one(text))
103 }
104
105 async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
106 Ok(texts.iter().map(|t| self.embed_one(t)).collect())
107 }
108
109 fn dimensions(&self) -> usize {
110 self.dimensions
111 }
112
113 }
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[tokio::test]
121 async fn noop_is_not_semantic_capable_but_deterministic_is() {
122 assert!(!NoopEmbedding::new(8).is_semantic_capable());
123 assert!(DeterministicEmbedding::new(8).is_semantic_capable());
124 }
125
126 #[tokio::test]
127 async fn deterministic_embedding_is_stable_and_nonzero() {
128 let e = DeterministicEmbedding::new(16);
129 let a = e.embed("clinician adjusted the dosage").await.unwrap();
130 let b = e.embed("clinician adjusted the dosage").await.unwrap();
131 assert_eq!(a, b, "same text embeds identically");
132 assert_eq!(a.len(), 16);
133 assert!(a.iter().any(|x| *x != 0.0), "produces non-zero vectors");
134 }
135}