use async_trait::async_trait;
#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
fn name(&self) -> &str;
fn dimensions(&self) -> usize;
async fn embed(&self, texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>>;
async fn embed_one(&self, text: &str) -> anyhow::Result<Vec<f32>> {
let mut results = self.embed(&[text]).await?;
results
.pop()
.ok_or_else(|| anyhow::anyhow!("Empty embedding result"))
}
}
pub struct NoopEmbedding;
#[async_trait]
impl EmbeddingProvider for NoopEmbedding {
fn name(&self) -> &str {
"none"
}
fn dimensions(&self) -> usize {
0
}
async fn embed(&self, _texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>> {
Ok(Vec::new())
}
}
pub fn create_embedding_provider(
_provider: &str,
_api_key: Option<&str>,
_model: &str,
_dims: usize,
) -> Box<dyn EmbeddingProvider> {
Box::new(NoopEmbedding)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noop_name() {
let p = NoopEmbedding;
assert_eq!(p.name(), "none");
assert_eq!(p.dimensions(), 0);
}
#[tokio::test]
async fn noop_embed_returns_empty() {
let p = NoopEmbedding;
let result = p.embed(&["hello"]).await.unwrap();
assert!(result.is_empty());
}
#[test]
fn factory_always_returns_noop() {
let p = create_embedding_provider("openai", Some("key"), "text-embedding-3-small", 1536);
assert_eq!(p.name(), "none");
assert_eq!(p.dimensions(), 0);
}
#[tokio::test]
async fn noop_embed_one_returns_error() {
let p = NoopEmbedding;
let result = p.embed_one("hello").await;
assert!(result.is_err());
}
#[tokio::test]
async fn noop_embed_empty_batch() {
let p = NoopEmbedding;
let result = p.embed(&[]).await.unwrap();
assert!(result.is_empty());
}
}