use super::*;
use crate::errors::AppError;
use crate::extract::llm_embedding::LlmEmbedding;
use parking_lot::Mutex;
use std::path::Path;
pub fn embed_passage(embedder: &Mutex<LlmEmbedding>, text: &str) -> Result<Vec<f32>, AppError> {
let client = apply_query_timeout_if_needed(clone_client(embedder));
let result = client.embed_passage(text)?;
validate_dim(result)
}
pub fn embed_query(embedder: &Mutex<LlmEmbedding>, text: &str) -> Result<Vec<f32>, AppError> {
let client = apply_query_timeout_if_needed(clone_client(embedder));
let result = client.embed_query(text)?;
validate_dim(result)
}
pub fn embed_passages_controlled(
embedder: &Mutex<LlmEmbedding>,
texts: &[&str],
_token_counts: &[usize],
) -> Result<Vec<Vec<f32>>, AppError> {
if texts.is_empty() {
return Ok(Vec::new());
}
let owned: Vec<String> = texts.iter().map(|t| t.to_string()).collect();
embed_texts_parallel(embedder, &owned, 1, chunk_embed_batch_size())
}
pub fn embed_passage_local(models_dir: &Path, text: &str) -> Result<Vec<f32>, AppError> {
let _slot_guard = acquire_llm_slot_for_embedding()?;
let embedder = get_embedder(models_dir)?;
embed_passage(embedder, text)
}
pub fn should_skip_embedding_on_failure() -> bool {
crate::runtime_config::skip_embedding_on_failure()
}
pub fn embed_passage_or_skip(
models_dir: &Path,
text: &str,
choice: Option<crate::cli::LlmBackendChoice>,
) -> Result<Option<Vec<f32>>, AppError> {
match embed_passage_with_choice(models_dir, text, choice) {
Ok((v, _backend)) => Ok(Some(v)),
Err(AppError::Validation(msg)) => Err(AppError::Validation(msg)),
Err(e) => {
if should_skip_embedding_on_failure() {
tracing::warn!(
error = %e,
"embedding failed but --skip-embedding-on-failure is active; persisting with NULL embedding"
);
Ok(None)
} else {
Err(e)
}
}
}
}
pub fn embed_passage_local_resolved(
models_dir: &Path,
text: &str,
) -> Result<(Vec<f32>, LlmBackendKind), AppError> {
let _slot_guard = acquire_llm_slot_for_embedding()?;
let embedder = get_embedder(models_dir)?;
let v = embed_passage(embedder, text)?;
let kind = match embedder.lock().flavour() {
crate::extract::llm_embedding::EmbeddingFlavour::Codex => LlmBackendKind::Codex,
crate::extract::llm_embedding::EmbeddingFlavour::Claude => LlmBackendKind::Claude,
crate::extract::llm_embedding::EmbeddingFlavour::Opencode => LlmBackendKind::Opencode,
};
Ok((v, kind))
}
pub fn embed_query_local(models_dir: &Path, text: &str) -> Result<Vec<f32>, AppError> {
let _slot_guard = acquire_llm_slot_for_embedding()?;
let embedder = get_embedder(models_dir)?;
embed_query(embedder, text)
}
pub fn embed_passage_with_choice(
models_dir: &Path,
text: &str,
choice: Option<crate::cli::LlmBackendChoice>,
) -> Result<(Vec<f32>, LlmBackendKind), AppError> {
let _slot_guard = acquire_llm_slot_for_embedding()?;
match choice {
None => {
let embedder = get_embedder(models_dir)?;
embed_passage(embedder, text).map(|v| (v, LlmBackendKind::None))
}
Some(choice) => embed_with_fallback(models_dir, text, &choice.to_chain(), false),
}
}
pub fn embed_passage_with_embedding_choice(
models_dir: &Path,
text: &str,
embedding_backend: crate::cli::EmbeddingBackendChoice,
llm_backend: crate::cli::LlmBackendChoice,
) -> Result<(Vec<f32>, LlmBackendKind), AppError> {
let _slot_guard = acquire_llm_slot_for_embedding()?;
let chain = embedding_backend.to_chain(llm_backend);
embed_with_fallback(models_dir, text, &chain, false)
}
pub fn try_embed_query_with_choice(
models_dir: &Path,
text: &str,
choice: Option<crate::cli::LlmBackendChoice>,
) -> Result<(Vec<f32>, LlmBackendKind), FallbackReason> {
match with_query_embed_fast(|| embed_passage_with_choice(models_dir, text, choice)) {
Ok((v, _backend)) if v.is_empty() => Err(FallbackReason::DimZero),
Ok((v, backend)) => Ok((v, backend)),
Err(e) => Err(classify_embedding_error(e)),
}
}
pub fn try_embed_query_with_embedding_choice(
models_dir: &Path,
text: &str,
embedding_backend: crate::cli::EmbeddingBackendChoice,
llm_backend: crate::cli::LlmBackendChoice,
) -> Result<(Vec<f32>, LlmBackendKind), FallbackReason> {
match with_query_embed_fast(|| {
embed_passage_with_embedding_choice(models_dir, text, embedding_backend, llm_backend)
}) {
Ok((v, _backend)) if v.is_empty() => Err(FallbackReason::DimZero),
Ok((v, backend)) => Ok((v, backend)),
Err(e) => Err(classify_embedding_error(e)),
}
}
pub(crate) fn acquire_llm_slot_for_embedding() -> Result<crate::llm_slots::LlmSlotGuard, AppError> {
use crate::constants::{CLI_LOCK_DEFAULT_WAIT_SECS, LLM_WORKER_RSS_MB};
let default_max = crate::llm_slots::default_max_concurrency() as usize;
let max = crate::runtime_config::llm_max_host_concurrency(default_max).max(1) as u32;
let wait_secs = if crate::runtime_config::llm_slot_no_wait() {
0
} else {
crate::runtime_config::llm_slot_wait_secs(CLI_LOCK_DEFAULT_WAIT_SECS)
};
let _ = LLM_WORKER_RSS_MB; match crate::llm_slots::acquire_llm_slot(max, wait_secs) {
Ok(guard) => Ok(guard),
Err(e @ AppError::LockBusy { .. }) if wait_secs > 0 => Err(AppError::Embedding(
crate::i18n::validation::embedding_slot_exhausted(&e),
)),
Err(e) => Err(e),
}
}