use std::sync::Arc;
use smooth_operator::embedding::{DeterministicEmbedder, Embedder};
#[cfg(feature = "postgres")]
use smooth_operator_adapter_postgres::GatewayEmbedder;
#[cfg(feature = "postgres")]
pub use smooth_operator_adapter_postgres::OPENAI_SMALL_EMBEDDING_DIM;
#[cfg(not(feature = "postgres"))]
pub const OPENAI_SMALL_EMBEDDING_DIM: usize = 1536;
#[derive(Debug, Clone)]
pub struct EmbedderConfig {
pub gateway_url: String,
pub gateway_key: Option<String>,
pub model: String,
}
impl EmbedderConfig {
#[must_use]
pub fn from_server_config(config: &crate::config::ServerConfig) -> Self {
Self {
gateway_url: config.gateway_url.clone(),
gateway_key: config.gateway_key.clone(),
model: DEFAULT_EMBEDDING_MODEL.to_string(),
}
}
}
pub const DEFAULT_EMBEDDING_MODEL: &str = "text-embedding-3-small";
#[must_use]
pub fn build_embedder(config: &EmbedderConfig) -> Arc<dyn Embedder> {
match &config.gateway_key {
#[cfg(feature = "postgres")]
Some(key) if !key.trim().is_empty() => {
tracing::info!(
model = %config.model,
"using GatewayEmbedder (semantic, 1536-d) for retrieval"
);
Arc::new(GatewayEmbedder::new(
config.gateway_url.clone(),
key.clone(),
config.model.clone(),
OPENAI_SMALL_EMBEDDING_DIM,
))
}
_ => {
tracing::warn!(
"using non-semantic DeterministicEmbedder (FNV-1a hash, 1024-d) — \
set SMOOAI_GATEWAY_KEY for real semantic retrieval"
);
Arc::new(DeterministicEmbedder::new())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use smooth_operator::embedding::DEFAULT_EMBEDDING_DIM;
fn cfg(key: Option<&str>) -> EmbedderConfig {
EmbedderConfig {
gateway_url: "https://example.test/v1".into(),
gateway_key: key.map(str::to_string),
model: DEFAULT_EMBEDDING_MODEL.to_string(),
}
}
#[cfg(feature = "postgres")]
#[test]
fn keyed_config_selects_gateway_embedder_1536() {
let embedder = build_embedder(&cfg(Some("sk-test")));
assert_eq!(
embedder.dim(),
OPENAI_SMALL_EMBEDDING_DIM,
"keyed config must select the 1536-d GatewayEmbedder"
);
}
#[cfg(not(feature = "postgres"))]
#[test]
fn keyed_config_falls_back_to_deterministic_on_lean_build() {
let embedder = build_embedder(&cfg(Some("sk-test")));
assert_eq!(
embedder.dim(),
DEFAULT_EMBEDDING_DIM,
"lean build (no postgres feature) has no GatewayEmbedder; key is ignored"
);
}
#[test]
fn unkeyed_config_falls_back_to_deterministic_1024() {
let embedder = build_embedder(&cfg(None));
assert_eq!(
embedder.dim(),
DEFAULT_EMBEDDING_DIM,
"unkeyed config must fall back to the 1024-d DeterministicEmbedder"
);
}
#[test]
fn empty_or_whitespace_key_falls_back_to_deterministic() {
assert_eq!(build_embedder(&cfg(Some(""))).dim(), DEFAULT_EMBEDDING_DIM);
assert_eq!(
build_embedder(&cfg(Some(" "))).dim(),
DEFAULT_EMBEDDING_DIM
);
}
}