chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
//! Stub embedding module when the `embeddings` feature is disabled.
|
||||
//!
|
||||
//! Provides the same public API as the real embedding module but all
|
||||
//! operations return errors or no-ops.
|
||||
|
||||
use crate::logging;
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::time::Duration;
|
||||
|
||||
pub type EmbeddingVec = Vec<f32>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TopKItem<T> {
|
||||
score: f32,
|
||||
ordinal: usize,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> PartialEq for TopKItem<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.score.to_bits() == other.score.to_bits() && self.ordinal == other.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for TopKItem<T> {}
|
||||
|
||||
impl<T> PartialOrd for TopKItem<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Ord for TopKItem<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.score
|
||||
.total_cmp(&other.score)
|
||||
.then_with(|| self.ordinal.cmp(&other.ordinal))
|
||||
}
|
||||
}
|
||||
|
||||
fn top_k_scored<T, I>(items: I, limit: usize) -> Vec<(T, f32)>
|
||||
where
|
||||
I: IntoIterator<Item = (T, f32)>,
|
||||
{
|
||||
if limit == 0 {
|
||||
logging::debug("embedding top_k requested with zero limit");
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut heap: BinaryHeap<Reverse<TopKItem<T>>> = BinaryHeap::new();
|
||||
for (ordinal, (value, score)) in items.into_iter().enumerate() {
|
||||
let candidate = Reverse(TopKItem {
|
||||
score,
|
||||
ordinal,
|
||||
value,
|
||||
});
|
||||
|
||||
if heap.len() < limit {
|
||||
heap.push(candidate);
|
||||
continue;
|
||||
}
|
||||
|
||||
let replace = heap
|
||||
.peek()
|
||||
.map(|smallest| score > smallest.0.score)
|
||||
.unwrap_or(false);
|
||||
if replace {
|
||||
heap.pop();
|
||||
heap.push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
let mut results: Vec<_> = heap
|
||||
.into_iter()
|
||||
.map(|Reverse(item)| (item.value, item.score, item.ordinal))
|
||||
.collect();
|
||||
results.sort_by(|a, b| b.1.total_cmp(&a.1).then_with(|| a.2.cmp(&b.2)));
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(value, score, _)| (value, score))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct EmbedderStats {
|
||||
pub loaded: bool,
|
||||
pub model_artifact_bytes: u64,
|
||||
pub tokenizer_artifact_bytes: u64,
|
||||
pub total_artifact_bytes: u64,
|
||||
pub load_count: u64,
|
||||
pub unload_count: u64,
|
||||
pub embed_calls: u64,
|
||||
pub embed_failures: u64,
|
||||
pub total_embed_ms: u64,
|
||||
pub avg_embed_ms: Option<f64>,
|
||||
pub idle_secs: Option<u64>,
|
||||
pub loaded_secs: Option<u64>,
|
||||
pub cache_hits: u64,
|
||||
pub cache_size: usize,
|
||||
pub cache_bytes_estimate: u64,
|
||||
pub embedding_dim: usize,
|
||||
}
|
||||
|
||||
pub struct Embedder;
|
||||
|
||||
impl Embedder {
|
||||
pub fn load() -> Result<Self> {
|
||||
logging::warn("embedding load requested but embeddings feature is disabled");
|
||||
anyhow::bail!("Embeddings feature not compiled in this build")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_embedder() -> Result<std::sync::Arc<Embedder>> {
|
||||
logging::warn("embedding handle requested but embeddings feature is disabled");
|
||||
anyhow::bail!("Embeddings feature not compiled in this build")
|
||||
}
|
||||
|
||||
pub fn embed(text: &str) -> Result<EmbeddingVec> {
|
||||
logging::warn(&format!(
|
||||
"embedding request rejected because embeddings feature is disabled bytes={}",
|
||||
text.len()
|
||||
));
|
||||
anyhow::bail!("Embeddings feature not compiled in this build")
|
||||
}
|
||||
|
||||
pub fn maybe_unload_if_idle(idle_for: Duration) -> bool {
|
||||
logging::debug(&format!(
|
||||
"embedding idle unload skipped in stub idle_secs={}",
|
||||
idle_for.as_secs()
|
||||
));
|
||||
false
|
||||
}
|
||||
|
||||
pub fn unload_now() -> bool {
|
||||
logging::debug("embedding unload skipped in stub");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn stats() -> EmbedderStats {
|
||||
logging::debug("returning embedding stub stats");
|
||||
EmbedderStats {
|
||||
loaded: false,
|
||||
model_artifact_bytes: 0,
|
||||
tokenizer_artifact_bytes: 0,
|
||||
total_artifact_bytes: 0,
|
||||
load_count: 0,
|
||||
unload_count: 0,
|
||||
embed_calls: 0,
|
||||
embed_failures: 0,
|
||||
total_embed_ms: 0,
|
||||
avg_embed_ms: None,
|
||||
idle_secs: None,
|
||||
loaded_secs: None,
|
||||
cache_hits: 0,
|
||||
cache_size: 0,
|
||||
cache_bytes_estimate: 0,
|
||||
embedding_dim: embedding_dim(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
|
||||
if a.len() != b.len() || a.is_empty() {
|
||||
logging::debug(&format!(
|
||||
"embedding cosine similarity returning zero len_a={} len_b={}",
|
||||
a.len(),
|
||||
b.len()
|
||||
));
|
||||
return 0.0;
|
||||
}
|
||||
let dot: f32 = a.iter().zip(b.iter()).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 {
|
||||
logging::debug("embedding cosine similarity returning zero for zero norm vector");
|
||||
return 0.0;
|
||||
}
|
||||
dot / (norm_a * norm_b)
|
||||
}
|
||||
|
||||
pub fn batch_cosine_similarity(query: &[f32], candidates: &[&[f32]]) -> Vec<f32> {
|
||||
let dim = query.len();
|
||||
if dim == 0 || candidates.is_empty() {
|
||||
logging::debug(&format!(
|
||||
"embedding batch similarity short-circuited query_dim={} candidates={}",
|
||||
dim,
|
||||
candidates.len()
|
||||
));
|
||||
return vec![0.0; candidates.len()];
|
||||
}
|
||||
candidates
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if c.len() != dim {
|
||||
0.0
|
||||
} else {
|
||||
c.iter().zip(query.iter()).map(|(a, b)| a * b).sum()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_similar(
|
||||
query: &[f32],
|
||||
candidates: &[EmbeddingVec],
|
||||
threshold: f32,
|
||||
top_k: usize,
|
||||
) -> Vec<(usize, f32)> {
|
||||
logging::debug(&format!(
|
||||
"embedding stub find_similar candidates={} threshold={threshold} top_k={top_k}",
|
||||
candidates.len()
|
||||
));
|
||||
let refs: Vec<&[f32]> = candidates.iter().map(|v| v.as_slice()).collect();
|
||||
let scores = batch_cosine_similarity(query, &refs);
|
||||
top_k_scored(
|
||||
scores
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, score)| *score >= threshold),
|
||||
top_k,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_model_available() -> bool {
|
||||
logging::debug("embedding model availability checked in stub: false");
|
||||
false
|
||||
}
|
||||
|
||||
pub const fn embedding_dim() -> usize {
|
||||
384
|
||||
}
|
||||
Reference in New Issue
Block a user