use velesdb_memory::DynEmbedder;
pub(crate) struct ConfiguredEmbedder {
pub(crate) embedder: DynEmbedder,
pub(crate) model: String,
}
#[cfg(not(feature = "extractor-http"))]
pub(crate) fn warn_if_extraction_backend_is_unreachable(_backend: &str) {}
#[cfg(feature = "extractor-http")]
pub(crate) const EXTRACTION_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
#[cfg(feature = "extractor-http")]
pub(crate) fn warn_if_extraction_backend_is_unreachable(backend: &str) {
use velesdb_memory::reachability::{probe_openai, warning_line, Reachability};
if std::env::var_os("VELESDB_MEMORY_QUIET").is_some() {
return;
}
let Some((url, model)) = extraction_endpoint_for_probe(backend) else {
return;
};
let token = env_opt("VELESDB_MEMORY_EXTRACTOR_API_TOKEN");
let outcome = probe_openai(&url, &model, token.as_deref(), EXTRACTION_PROBE_TIMEOUT);
if outcome == Reachability::Reachable {
return;
}
if let Some(line) = warning_line("extraction", &url, &model, &outcome) {
eprintln!("{line}");
}
}
#[cfg(feature = "extractor-http")]
pub(crate) fn extraction_endpoint_for_probe(backend: &str) -> Option<(String, String)> {
let endpoint = extractor_endpoint().ok()?;
let url = match backend {
"ollama" => Some(
endpoint
.url
.unwrap_or_else(|| velesdb_memory::extract::DEFAULT_OLLAMA_URL.to_owned()),
),
"openai" => endpoint.url,
_ => None,
}?;
Some((url, endpoint.model?))
}
#[cfg(feature = "extractor-http")]
pub(crate) fn build_remote_extractor(
backend: &str,
) -> Result<velesdb_memory::DynExtractor, Box<dyn std::error::Error>> {
match backend {
"ollama" => build_ollama_extractor(),
"openai" => build_openai_extractor(),
other => Err(unwired_backend("extraction", other).into()),
}
}
#[cfg(not(feature = "extractor-http"))]
pub(crate) fn build_remote_extractor(
backend: &str,
) -> Result<velesdb_memory::DynExtractor, Box<dyn std::error::Error>> {
Err(format!(
"VELESDB_MEMORY_EXTRACTOR={backend} needs a build with `--features extractor-http`; \
for an offline deterministic graph with no rebuild, set \
VELESDB_MEMORY_EXTRACTOR=outline instead"
)
.into())
}
#[cfg(feature = "embedder-http")]
pub(crate) fn embedder_endpoint(
) -> Result<velesdb_memory::RemoteEndpoint, Box<dyn std::error::Error>> {
let (endpoint, notice) = velesdb_memory::embedder_env_endpoint()?;
if let Some(notice) = notice {
if std::env::var_os("VELESDB_MEMORY_QUIET").is_none() {
eprintln!("{notice}");
}
}
Ok(endpoint)
}
#[cfg(feature = "extractor-http")]
pub(crate) fn extractor_endpoint(
) -> Result<velesdb_memory::RemoteEndpoint, Box<dyn std::error::Error>> {
Ok(velesdb_memory::RemoteEndpoint {
url: env_opt("VELESDB_MEMORY_EXTRACTOR_URL"),
model: env_opt("VELESDB_MEMORY_EXTRACTOR_MODEL"),
auth: velesdb_memory::role_auth("VELESDB_MEMORY_EXTRACTOR_API_TOKEN")?,
})
}
#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
pub(crate) fn env_opt(name: &str) -> Option<String> {
std::env::var(name).ok()
}
#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
pub(crate) fn unwired_backend(role: &str, backend: &str) -> String {
format!(
"the {role} backend '{backend}' is accepted by velesdb-memory's selector but \
the daemon has no builder wired for it — this is a bug in velesdb-memory, \
not a configuration error; please report it quoting this message"
)
}
#[cfg(feature = "extractor-http")]
pub(crate) fn build_ollama_extractor(
) -> Result<velesdb_memory::DynExtractor, Box<dyn std::error::Error>> {
use std::sync::Arc;
use velesdb_memory::extract::DEFAULT_OLLAMA_URL;
use velesdb_memory::OllamaExtractor;
let endpoint = extractor_endpoint()?;
let url = endpoint
.url
.unwrap_or_else(|| DEFAULT_OLLAMA_URL.to_owned());
let model = endpoint.model.ok_or(
"VELESDB_MEMORY_EXTRACTOR=ollama requires VELESDB_MEMORY_EXTRACTOR_MODEL \
(e.g. qwen3.6:35b-mlx)",
)?;
Ok(Arc::new(OllamaExtractor::new(url, model)))
}
#[cfg(feature = "extractor-http")]
pub(crate) fn build_openai_extractor(
) -> Result<velesdb_memory::DynExtractor, Box<dyn std::error::Error>> {
use std::sync::Arc;
use velesdb_memory::OpenAiExtractor;
let (url, model, auth) = extractor_endpoint()?.require("VELESDB_MEMORY_EXTRACTOR")?;
Ok(Arc::new(OpenAiExtractor::new(url, model, auth)))
}
pub(crate) fn build_embedder() -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
let backend = std::env::var("VELESDB_MEMORY_EMBEDDER");
build_embedder_selection(backend.as_deref().ok())
}
pub(crate) fn build_migration_target(
backend: &str,
) -> Result<(DynEmbedder, String), velesdb_memory::MemoryError> {
let configured = build_embedder_selection(Some(backend)).map_err(|error| {
velesdb_memory::MemoryError::MigrationCapture(format!(
"cannot configure migration target '{backend}': {error}"
))
})?;
Ok((configured.embedder, configured.model))
}
pub(crate) fn build_embedder_selection(
backend: Option<&str>,
) -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
let selection = velesdb_memory::select_embedder(backend)
.map_err(|err| format!("VELESDB_MEMORY_EMBEDDER: {err}"))?;
match selection {
velesdb_memory::EmbedderSelection::Ready("hash", embedder) => {
warn_hash_embedder_not_semantic();
Ok(ConfiguredEmbedder {
embedder,
model: "hash".to_owned(),
})
}
velesdb_memory::EmbedderSelection::Ready(name, embedder) => Ok(ConfiguredEmbedder {
embedder,
model: name.to_owned(),
}),
velesdb_memory::EmbedderSelection::NeedsRemoteConfig(backend) => {
build_remote_embedder(backend)
}
}
}
#[cfg(feature = "embedder-http")]
pub(crate) fn build_remote_embedder(
backend: &str,
) -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
match backend {
"ollama" => build_ollama_embedder(),
"openai" => build_openai_embedder(),
other => Err(unwired_backend("embedding", other).into()),
}
}
#[cfg(not(feature = "embedder-http"))]
pub(crate) fn build_remote_embedder(
backend: &str,
) -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
Err(format!(
"the '{backend}' embedder requires building with `--features embedder-http` \
(that feature carries the HTTP dependency for both remote embedding \
backends); VELESDB_MEMORY_EMBEDDER=hash needs no rebuild"
)
.into())
}
pub(crate) fn warn_hash_embedder_not_semantic() {
if std::env::var_os("VELESDB_MEMORY_QUIET").is_some() {
return;
}
eprintln!(
"[velesdb-memory] {} For real semantic recall set \
VELESDB_MEMORY_EMBEDDER=ollama or =openai \
(no rebuild needed; see crates/velesdb-memory/README.md for the model \
to pull). Set VELESDB_MEMORY_QUIET=1 to silence this notice.",
velesdb_memory::HASH_EMBEDDER_NOTICE
);
}
#[cfg(feature = "embedder-http")]
pub(crate) fn build_ollama_embedder() -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
use velesdb_memory::{OllamaEmbedder, DEFAULT_OLLAMA_MODEL, DEFAULT_OLLAMA_URL};
let endpoint = embedder_endpoint()?;
let url = endpoint
.url
.unwrap_or_else(|| DEFAULT_OLLAMA_URL.to_owned());
let model = endpoint
.model
.unwrap_or_else(|| DEFAULT_OLLAMA_MODEL.to_owned());
Ok(ConfiguredEmbedder {
embedder: Box::new(OllamaEmbedder::new(&url, &model)?),
model,
})
}
#[cfg(feature = "embedder-http")]
pub(crate) fn build_openai_embedder() -> Result<ConfiguredEmbedder, Box<dyn std::error::Error>> {
use velesdb_memory::OpenAiEmbedder;
let (url, model, auth) = embedder_endpoint()?.require("VELESDB_MEMORY_EMBEDDER")?;
Ok(ConfiguredEmbedder {
embedder: Box::new(OpenAiEmbedder::new(url, &model, auth)?),
model,
})
}