use crate::prefix_diet::{self, MIN_DEDUP_BYTES};
pub const FUZZY_DEDUP_MARKER: &str =
"[AXIOM-FUZZY-DEDUP: near-duplicate of an earlier block in this prompt]";
pub const DEFAULT_FUZZY_THRESHOLD: f32 = 0.93;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct FuzzyDietReport {
pub original_tokens: usize,
pub fuzzy_dedup_tokens: usize,
pub blocks_marked: usize,
pub backend_active: bool,
}
pub trait SimilarityBackend {
fn embed_batch(&self, texts: &[String]) -> Option<Vec<Vec<f32>>>;
}
pub struct NullBackend;
impl SimilarityBackend for NullBackend {
fn embed_batch(&self, _texts: &[String]) -> Option<Vec<Vec<f32>>> {
None
}
}
pub fn cosine(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() || a.is_empty() {
return 0.0;
}
let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
pub fn fuzzy_diet<B: SimilarityBackend>(
text: &str,
backend: &B,
threshold: f32,
) -> (String, FuzzyDietReport) {
let original_tokens = text.split_whitespace().count();
let (exact_deduped, _exact_count) = prefix_diet::diet_with_report(text);
let (blocks, seps) = prefix_diet::split_blocks(&exact_deduped);
let candidate_idx: Vec<usize> = blocks
.iter()
.enumerate()
.filter(|(_, b)| {
let t = b.trim();
t.len() >= MIN_DEDUP_BYTES && t != prefix_diet::DEDUP_MARKER
})
.map(|(i, _)| i)
.collect();
if candidate_idx.is_empty() {
return (
exact_deduped,
FuzzyDietReport {
original_tokens,
..Default::default()
},
);
}
let candidate_texts: Vec<String> = candidate_idx
.iter()
.map(|&i| blocks[i].trim().to_string())
.collect();
let Some(embeddings) = backend.embed_batch(&candidate_texts) else {
return (
exact_deduped,
FuzzyDietReport {
original_tokens,
..Default::default()
},
);
};
let mut kept_reps: Vec<usize> = Vec::new(); let mut is_dup = vec![false; candidate_idx.len()];
for i in 0..embeddings.len() {
let mut matched = false;
for &rep in &kept_reps {
if cosine(&embeddings[i], &embeddings[rep]) >= threshold {
matched = true;
break;
}
}
if matched {
is_dup[i] = true;
} else {
kept_reps.push(i);
}
}
let mut dup_block_idx = std::collections::HashSet::new();
for (candidate_pos, &blk_idx) in candidate_idx.iter().enumerate() {
if is_dup[candidate_pos] {
dup_block_idx.insert(blk_idx);
}
}
let mut result = String::new();
let mut blocks_marked = 0usize;
for (i, b) in blocks.iter().enumerate() {
if i > 0 {
result.push_str(&seps[i - 1]);
}
if dup_block_idx.contains(&i) {
result.push_str(FUZZY_DEDUP_MARKER);
blocks_marked += 1;
} else {
result.push_str(b);
}
}
let fuzzy_dedup_tokens = exact_deduped
.split_whitespace()
.count()
.saturating_sub(result.split_whitespace().count());
(
result,
FuzzyDietReport {
original_tokens,
fuzzy_dedup_tokens,
blocks_marked,
backend_active: true,
},
)
}
pub struct AutoBackend {
inner: Option<Box<dyn SimilarityBackend>>,
pub active_backend: &'static str,
}
impl AutoBackend {
pub fn build() -> Self {
#[cfg(feature = "fuzzy-embed-gpu")]
{
use crate::gpu_embed::bert_backend::{BertEmbedBackend, DEFAULT_GPU_MODEL_REPO};
#[cfg(feature = "cuda")]
if let Ok(device) = candle_core::Device::new_cuda(0) {
if let Ok(b) = BertEmbedBackend::load(DEFAULT_GPU_MODEL_REPO, device) {
return Self {
inner: Some(Box::new(b)),
active_backend: "bert-gpu",
};
}
}
if let Ok(b) = BertEmbedBackend::load(DEFAULT_GPU_MODEL_REPO, candle_core::Device::Cpu)
{
return Self {
inner: Some(Box::new(b)),
active_backend: "bert-cpu",
};
}
}
#[cfg(feature = "fuzzy-embed")]
{
use model2vec_backend::{Model2VecBackend, DEFAULT_MODEL_REPO};
if let Ok(b) = Model2VecBackend::load(DEFAULT_MODEL_REPO) {
return Self {
inner: Some(Box::new(b)),
active_backend: "model2vec-cpu",
};
}
}
Self {
inner: None,
active_backend: "none",
}
}
}
impl SimilarityBackend for AutoBackend {
fn embed_batch(&self, texts: &[String]) -> Option<Vec<Vec<f32>>> {
self.inner.as_ref()?.embed_batch(texts)
}
}
#[cfg(feature = "fuzzy-embed")]
pub mod model2vec_backend {
use super::SimilarityBackend;
use model2vec_rs::model::StaticModel;
pub const DEFAULT_MODEL_REPO: &str = "minishlab/potion-base-8M";
pub struct Model2VecBackend {
model: StaticModel,
}
impl Model2VecBackend {
pub fn load(repo_or_path: &str) -> anyhow::Result<Self> {
let model = StaticModel::from_pretrained(repo_or_path, None, None, None)?;
Ok(Self { model })
}
}
impl SimilarityBackend for Model2VecBackend {
fn embed_batch(&self, texts: &[String]) -> Option<Vec<Vec<f32>>> {
Some(self.model.encode(texts))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn block(byte: char, len: usize) -> String {
let word: String = std::iter::repeat(byte).take(4).collect();
let mut s = String::new();
while s.len() < len {
if !s.is_empty() {
s.push(' ');
}
s.push_str(&word);
}
s.truncate(len);
s
}
#[test]
fn cosine_identical_vectors_is_one() {
let v = vec![1.0, 2.0, 3.0];
assert!((cosine(&v, &v) - 1.0).abs() < 1e-6);
}
#[test]
fn cosine_orthogonal_vectors_is_zero() {
assert!((cosine(&[1.0, 0.0], &[0.0, 1.0])).abs() < 1e-6);
}
#[test]
fn cosine_zero_vector_is_never_a_match() {
assert_eq!(cosine(&[0.0, 0.0], &[1.0, 1.0]), 0.0);
assert_eq!(cosine(&[], &[]), 0.0);
}
#[test]
fn null_backend_falls_back_to_exact_dedup_only() {
let repeated = block('a', 500);
let text = format!("{repeated}\n\nunique\n\n{repeated}");
let (out, report) = fuzzy_diet(&text, &NullBackend, DEFAULT_FUZZY_THRESHOLD);
assert!(out.contains(prefix_diet::DEDUP_MARKER));
assert!(!report.backend_active);
assert_eq!(report.blocks_marked, 0);
}
struct StubBackend {
vectors: std::collections::HashMap<String, Vec<f32>>,
}
impl SimilarityBackend for StubBackend {
fn embed_batch(&self, texts: &[String]) -> Option<Vec<Vec<f32>>> {
texts
.iter()
.map(|t| self.vectors.get(t).cloned())
.collect::<Option<Vec<_>>>()
}
}
#[test]
fn stub_backend_marks_near_duplicate_blocks() {
let a = block('x', 450);
let b = block('y', 450); let c = block('z', 450);
let mut vectors = std::collections::HashMap::new();
vectors.insert(a.trim().to_string(), vec![1.0, 0.0, 0.0]);
vectors.insert(b.trim().to_string(), vec![0.999, 0.001, 0.0]);
vectors.insert(c.trim().to_string(), vec![0.0, 1.0, 0.0]);
let backend = StubBackend { vectors };
let text = format!("{a}\n\n{c}\n\n{b}");
let (out, report) = fuzzy_diet(&text, &backend, 0.9);
assert!(report.backend_active);
assert_eq!(report.blocks_marked, 1, "only the later near-duplicate should be marked");
assert!(out.contains(FUZZY_DEDUP_MARKER));
assert!(out.contains(a.trim()), "first occurrence is kept in full");
assert!(out.contains(c.trim()), "unrelated block is untouched");
}
#[test]
fn stub_backend_leaves_dissimilar_blocks_untouched() {
let a = block('p', 450);
let b = block('q', 450);
let mut vectors = std::collections::HashMap::new();
vectors.insert(a.trim().to_string(), vec![1.0, 0.0]);
vectors.insert(b.trim().to_string(), vec![0.0, 1.0]);
let backend = StubBackend { vectors };
let text = format!("{a}\n\n{b}");
let (out, report) = fuzzy_diet(&text, &backend, 0.9);
assert_eq!(report.blocks_marked, 0);
assert_eq!(out, text);
}
#[test]
fn fuzzy_token_savings_exclude_exact_dedup_savings() {
let repeated = block('r', 500);
let unique = block('u', 500);
let mut vectors = std::collections::HashMap::new();
vectors.insert(repeated.trim().to_string(), vec![1.0, 0.0]);
vectors.insert(unique.trim().to_string(), vec![0.0, 1.0]);
let backend = StubBackend { vectors };
let text = format!("{repeated}\n\n{unique}\n\n{repeated}");
let (out, report) = fuzzy_diet(&text, &backend, 0.9);
assert!(out.contains(prefix_diet::DEDUP_MARKER));
assert!(report.backend_active);
assert_eq!(report.blocks_marked, 0);
assert_eq!(report.fuzzy_dedup_tokens, 0);
}
#[test]
fn short_blocks_are_never_candidates_even_with_a_backend() {
let short = "too short to matter".to_string();
let text = format!("{short}\n\n{short}");
struct AlwaysMatch;
impl SimilarityBackend for AlwaysMatch {
fn embed_batch(&self, texts: &[String]) -> Option<Vec<Vec<f32>>> {
Some(texts.iter().map(|_| vec![1.0]).collect())
}
}
let (out, report) = fuzzy_diet(&text, &AlwaysMatch, 0.5);
assert_eq!(report.blocks_marked, 0);
assert_eq!(out, text);
}
#[test]
#[cfg(feature = "fuzzy-embed")]
#[ignore = "needs network access to the Hugging Face Hub on first load; \
run explicitly once a model cache is warm, matching the \
convention semantic_harness.rs / cvm_eval.rs already use for \
network- or credential-gated tests"]
fn real_backend_loads_and_embeds() {
use super::model2vec_backend::Model2VecBackend;
let backend = Model2VecBackend::load(super::model2vec_backend::DEFAULT_MODEL_REPO)
.expect("model load");
let out = backend
.embed_batch(&["hello world".to_string()])
.expect("embeddings");
assert_eq!(out.len(), 1);
assert!(!out[0].is_empty());
}
#[test]
fn auto_backend_degrades_gracefully_with_no_features_compiled() {
let auto = AutoBackend::build();
#[cfg(not(any(feature = "fuzzy-embed", feature = "fuzzy-embed-gpu")))]
{
assert_eq!(auto.active_backend, "none");
assert!(auto.embed_batch(&["x".to_string()]).is_none());
}
let _ = auto.embed_batch(&["a warm-up call".to_string()]);
}
}