///|
/// Search a query and keep only hits at or above a score threshold.
pub fn MoonEmbedIndex::search_threshold(
self : MoonEmbedIndex,
query : Array[Double],
k : Int,
threshold : Double,
) -> SearchReport {
let report = self.search(query, k)
let hits = []
for hit in report.hits {
if hit.score >= threshold {
hits.push(hit)
}
}
{ ..report, hits, }
}
///|
/// Search with a sentence average embedding. Unknown terms are ignored.
pub fn MoonEmbedIndex::search_text(
self : MoonEmbedIndex,
text : String,
k : Int,
) -> SearchReport {
self.search_terms(text, k)
}
///|
/// Search several queries in one call. The output order matches the input order.
pub fn MoonEmbedIndex::search_many(
self : MoonEmbedIndex,
queries : Array[Array[Double]],
k : Int,
) -> Array[SearchReport] {
let result = []
for query in queries {
result.push(self.search(query, k))
}
result
}
///|
/// Search a token prefix and score each matching vector exactly.
pub fn MoonEmbedIndex::search_prefix(
self : MoonEmbedIndex,
prefix : String,
k : Int,
) -> SearchReport {
if k <= 0 {
return { hits: [], scanned: 0, candidates: 0 }
}
let hits = []
let mut scanned = 0
for record in self.corpus.prefix(prefix, self.corpus.size()) {
insert_hit(hits, { token: record.token, score: 1.0 }, k)
scanned = scanned + 1
}
{ hits, scanned, candidates: scanned }
}
///|
/// Compute recall against an exact result for a query.
pub fn recall_at_k(
approx : SearchReport,
exact : SearchReport,
k : Int,
) -> Double {
if k <= 0 || exact.hits.is_empty() {
return 0.0
}
let limit = if k < exact.hits.length() { k } else { exact.hits.length() }
let mut found = 0
for i in 0.. String {
"cases=\{self.cases}, passed=\{self.passed}, mean_recall=\{self.mean_recall.to_string()}, mean_candidates=\{self.mean_candidates.to_string()}"
}
///|
/// Evaluate top-k membership with exact search as the reference.
pub fn MoonEmbedIndex::evaluate(
self : MoonEmbedIndex,
cases : Array[RetrievalCase],
k : Int,
) -> RetrievalMetrics {
if cases.is_empty() || k <= 0 {
return {
cases: cases.length(),
passed: 0,
mean_recall: 0.0,
mean_candidates: 0.0,
}
}
let mut passed = 0
let mut recall_total = 0.0
let mut candidate_total = 0.0
for case in cases {
let exact = self.search_exact(case.query, k)
let approx = self.search(case.query, k)
let recall = recall_at_k(approx, exact, k)
recall_total = recall_total + recall
candidate_total = candidate_total + approx.candidates.to_double()
let mut case_passed = true
for expected in case.expected {
if !approx.tokens().contains(expected) {
case_passed = false
}
}
if case_passed {
passed = passed + 1
}
}
{
cases: cases.length(),
passed,
mean_recall: recall_total / cases.length().to_double(),
mean_candidates: candidate_total / cases.length().to_double(),
}
}