#![cfg(all(feature = "default-embedder", feature = "loader-test-hooks"))]
use fathomdb_embedder::loader::HF_REVISION as PINNED_REVISION;
use fathomdb_embedder::CandleBgeEmbedder;
use fathomdb_embedder_api::Embedder;
fn make_embedder() -> CandleBgeEmbedder {
CandleBgeEmbedder::new().expect("CandleBgeEmbedder::new must succeed")
}
#[test]
fn identity_returns_chosen_model_at_chosen_revision() {
let e = make_embedder();
let id = e.identity();
assert_eq!(id.name, "fathomdb-bge-small-en-v1.5");
assert!(
id.revision.contains(PINNED_REVISION),
"revision {:?} must contain pinned snapshot sha {}",
id.revision,
PINNED_REVISION
);
assert_eq!(id.dimension, 384);
}
#[test]
fn embed_returns_unit_norm_vector() {
let e = make_embedder();
let inputs = [
"the quick brown fox",
"",
"a longer paragraph with multiple sentences. Some commas, periods, etc.",
];
for input in inputs {
let v = e.embed(input).expect("embed must succeed");
let n = v.iter().map(|x| (x as &f32) * x).sum::<f32>().sqrt();
assert!((n - 1.0).abs() < 1e-5, "vector for {input:?} not unit-norm: ‖v‖ = {n}");
}
}
#[test]
fn embed_returns_dimension_correct_vector() {
let e = make_embedder();
let dim = e.identity().dimension as usize;
let v = e.embed("hello world").expect("embed must succeed");
assert_eq!(v.len(), dim);
assert_eq!(dim, 384);
}
#[test]
fn embed_is_deterministic_for_same_input() {
let e = make_embedder();
let a = e.embed("deterministic forward pass").unwrap();
let b = e.embed("deterministic forward pass").unwrap();
assert_eq!(a.len(), b.len());
let a_bytes: Vec<[u8; 4]> = a.iter().map(|x| x.to_le_bytes()).collect();
let b_bytes: Vec<[u8; 4]> = b.iter().map(|x| x.to_le_bytes()).collect();
assert_eq!(a_bytes, b_bytes, "two embeddings of the same input must be bit-identical");
}
fn cosine(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
#[test]
fn embed_two_similar_inputs_high_cosine() {
let e = make_embedder();
let v_cat_a = e.embed("the cat sat on the mat").unwrap();
let v_cat_b = e.embed("a cat is on a mat").unwrap();
let v_unrelated =
e.embed("quantum mechanics dictates the behaviour of subatomic particles").unwrap();
let near = cosine(&v_cat_a, &v_cat_b);
let far_a = cosine(&v_cat_a, &v_unrelated);
let far_b = cosine(&v_cat_b, &v_unrelated);
assert!(
near - far_a > 0.15 && near - far_b > 0.15,
"expected near-pair cosine to dominate by >0.15: near={near}, far_a={far_a}, far_b={far_b}"
);
}
#[test]
fn embed_does_not_panic_on_empty_string() {
let e = make_embedder();
let r = e.embed("");
assert!(r.is_ok(), "embed(\"\") must produce a vector, got {r:?}");
let v = r.unwrap();
let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((n - 1.0).abs() < 1e-5, "empty-string vector not unit-norm: ‖v‖ = {n}");
}
#[test]
fn embed_truncates_documents_over_512_tokens() {
let e = make_embedder();
let long_doc = "lorem ipsum dolor sit amet consectetur ".repeat(700);
let r = e.embed(&long_doc);
assert!(r.is_ok(), "embed of a >512-token document must truncate and succeed, got {r:?}");
let v = r.unwrap();
assert_eq!(v.len(), 384, "truncated embedding must still be dim 384");
let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((n - 1.0).abs() < 1e-5, "long-doc vector not unit-norm: ‖v‖ = {n}");
}
#[test]
fn embed_batch_matches_per_item_embed() {
let e = make_embedder();
let inputs = [
"The Eiffel Tower is a wrought-iron lattice tower in Paris, completed in 1889.",
"Mount Everest is Earth's highest mountain above sea level.",
"Photosynthesis converts sunlight into chemical energy in plants.",
"", ];
let per_item: Vec<Vec<f32>> =
inputs.iter().map(|s| e.embed(s).expect("embed must succeed")).collect();
let batched = e.embed_batch(&inputs).expect("embed_batch must succeed");
assert_eq!(batched.len(), inputs.len(), "one row out per input");
for (i, (b, p)) in batched.iter().zip(&per_item).enumerate() {
assert_eq!(b.len(), 384, "row {i} must be dim 384");
assert_eq!(b.len(), p.len(), "row {i} dim mismatch vs per-item");
let max_abs = b.iter().zip(p).map(|(x, y)| (x - y).abs()).fold(0.0_f32, f32::max);
assert!(
max_abs <= 1e-4,
"row {i}: embed_batch diverges from embed (max abs diff {max_abs} > 1e-4) — \
batching would corrupt stored vectors"
);
}
}
#[test]
fn embed_batch_empty_input_is_empty() {
let e = make_embedder();
assert!(e.embed_batch(&[]).expect("empty batch is Ok").is_empty());
}