Skip to main content

clawft_core/embeddings/
mod.rs

1//! Embedding trait definitions and implementations.
2//!
3//! Provides the [`Embedder`] trait for generating vector embeddings from text,
4//! plus a [`hash_embedder::HashEmbedder`] that uses SimHash for local,
5//! deterministic embeddings with no API calls required.
6//!
7//! All types in this module are gated behind the `vector-memory` feature flag.
8
9#[cfg(feature = "vector-memory")]
10pub mod hash_embedder;
11#[cfg(feature = "vector-memory")]
12pub mod hnsw_store;
13#[cfg(feature = "vector-memory")]
14pub mod micro_hnsw;
15
16#[cfg(feature = "rvf")]
17pub mod api_embedder;
18#[cfg(feature = "rvf")]
19pub mod progressive;
20#[cfg(feature = "rvf")]
21pub mod quantization;
22#[cfg(feature = "rvf")]
23pub mod rvf_io;
24#[cfg(feature = "rvf")]
25pub mod rvf_stub;
26#[cfg(feature = "rvf")]
27pub mod witness;
28
29use async_trait::async_trait;
30use std::fmt;
31
32/// Errors that can occur during embedding generation.
33#[non_exhaustive]
34#[derive(Debug)]
35pub enum EmbeddingError {
36    /// The input text could not be processed.
37    InvalidInput(String),
38    /// An internal error occurred in the embedder.
39    Internal(String),
40}
41
42impl fmt::Display for EmbeddingError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            EmbeddingError::InvalidInput(msg) => write!(f, "invalid input: {msg}"),
46            EmbeddingError::Internal(msg) => write!(f, "internal error: {msg}"),
47        }
48    }
49}
50
51impl std::error::Error for EmbeddingError {}
52
53/// Trait for generating vector embeddings from text.
54///
55/// Implementations can be local (e.g. SimHash) or remote (e.g. OpenAI embeddings API).
56/// All implementations must be `Send + Sync` for use across async tasks.
57#[async_trait]
58pub trait Embedder: Send + Sync {
59    /// Generate a vector embedding for the given text.
60    async fn embed(&self, text: &str) -> Result<Vec<f32>, EmbeddingError>;
61
62    /// Batch embed multiple texts.
63    ///
64    /// Default implementation calls [`embed`](Embedder::embed) for each text sequentially.
65    async fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, EmbeddingError> {
66        let mut results = Vec::with_capacity(texts.len());
67        for text in texts {
68            results.push(self.embed(text).await?);
69        }
70        Ok(results)
71    }
72
73    /// Return the dimensionality of embeddings produced by this embedder.
74    fn dimension(&self) -> usize;
75
76    /// Return the name/identifier of this embedder (e.g. "hash", "openai-text-embedding-3-small").
77    ///
78    /// Used for logging and configuration. Default returns "unknown".
79    fn name(&self) -> &str {
80        "unknown"
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn embedding_error_display_invalid_input() {
90        let err = EmbeddingError::InvalidInput("bad text".into());
91        assert_eq!(format!("{err}"), "invalid input: bad text");
92    }
93
94    #[test]
95    fn embedding_error_display_internal() {
96        let err = EmbeddingError::Internal("something broke".into());
97        assert_eq!(format!("{err}"), "internal error: something broke");
98    }
99
100    #[test]
101    fn embedding_error_is_error_trait() {
102        let err: Box<dyn std::error::Error> = Box::new(EmbeddingError::Internal("test".into()));
103        assert!(err.to_string().contains("test"));
104    }
105}