use std::collections::HashSet;
use mentedb_core::types::MemoryId;
pub struct RerankCandidate<'a> {
pub id: MemoryId,
pub content: &'a str,
pub score: f32,
}
pub trait Reranker: Send + Sync {
fn rerank(&self, query: &str, candidates: &[RerankCandidate<'_>]) -> Vec<(MemoryId, f32)>;
}
pub struct LexicalReranker {
overlap_weight: f32,
}
impl Default for LexicalReranker {
fn default() -> Self {
Self {
overlap_weight: 0.5,
}
}
}
impl LexicalReranker {
pub fn new(overlap_weight: f32) -> Self {
Self {
overlap_weight: overlap_weight.clamp(0.0, 1.0),
}
}
}
fn tokens(s: &str) -> HashSet<String> {
s.to_lowercase()
.split(|c: char| !c.is_alphanumeric())
.filter(|t| !t.is_empty())
.map(|t| t.to_string())
.collect()
}
impl Reranker for LexicalReranker {
fn rerank(&self, query: &str, candidates: &[RerankCandidate<'_>]) -> Vec<(MemoryId, f32)> {
let q = tokens(query);
candidates
.iter()
.map(|c| {
let ct = tokens(c.content);
let overlap = if q.is_empty() {
0.0
} else {
q.intersection(&ct).count() as f32 / q.len() as f32
};
let blended = (1.0 - self.overlap_weight) * c.score + self.overlap_weight * overlap;
(c.id, blended)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(n: u128) -> MemoryId {
uuid::Uuid::from_u128(n).into()
}
#[test]
fn lexical_reranker_lifts_exact_term_matches() {
let a = id(1);
let b = id(2);
let cands = vec![
RerankCandidate {
id: a,
content: "a note about gardening tools",
score: 0.9,
},
RerankCandidate {
id: b,
content: "the espresso machine broke",
score: 0.6,
},
];
let rr = LexicalReranker::new(0.8);
let mut scored = rr.rerank("espresso machine", &cands);
scored.sort_by(|x, y| y.1.total_cmp(&x.1));
assert_eq!(scored[0].0, b, "exact-term candidate should rank first");
}
#[test]
fn empty_query_leaves_first_pass_order() {
let a = id(1);
let b = id(2);
let cands = vec![
RerankCandidate {
id: a,
content: "alpha",
score: 0.8,
},
RerankCandidate {
id: b,
content: "beta",
score: 0.5,
},
];
let rr = LexicalReranker::default();
let scored = rr.rerank("", &cands);
assert!(scored[0].1 > scored[1].1);
}
}