use super::*;
use crate::errors::AppError;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LlmBackendKind {
OpenRouter,
None,
}
impl LlmBackendKind {
pub fn as_str(self) -> &'static str {
match self {
Self::OpenRouter => "openrouter",
Self::None => "none",
}
}
}
pub(crate) fn backend_ready_probe(backend: &LlmBackendKind) -> Result<(), AppError> {
match backend {
LlmBackendKind::None => Ok(()),
LlmBackendKind::OpenRouter => {
if OPENROUTER_CLIENT.get().is_some() {
Ok(())
} else {
Err(AppError::Embedding(
crate::i18n::validation::embedding_openrouter_probe_not_initialised(),
))
}
}
}
}
pub fn embed_via_backend(
_models_dir: &Path,
text: &str,
backend: &LlmBackendKind,
) -> Result<(Vec<f32>, LlmBackendKind), AppError> {
match backend {
LlmBackendKind::None => Ok((Vec::new(), LlmBackendKind::None)),
LlmBackendKind::OpenRouter => {
tracing::debug!(
target: "embedder",
backend = "openrouter",
"embed_via_backend: using OpenRouter API (v1.0.93)"
);
let client = OPENROUTER_CLIENT.get().ok_or_else(|| {
AppError::Embedding(
crate::i18n::validation::embedding_openrouter_client_not_initialised(),
)
})?;
let vec = match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| {
handle.block_on(client.embed_single(text, client.default_input_type()))
})
.map_err(super::embed_error::app_error_preserving_retry_class)?,
Err(_) => shared_runtime()?
.block_on(client.embed_single(text, client.default_input_type()))
.map_err(super::embed_error::app_error_preserving_retry_class)?,
};
Ok((vec, LlmBackendKind::OpenRouter))
}
}
}
pub fn embed_via_backend_strict(
models_dir: &Path,
text: &str,
backend: &LlmBackendKind,
last_err: Option<&AppError>,
skip_on_failure: bool,
) -> Result<(Vec<f32>, LlmBackendKind), AppError> {
use crate::llm::exit_code_hints::LlmBackendError;
match backend {
LlmBackendKind::None => {
if last_err.is_none() || skip_on_failure {
Ok((Vec::new(), LlmBackendKind::None))
} else {
Err(match last_err {
Some(e) => super::embed_error::embedding_error_with_class_of(
crate::i18n::validation::embedding_detail(e),
e,
),
None => AppError::Embedding(crate::i18n::validation::embedding_detail(
LlmBackendError::NoBackendsAvailable,
)),
})
}
}
LlmBackendKind::OpenRouter => embed_via_backend(models_dir, text, backend),
}
}
pub fn embed_via_backend_legacy(
models_dir: &Path,
text: &str,
backend: &LlmBackendKind,
) -> Result<Vec<f32>, AppError> {
embed_via_backend(models_dir, text, backend).map(|(v, _)| v)
}
pub fn f32_to_bytes(v: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(v.len() * 4);
for f in v {
out.extend_from_slice(&f.to_le_bytes());
}
out
}
pub fn bytes_to_f32(bytes: &[u8]) -> Vec<f32> {
let mut out = Vec::with_capacity(bytes.len() / 4);
for chunk in bytes.chunks_exact(4) {
out.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
out
}
pub fn embedding_dim() -> usize {
crate::constants::embedding_dim()
}