1use async_trait::async_trait;
13use thiserror::Error;
14
15#[derive(Debug, Error)]
21pub enum EmbedderError {
22 #[error("provider error: {0}")]
24 Provider(String),
25
26 #[error("embedding dim mismatch: expected {expected}, got {actual}")]
28 DimMismatch {
29 expected: usize,
31 actual: usize,
33 },
34
35 #[error("internal error: {0}")]
37 Internal(String),
38}
39
40pub type EmbedderResult<T> = std::result::Result<T, EmbedderError>;
42
43#[async_trait]
48pub trait Embedder: Send + Sync {
49 fn dim(&self) -> usize;
51
52 fn model_name(&self) -> &str;
54
55 async fn embed(&self, text: &str) -> EmbedderResult<Vec<f32>>;
57
58 async fn embed_batch(&self, texts: &[String]) -> EmbedderResult<Vec<Vec<f32>>> {
63 let mut out = Vec::with_capacity(texts.len());
64 for t in texts {
65 out.push(self.embed(t).await?);
66 }
67 Ok(out)
68 }
69}
70
71#[derive(Debug, Clone)]
78pub struct MockEmbedder {
79 dim: usize,
80 model: String,
81}
82
83impl MockEmbedder {
84 pub fn new() -> Self {
87 Self {
88 dim: 384,
89 model: "mock-384".into(),
90 }
91 }
92
93 pub fn with_dim(dim: usize) -> Self {
96 Self {
97 dim,
98 model: format!("mock-{dim}"),
99 }
100 }
101}
102
103impl Default for MockEmbedder {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109#[async_trait]
110impl Embedder for MockEmbedder {
111 fn dim(&self) -> usize {
112 self.dim
113 }
114
115 fn model_name(&self) -> &str {
116 &self.model
117 }
118
119 async fn embed(&self, text: &str) -> EmbedderResult<Vec<f32>> {
120 let mut seed: u64 = 0xcbf29ce484222325;
121 for b in text.bytes() {
122 seed ^= b as u64;
123 seed = seed.wrapping_mul(0x100000001b3);
124 }
125 let mut out = Vec::with_capacity(self.dim);
126 for i in 0..self.dim {
127 seed = seed
128 .wrapping_mul(6364136223846793005)
129 .wrapping_add(1442695040888963407);
130 let f = ((seed >> (i % 32)) as i32) as f32 / i32::MAX as f32;
131 out.push(f.clamp(-1.0, 1.0));
132 }
133 Ok(out)
134 }
135}
136
137impl From<EmbedderError> for crate::MemoryError {
138 fn from(e: EmbedderError) -> Self {
139 match e {
140 EmbedderError::DimMismatch { expected, actual } => crate::MemoryError::InvalidArgument(
141 format!("embedding dim mismatch: expected {expected}, got {actual}"),
142 ),
143 EmbedderError::Provider(msg) => crate::MemoryError::Provider(msg),
144 EmbedderError::Internal(msg) => crate::MemoryError::Internal(msg),
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[tokio::test]
154 async fn mock_embedder_deterministic() {
155 let e = MockEmbedder::new();
156 let a = e.embed("hello").await.unwrap();
157 let b = e.embed("hello").await.unwrap();
158 assert_eq!(a, b);
159 assert_eq!(a.len(), 384);
160 }
161
162 #[tokio::test]
163 async fn mock_embedder_different_for_different_input() {
164 let e = MockEmbedder::new();
165 let a = e.embed("hello").await.unwrap();
166 let b = e.embed("world").await.unwrap();
167 assert_ne!(a, b);
168 }
169
170 #[tokio::test]
171 async fn mock_embedder_with_dim_honors_dim() {
172 let e = MockEmbedder::with_dim(8);
173 let v = e.embed("hi").await.unwrap();
174 assert_eq!(v.len(), 8);
175 assert_eq!(e.dim(), 8);
176 }
177
178 #[tokio::test]
179 async fn batch_default_works() {
180 let e = MockEmbedder::new();
181 let v = e
182 .embed_batch(&["a".to_string(), "b".to_string()])
183 .await
184 .unwrap();
185 assert_eq!(v.len(), 2);
186 assert_eq!(v[0].len(), 384);
187 }
188}