pub mod onnx;
pub mod openai;
use crate::error::Result;
#[async_trait::async_trait]
pub trait EmbeddingProvider: Send + Sync {
async fn embed(&self, text: &str) -> Result<Vec<f32>>;
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>>;
fn dimensions(&self) -> usize;
fn is_semantic_capable(&self) -> bool {
true
}
}
pub struct NoopEmbedding {
dimensions: usize,
}
impl NoopEmbedding {
pub fn new(dimensions: usize) -> Self {
Self { dimensions }
}
}
#[async_trait::async_trait]
impl EmbeddingProvider for NoopEmbedding {
async fn embed(&self, _text: &str) -> Result<Vec<f32>> {
Ok(vec![0.0; self.dimensions])
}
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
Ok(texts.iter().map(|_| vec![0.0; self.dimensions]).collect())
}
fn dimensions(&self) -> usize {
self.dimensions
}
fn is_semantic_capable(&self) -> bool {
false
}
}
pub struct DeterministicEmbedding {
dimensions: usize,
}
impl DeterministicEmbedding {
pub fn new(dimensions: usize) -> Self {
Self { dimensions }
}
fn embed_one(&self, text: &str) -> Vec<f32> {
let mut v = vec![0f32; self.dimensions];
for tok in text.split_whitespace() {
let mut h = 0xcbf29ce484222325u64;
for b in tok.bytes() {
h ^= b as u64;
h = h.wrapping_mul(0x100000001b3);
}
let idx = (h as usize) % self.dimensions.max(1);
v[idx] += 1.0;
}
let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in &mut v {
*x /= norm;
}
}
v
}
}
#[async_trait::async_trait]
impl EmbeddingProvider for DeterministicEmbedding {
async fn embed(&self, text: &str) -> Result<Vec<f32>> {
Ok(self.embed_one(text))
}
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
Ok(texts.iter().map(|t| self.embed_one(t)).collect())
}
fn dimensions(&self) -> usize {
self.dimensions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn noop_is_not_semantic_capable_but_deterministic_is() {
assert!(!NoopEmbedding::new(8).is_semantic_capable());
assert!(DeterministicEmbedding::new(8).is_semantic_capable());
}
#[tokio::test]
async fn deterministic_embedding_is_stable_and_nonzero() {
let e = DeterministicEmbedding::new(16);
let a = e.embed("clinician adjusted the dosage").await.unwrap();
let b = e.embed("clinician adjusted the dosage").await.unwrap();
assert_eq!(a, b, "same text embeds identically");
assert_eq!(a.len(), 16);
assert!(a.iter().any(|x| *x != 0.0), "produces non-zero vectors");
}
}