candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
//! `Qdrant/bm25`'s entry point beside `SparseTextEmbedding`: no encoder, device, dtype or weights, so `build()` and
//! the embed calls return values rather than a `Result` and take no batch size. English only today.

use crate::models::sparse::bm25_embed_document;
use crate::models::sparse::bm25_embed_query;
use crate::models::sparse::stopwords::Stopwords;
use crate::registry;
use crate::registry::sparse::SparseDescriptor;
use crate::sparse::SparseEmbedding;

/// The published English stopword list `Qdrant/bm25` ships as `english.txt`, compiled in.
const ENGLISH_STOPWORDS: &str = include_str!("../models/sparse/english_stopwords.txt");

/// `Qdrant/bm25`: sparse rows computed from stemmed, hashed text with a stopword list compiled into the crate, and
/// no model, device or dtype.
pub struct Bm25TextEmbedding {
  stopwords: Stopwords,
  descriptor: SparseDescriptor,
}

impl Bm25TextEmbedding {
  pub fn builder() -> Bm25TextEmbeddingBuilder {
    Bm25TextEmbeddingBuilder::new()
  }

  pub fn descriptor(&self) -> &SparseDescriptor {
    &self.descriptor
  }

  /// The passage side, the same rows as `passage_embed`.
  pub fn embed<S: AsRef<str>>(&self, texts: &[S]) -> Vec<SparseEmbedding> {
    self.passage_embed(texts)
  }

  /// The passage side: `Bm25.raw_embed`'s own term-frequency saturation, one row per text.
  pub fn passage_embed<S: AsRef<str>>(&self, texts: &[S]) -> Vec<SparseEmbedding> {
    texts
      .iter()
      .map(|text| bm25_embed_document(text.as_ref(), &self.stopwords))
      .collect()
  }

  /// The query side: `Bm25.query_embed`'s own uniform weighting, one row per text.
  pub fn query_embed<S: AsRef<str>>(&self, texts: &[S]) -> Vec<SparseEmbedding> {
    texts
      .iter()
      .map(|text| bm25_embed_query(text.as_ref(), &self.stopwords))
      .collect()
  }
}

/// No options today; a builder anyway, so a later one, such as a language, leaves
/// `Bm25TextEmbedding::builder().build()` unchanged.
pub struct Bm25TextEmbeddingBuilder {
  _private: (),
}

impl Bm25TextEmbeddingBuilder {
  pub(crate) fn new() -> Self {
    Self { _private: () }
  }

  /// Cannot fail: everything the embedder needs is compiled into the crate.
  pub fn build(self) -> Bm25TextEmbedding {
    Bm25TextEmbedding {
      stopwords: Stopwords::parse(ENGLISH_STOPWORDS),
      descriptor: registry::sparse::lookup("Qdrant/bm25")
        .expect("Qdrant/bm25 is a registered sparse entry"),
    }
  }
}

#[cfg(test)]
mod tests;