use crate::embedding::{EMBED_MODEL_ID, EMBEDDING_DIMS, EmbeddingEngine};
const GOLDEN_FILE: &str = "src/embedding/tests/golden_bge_small.txt";
const MAX_ABS_DIFF: f32 = 1e-4;
const GOLDEN_TEXTS: [&str; 10] = [
"hello world",
"the cat sat on the mat",
"Memory stores keep context alive across sessions for AI agents.",
"API endpoint returns 429 Too Many Requests when the rate limit is exceeded.",
"Der Speicher hält den Kontext für KI-Agenten über Sitzungen hinweg bereit.",
"记忆为智能体跨会话保留上下文信息",
"記憶エージェントの文脈をセッション横断で保持する",
"SELECT m.id, m.content FROM memories m WHERE m.status = 'active' ORDER BY m.created_at DESC LIMIT 25;",
"fn main() { let store = MemoryStore::new(&path, model_id, config)?; store.add(\"p\", \"fact\", None)?; }",
"multiple spaces and tabs",
];
fn whitespace_key(raw: &str) -> String {
raw.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn encode_line(text: &str, embedding: &[f32]) -> String {
let mut line = text.to_string();
for value in embedding {
line.push(' ');
line.push_str(&format!("{value:.9e}"));
}
line
}
fn decode_line(line: &str) -> Option<(String, Vec<f32>)> {
let fields: Vec<&str> = line.split(' ').collect();
let split = fields.len().checked_sub(EMBEDDING_DIMS)?;
if split == 0 {
return None;
}
let floats = fields[split..]
.iter()
.map(|token| token.parse::<f32>().ok())
.collect::<Option<Vec<f32>>>()?;
if !floats.iter().all(|v| v.is_finite()) {
return None;
}
Some((fields[..split].join(" "), floats))
}
fn read_fixture_lines() -> Vec<String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let path = std::path::Path::new(&manifest_dir).join(GOLDEN_FILE);
let raw = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("read fixture {}: {e}", path.display()));
raw.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.to_string())
.collect()
}
#[test]
fn test_golden_fixture_shape() {
let lines = read_fixture_lines();
assert_eq!(
lines.len(),
GOLDEN_TEXTS.len(),
"fixture must hold one record per golden text"
);
for (index, line) in lines.iter().enumerate() {
let (text, embedding) = decode_line(line)
.unwrap_or_else(|| panic!("fixture line {} does not parse", index + 1));
assert_eq!(
text,
GOLDEN_TEXTS[index],
"fixture line {} must carry the golden text at that index",
index + 1
);
assert_eq!(
embedding.len(),
EMBEDDING_DIMS,
"fixture line {} must hold exactly {EMBEDDING_DIMS} floats",
index + 1
);
assert!(
embedding.iter().all(|v| v.is_finite()),
"fixture line {} must hold only finite floats",
index + 1
);
let norm = embedding.iter().map(|v| v * v).sum::<f32>().sqrt();
assert!(
(norm - 1.0).abs() <= 1e-3,
"fixture line {} must be L2-normalised (norm {norm})",
index + 1
);
}
}
#[ignore]
#[test]
fn test_golden_embeddings_match_fixture() {
let lines = read_fixture_lines();
assert_eq!(lines.len(), GOLDEN_TEXTS.len());
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID)
.unwrap_or_else(|e| panic!("load model {EMBED_MODEL_ID} for golden comparison: {e}"));
let mut measured_max_diff = 0.0f32;
let mut worst_text = String::new();
for (index, line) in lines.iter().enumerate() {
let (text, expected) = decode_line(line)
.unwrap_or_else(|| panic!("fixture line {} does not parse", index + 1));
assert_eq!(text, GOLDEN_TEXTS[index]);
assert_eq!(
expected.len(),
EMBEDDING_DIMS,
"fixture line {} must hold exactly {EMBEDDING_DIMS} floats",
index + 1
);
let actual = engine
.embed_passage(&text)
.unwrap_or_else(|e| panic!("embed golden text {index}: {e}"));
assert_eq!(actual.len(), EMBEDDING_DIMS);
let diff = actual
.iter()
.zip(expected.iter())
.map(|(a, e)| (a - e).abs())
.fold(0.0f32, f32::max);
println!("golden[{index}] max abs diff = {diff:.3e} text = {text:?}");
if diff > measured_max_diff {
measured_max_diff = diff;
worst_text = text;
}
}
println!(
"golden fixture: measured max abs diff over {} texts = {measured_max_diff:.3e}",
GOLDEN_TEXTS.len()
);
assert!(
measured_max_diff <= MAX_ABS_DIFF,
"golden embedding drift {measured_max_diff:.3e} exceeds {MAX_ABS_DIFF:e} (worst text {worst_text:?}) — \
the runtime changed the vectors; re-embed existing databases with \
`vipune reindex --force --all-projects` and report the measured drift",
);
}
const REGENERATE_ENV_VAR: &str = "VIPUNE_REGENERATE_GOLDEN";
#[ignore]
#[test]
fn golden_fixture_regenerate() {
if std::env::var(REGENERATE_ENV_VAR).as_deref() != Ok("1") {
println!(
"skipped: {REGENERATE_ENV_VAR} is not set, leaving {GOLDEN_FILE} untouched \
(set {REGENERATE_ENV_VAR}=1 to regenerate)"
);
return;
}
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID)
.unwrap_or_else(|e| panic!("load model {EMBED_MODEL_ID} for golden regeneration: {e}"));
let mut out = String::new();
for text in GOLDEN_TEXTS {
let text = whitespace_key(text);
let embedding = engine
.embed_passage(&text)
.unwrap_or_else(|e| panic!("embed {text:?}: {e}"));
assert_eq!(embedding.len(), EMBEDDING_DIMS, "embed {text:?}");
out.push_str(&encode_line(&text, &embedding));
out.push('\n');
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let path = std::path::Path::new(&manifest_dir).join(GOLDEN_FILE);
std::fs::write(&path, out).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
println!(
"wrote {} golden records to {}",
GOLDEN_TEXTS.len(),
path.display()
);
}
#[cfg(test)]
mod unit {
use super::*;
#[test]
fn encode_decode_roundtrips_text_and_floats() {
let embedding: Vec<f32> = (0..EMBEDDING_DIMS)
.map(|i| (i as f32 - 192.0) / 1000.0)
.collect();
for text in GOLDEN_TEXTS {
let text = whitespace_key(text);
assert_eq!(whitespace_key(&text), text, "{text:?} is not canonical");
let line = encode_line(&text, &embedding);
let (decoded_text, decoded_embedding) =
decode_line(&line).expect("generated line parses");
assert_eq!(decoded_text, text);
assert_eq!(
decoded_embedding.len(),
EMBEDDING_DIMS,
"{text:?}: a text word was parsed as a float"
);
let diff = decoded_embedding
.iter()
.zip(embedding.iter())
.map(|(a, e)| (a - e).abs())
.fold(0.0f32, f32::max);
assert!(
diff <= MAX_ABS_DIFF,
"text {text:?} lost precision: {diff:e}"
);
}
}
#[test]
fn whitespace_key_collapses_runs_and_trims() {
assert_eq!(whitespace_key(" a\t\n b c "), "a b c");
assert_eq!(whitespace_key("no-change"), "no-change");
}
#[test]
fn corpus_covers_both_single_word_and_multi_word_texts() {
let multi = GOLDEN_TEXTS.iter().filter(|t| t.contains(' ')).count();
let single = GOLDEN_TEXTS.iter().filter(|t| !t.contains(' ')).count();
assert!(multi >= 2, "corpus must hold multi-word texts");
assert!(single >= 1, "corpus must hold space-free texts");
}
#[test]
fn decode_line_rejects_a_record_with_too_few_fields() {
assert!(decode_line("no floats here").is_none());
}
#[test]
fn decode_line_rejects_a_non_numeric_float_field() {
let line = encode_line("some words", &[0.5f32; EMBEDDING_DIMS - 1]);
assert!(decode_line(&format!("{line} notanumber")).is_none());
}
#[test]
fn decode_line_rejects_non_finite_floats() {
for token in ["nan", "inf", "-inf"] {
let line = encode_line("some words", &[0.5f32; EMBEDDING_DIMS - 1]);
assert!(
decode_line(&format!("{line} {token}")).is_none(),
"{token} must not decode as a fixture float"
);
}
}
#[test]
fn decode_line_keeps_numeric_looking_words_in_the_text() {
let text = "returns 429 too many";
let embedding: Vec<f32> = (0..EMBEDDING_DIMS).map(|i| i as f32).collect();
let line = encode_line(text, &embedding);
let (decoded_text, decoded_embedding) = decode_line(&line).expect("line parses");
assert_eq!(decoded_text, text);
assert_eq!(decoded_embedding, embedding);
}
}