diff --git a/crates/vestige-core/src/embedder/fastembed.rs b/crates/vestige-core/src/embedder/fastembed.rs index a4cd87b..a6ac120 100644 --- a/crates/vestige-core/src/embedder/fastembed.rs +++ b/crates/vestige-core/src/embedder/fastembed.rs @@ -4,7 +4,7 @@ #[cfg(feature = "embeddings")] use crate::embeddings::{EMBEDDING_DIMENSIONS, EmbeddingService}; -use super::{EmbedderError, EmbedderResult, LocalEmbedder}; +use super::{EmbedderError, EmbedderResult, EmbedderSend}; pub struct FastembedEmbedder { #[cfg(feature = "embeddings")] @@ -41,8 +41,7 @@ impl Default for FastembedEmbedder { } } -#[async_trait::async_trait] -impl LocalEmbedder for FastembedEmbedder { +impl EmbedderSend for FastembedEmbedder { async fn embed(&self, text: &str) -> EmbedderResult> { #[cfg(feature = "embeddings")] { diff --git a/crates/vestige-core/src/embedder/mod.rs b/crates/vestige-core/src/embedder/mod.rs index 9d43d0d..e8e654a 100644 --- a/crates/vestige-core/src/embedder/mod.rs +++ b/crates/vestige-core/src/embedder/mod.rs @@ -1,5 +1,8 @@ //! Text-to-vector encoding trait. Pluggable per-install. +use std::future::Future; +use std::pin::Pin; + mod fastembed; pub use fastembed::FastembedEmbedder; @@ -18,14 +21,23 @@ pub enum EmbedderError { pub type EmbedderResult = std::result::Result; +/// Boxed Send future returning an `EmbedderResult`, bound to the lifetime +/// of the borrows captured by the call. Used as the return type of every +/// async method on the dyn-compatible `Embedder` trait below. +pub type BoxedEmbedderFuture<'a, T> = + Pin> + Send + 'a>>; + /// Pluggable embedder. The storage layer NEVER calls fastembed directly; /// callers compute vectors via this trait and pass them into `MemoryStore`. /// -/// `#[async_trait::async_trait]` makes every `async fn` return a -/// `Pin>`, which is required for `Box` -/// and `Arc` to be dyn-compatible. -#[async_trait::async_trait] -pub trait LocalEmbedder: Send + Sync + 'static { +/// `LocalEmbedder` is the source-of-truth trait declared with native +/// async-fn-in-trait. `#[trait_variant::make(EmbedderSend: Send)]` derives +/// a Send-bounded variant that backends actually implement (the +/// trait_variant 0.1.x blanket goes variant -> source). The dyn-compatible +/// public surface is the `Embedder` trait declared below, which wraps every +/// async method in `Pin>`. +#[trait_variant::make(EmbedderSend: Send)] +pub trait LocalEmbedder: Sync + 'static { async fn embed(&self, text: &str) -> EmbedderResult>; fn model_name(&self) -> &str; @@ -52,6 +64,53 @@ pub trait LocalEmbedder: Send + Sync + 'static { } } -/// Type alias: `Embedder` is the dyn-compatible, Send+Sync variant. -/// Both names refer to the same `async_trait`-annotated trait. -pub use LocalEmbedder as Embedder; +/// Dyn-compatible embedder trait. +/// +/// `EmbedderSend` above is the trait users implement; it uses native +/// async-fn-in-trait return types (RPITIT), which gives zero-allocation +/// static dispatch but is not dyn-safe. This trait wraps every async +/// method in `Pin>` so `Box` +/// and `Arc` work for the cognitive module surface and +/// the Phase 1 integration tests. +/// +/// Implementations should not target this trait directly; the blanket +/// `impl Embedder for T` adapts every Send-variant +/// implementation automatically. +pub trait Embedder: Send + Sync + 'static { + fn embed<'a>(&'a self, text: &'a str) -> BoxedEmbedderFuture<'a, Vec>; + fn embed_batch<'a>( + &'a self, + texts: &'a [&'a str], + ) -> BoxedEmbedderFuture<'a, Vec>>; + fn model_name(&self) -> &str; + fn dimension(&self) -> usize; + fn model_hash(&self) -> String; + fn signature(&self) -> crate::storage::ModelSignature; +} + +impl Embedder for T +where + T: EmbedderSend, +{ + fn embed<'a>(&'a self, text: &'a str) -> BoxedEmbedderFuture<'a, Vec> { + Box::pin(::embed(self, text)) + } + fn embed_batch<'a>( + &'a self, + texts: &'a [&'a str], + ) -> BoxedEmbedderFuture<'a, Vec>> { + Box::pin(::embed_batch(self, texts)) + } + fn model_name(&self) -> &str { + ::model_name(self) + } + fn dimension(&self) -> usize { + ::dimension(self) + } + fn model_hash(&self) -> String { + ::model_hash(self) + } + fn signature(&self) -> crate::storage::ModelSignature { + ::signature(self) + } +} diff --git a/crates/vestige-core/src/lib.rs b/crates/vestige-core/src/lib.rs index f8a35d6..15dbdbf 100644 --- a/crates/vestige-core/src/lib.rs +++ b/crates/vestige-core/src/lib.rs @@ -198,7 +198,9 @@ pub use storage::{ }; // Embedder trait and implementations -pub use embedder::{Embedder, EmbedderError, EmbedderResult, FastembedEmbedder, LocalEmbedder}; +pub use embedder::{ + Embedder, EmbedderError, EmbedderResult, EmbedderSend, FastembedEmbedder, LocalEmbedder, +}; // Consolidation (sleep-inspired memory processing) pub use consolidation::SleepConsolidation;