construct/memory/
embeddings.rs1use async_trait::async_trait;
9
10#[async_trait]
12pub trait EmbeddingProvider: Send + Sync {
13 fn name(&self) -> &str;
15
16 fn dimensions(&self) -> usize;
18
19 async fn embed(&self, texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>>;
21
22 async fn embed_one(&self, text: &str) -> anyhow::Result<Vec<f32>> {
24 let mut results = self.embed(&[text]).await?;
25 results
26 .pop()
27 .ok_or_else(|| anyhow::anyhow!("Empty embedding result"))
28 }
29}
30
31pub struct NoopEmbedding;
34
35#[async_trait]
36impl EmbeddingProvider for NoopEmbedding {
37 fn name(&self) -> &str {
38 "none"
39 }
40
41 fn dimensions(&self) -> usize {
42 0
43 }
44
45 async fn embed(&self, _texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>> {
46 Ok(Vec::new())
47 }
48}
49
50pub fn create_embedding_provider(
57 _provider: &str,
58 _api_key: Option<&str>,
59 _model: &str,
60 _dims: usize,
61) -> Box<dyn EmbeddingProvider> {
62 Box::new(NoopEmbedding)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn noop_name() {
71 let p = NoopEmbedding;
72 assert_eq!(p.name(), "none");
73 assert_eq!(p.dimensions(), 0);
74 }
75
76 #[tokio::test]
77 async fn noop_embed_returns_empty() {
78 let p = NoopEmbedding;
79 let result = p.embed(&["hello"]).await.unwrap();
80 assert!(result.is_empty());
81 }
82
83 #[test]
84 fn factory_always_returns_noop() {
85 let p = create_embedding_provider("openai", Some("key"), "text-embedding-3-small", 1536);
86 assert_eq!(p.name(), "none");
87 assert_eq!(p.dimensions(), 0);
88 }
89
90 #[tokio::test]
91 async fn noop_embed_one_returns_error() {
92 let p = NoopEmbedding;
93 let result = p.embed_one("hello").await;
94 assert!(result.is_err());
95 }
96
97 #[tokio::test]
98 async fn noop_embed_empty_batch() {
99 let p = NoopEmbedding;
100 let result = p.embed(&[]).await.unwrap();
101 assert!(result.is_empty());
102 }
103}