use crate::types::Embedder;
pub const BUNDLED_EMBEDDER_DIM: usize = 64;
const POTION_2M_MODEL: &[u8] = include_bytes!("../../assets/potion-base-2M/model.safetensors");
const POTION_2M_TOKENIZER: &[u8] = include_bytes!("../../assets/potion-base-2M/tokenizer.json");
const POTION_2M_CONFIG: &[u8] = include_bytes!("../../assets/potion-base-2M/config.json");
const POTION_2M_MODULES: &[u8] = include_bytes!("../../assets/potion-base-2M/modules.json");
#[derive(Clone)]
pub struct BundledEmbedder {
inner: std::sync::Arc<once_cell_lite::Lazy>,
}
mod once_cell_lite {
use model2vec_rs::model::StaticModel;
use std::sync::OnceLock;
pub struct Lazy {
cell: OnceLock<Result<StaticModel, String>>,
}
impl Lazy {
pub fn new() -> Self {
Self {
cell: OnceLock::new(),
}
}
pub fn get(&self) -> Result<&StaticModel, &str> {
let result = self.cell.get_or_init(|| init_model());
match result {
Ok(m) => Ok(m),
Err(e) => Err(e.as_str()),
}
}
}
fn init_model() -> Result<StaticModel, String> {
use std::io::Write;
let temp = std::env::temp_dir()
.join("yantrikdb")
.join(format!("potion-2M-{}", std::process::id()));
std::fs::create_dir_all(&temp)
.map_err(|e| format!("create temp dir {}: {e}", temp.display()))?;
for (name, bytes) in [
("model.safetensors", super::POTION_2M_MODEL),
("tokenizer.json", super::POTION_2M_TOKENIZER),
("config.json", super::POTION_2M_CONFIG),
("modules.json", super::POTION_2M_MODULES),
] {
let path = temp.join(name);
let need_write = match std::fs::metadata(&path) {
Ok(m) => m.len() != bytes.len() as u64,
Err(_) => true,
};
if need_write {
let mut f = std::fs::File::create(&path)
.map_err(|e| format!("create {}: {e}", path.display()))?;
f.write_all(bytes)
.map_err(|e| format!("write {}: {e}", path.display()))?;
}
}
StaticModel::from_pretrained(&temp, None, None, None)
.map_err(|e| format!("model2vec_rs load: {e}"))
}
}
impl BundledEmbedder {
pub fn new() -> Self {
Self {
inner: std::sync::Arc::new(once_cell_lite::Lazy::new()),
}
}
}
impl Default for BundledEmbedder {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for BundledEmbedder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BundledEmbedder")
.field("model", &"potion-base-2M")
.field("dim", &BUNDLED_EMBEDDER_DIM)
.finish()
}
}
impl Embedder for BundledEmbedder {
fn embed(
&self,
text: &str,
) -> std::result::Result<Vec<f32>, Box<dyn std::error::Error + Send + Sync>> {
let model = self
.inner
.get()
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
Ok(model.encode_single(text))
}
fn embed_batch(
&self,
texts: &[&str],
) -> std::result::Result<Vec<Vec<f32>>, Box<dyn std::error::Error + Send + Sync>> {
let model = self
.inner
.get()
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
let owned: Vec<String> = texts.iter().map(|s| (*s).to_string()).collect();
Ok(model.encode(&owned))
}
fn dim(&self) -> usize {
BUNDLED_EMBEDDER_DIM
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dim_is_64() {
assert_eq!(BundledEmbedder::new().dim(), BUNDLED_EMBEDDER_DIM);
assert_eq!(BUNDLED_EMBEDDER_DIM, 64);
}
#[test]
fn embed_returns_64_dim_vector() {
let e = BundledEmbedder::new();
let v = e.embed("Alice is the engineering lead at Acme").unwrap();
assert_eq!(v.len(), 64);
}
#[test]
fn embed_returns_l2_normalized_vector() {
let e = BundledEmbedder::new();
let v = e.embed("Project Atlas launches in March").unwrap();
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 1e-3, "expected unit-norm; got {norm}");
}
#[test]
fn embed_is_deterministic_across_calls() {
let e = BundledEmbedder::new();
let a = e.embed("Acme Corporation").unwrap();
let b = e.embed("Acme Corporation").unwrap();
assert_eq!(a, b, "same input must yield same vector");
}
#[test]
fn embed_batch_matches_single() {
let e = BundledEmbedder::new();
let inputs = ["one fish", "two fish", "red fish"];
let single: Vec<Vec<f32>> = inputs.iter().map(|t| e.embed(t).unwrap()).collect();
let batch = e.embed_batch(&inputs).unwrap();
assert_eq!(single.len(), batch.len());
for (s, b) in single.iter().zip(batch.iter()) {
assert_eq!(s.len(), b.len(), "dim mismatch single vs batch");
for (x, y) in s.iter().zip(b.iter()) {
assert!(
(x - y).abs() < 1e-5,
"single vs batch divergence: {x} vs {y}"
);
}
}
}
#[test]
fn semantically_similar_texts_score_higher_than_unrelated() {
let e = BundledEmbedder::new();
let a = e.embed("Alice is the engineering lead").unwrap();
let b = e.embed("Alice runs the technical team").unwrap();
let c = e.embed("the lunch menu has pasta today").unwrap();
let cos =
|x: &[f32], y: &[f32]| -> f32 { x.iter().zip(y.iter()).map(|(a, b)| a * b).sum() };
let sim_ab = cos(&a, &b);
let sim_ac = cos(&a, &c);
assert!(
sim_ab > sim_ac,
"semantic match should beat unrelated; sim_ab={sim_ab} sim_ac={sim_ac}"
);
}
}