use async_trait::async_trait;
#[cfg(feature = "local-embeddings")]
use std::path::Path;
use crate::{EmbeddingError, Embeddings};
pub struct BagOfWordsEmbeddings {
dim: usize,
}
impl BagOfWordsEmbeddings {
pub fn new(dim: usize) -> Self {
Self { dim: dim.max(1) }
}
pub fn default_dim() -> Self {
Self::new(256)
}
fn tokenize(text: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
for c in text.chars() {
if c.is_alphanumeric() {
if c.is_ascii() {
current.push(c.to_ascii_lowercase());
} else {
if !current.is_empty() {
tokens.push(std::mem::take(&mut current));
}
tokens.push(c.to_string());
}
} else if !current.is_empty() {
tokens.push(std::mem::take(&mut current));
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}
fn hash(s: &str) -> u64 {
let mut h: u64 = 0xcbf29ce484222325;
for b in s.bytes() {
h ^= b as u64;
h = h.wrapping_mul(0x100000001b3);
}
h
}
fn embed(&self, text: &str) -> Vec<f32> {
let mut v = vec![0.0f32; self.dim];
for token in Self::tokenize(text) {
let idx = (Self::hash(&token) as usize) % self.dim;
v[idx] += 1.0;
}
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in &mut v {
*x /= norm;
}
}
v
}
}
impl Default for BagOfWordsEmbeddings {
fn default() -> Self {
Self::default_dim()
}
}
#[async_trait]
impl Embeddings for BagOfWordsEmbeddings {
async fn embed_query(&self, text: &str) -> Result<Vec<f32>, EmbeddingError> {
if text.trim().is_empty() {
return Err(EmbeddingError::EmptyInput);
}
Ok(self.embed(text))
}
fn dimension(&self) -> usize {
self.dim
}
fn model_name(&self) -> &str {
"local-bow"
}
}
#[cfg(feature = "local-embeddings")]
mod nn;
#[cfg(feature = "local-embeddings")]
pub use nn::{LocalEmbeddings, LocalEmbeddingsBuilder};
#[cfg(test)]
mod tests;