Files
1jehuang--jcode/crates/jcode-base/src/embedding_stub.rs
T
wehub-resource-sync a789495a98
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
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

234 lines
6.2 KiB
Rust

//! 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
}