use std::sync::Arc;
use super::super::definitions::EmbeddingTools;
use crate::mcp::tools::embeddings::types::{ConfigureEmbeddingsInput, ConfigureEmbeddingsOutput};
use anyhow::{Result, anyhow};
use do_memory_core::SelfLearningMemory;
use do_memory_core::embeddings::{
EmbeddingConfig, SelectedEmbeddingStorage, SemanticService,
config::{LocalConfig, ProviderConfig},
};
use tracing::{debug, info, instrument, warn};
#[must_use]
pub fn configured_embedding_storage(
memory: &SelfLearningMemory,
provider_config: &ProviderConfig,
) -> SelectedEmbeddingStorage {
let scope = do_memory_core::embeddings::EmbeddingStorageScope::new(
provider_config.cache_identity(),
provider_config.config_revision(),
);
memory.embedding_storage(scope)
}
impl EmbeddingTools {
#[instrument(skip(self, input), fields(provider = %input.provider))]
pub async fn execute_configure_embeddings(
&self,
input: ConfigureEmbeddingsInput,
) -> Result<ConfigureEmbeddingsOutput> {
info!("Configuring embedding provider: {}", input.provider);
let mut warnings = Vec::new();
let provider_config = parse_provider_config(&input, &mut warnings)?;
let api_key = resolve_api_key(&provider_config, &input.api_key_env, &mut warnings)?;
let SelectedEmbeddingStorage { storage, mode } =
configured_embedding_storage(&self.memory, &provider_config);
let storage_mode = mode.label().to_string();
let storage_scope = mode.scope().key_prefix();
if !mode.is_durable() {
warnings.push(
"Embeddings are held in memory only for this process and will be lost on restart."
.to_string(),
);
}
let embedding_config = EmbeddingConfig {
provider: provider_config.clone(),
similarity_threshold: input.similarity_threshold.unwrap_or(0.7),
batch_size: input.batch_size.unwrap_or(32),
cache_embeddings: true,
timeout_seconds: 30,
..Default::default()
};
let service = match SemanticService::build_exact(
&provider_config,
api_key,
storage,
embedding_config,
)
.await
{
Ok(svc) => svc,
Err(e) => {
warn!("Embedding provider build/probe failed: {}", e);
return Err(anyhow!("Embedding provider activation failed: {}", e));
}
};
let provider_identity = provider_config.cache_identity();
let prev = self
.memory
.activate_semantic_service(Arc::new(service), provider_identity.clone())
.await;
let activation = self
.memory
.embedding_activation()
.await
.expect("activation must be set immediately after activate_semantic_service");
debug!(
revision = activation.revision,
identity = %activation.provider_identity,
reindex = activation.reindex_required,
"Embedding provider activated"
);
drop(prev);
let provider_name = input.provider.clone();
Ok(ConfigureEmbeddingsOutput {
success: true,
provider: input.provider,
model: provider_config.model_name(),
dimension: provider_config.effective_dimension(),
message: format!(
"Activated {} provider with model {} (dimension: {}, revision: {})",
provider_name,
provider_config.model_name(),
provider_config.effective_dimension(),
activation.revision,
),
warnings,
activation_revision: Some(activation.revision),
reindex_required: activation.reindex_required,
provider_health: "active".to_string(),
storage_mode,
storage_scope,
})
}
}
fn parse_provider_config(
input: &ConfigureEmbeddingsInput,
warnings: &mut Vec<String>,
) -> Result<ProviderConfig> {
match input.provider.to_lowercase().as_str() {
"openai" => {
let model_name = input.model.as_deref().unwrap_or("text-embedding-3-small");
let config = match model_name {
"text-embedding-3-small" => ProviderConfig::openai_3_small(),
"text-embedding-3-large" => ProviderConfig::openai_3_large(),
"text-embedding-ada-002" => ProviderConfig::openai_ada_002(),
_ => {
warnings.push(format!(
"Unknown OpenAI model '{}', using text-embedding-3-small",
model_name
));
ProviderConfig::openai_3_small()
}
};
Ok(config)
}
"mistral" => {
let model_name = input.model.as_deref().unwrap_or("mistral-embed");
if model_name != "mistral-embed" {
warnings.push(format!(
"Unknown Mistral model '{}', using mistral-embed",
model_name
));
}
Ok(ProviderConfig::mistral_embed())
}
"local" => {
let model_name = input
.model
.as_deref()
.unwrap_or("sentence-transformers/all-MiniLM-L6-v2");
Ok(ProviderConfig::Local(LocalConfig::new(model_name, 384)))
}
"azure" | "azure_openai" => Err(anyhow!(
"Azure provider is not supported. Supported providers: openai, local, mistral."
)),
"custom" => Err(anyhow!(
"Custom provider is not supported. Supported providers: openai, local, mistral."
)),
"cohere" => Err(anyhow!(
"Cohere provider is not implemented. Supported providers: openai, local, mistral."
)),
_ => Err(anyhow!(
"Unsupported provider: {}. Supported providers: openai, local, mistral",
input.provider
)),
}
}
fn resolve_api_key(
provider_config: &ProviderConfig,
api_key_env: &Option<String>,
warnings: &mut Vec<String>,
) -> Result<Option<String>> {
match provider_config {
ProviderConfig::Local(_) => Ok(None),
ProviderConfig::OpenAI(_) | ProviderConfig::Mistral(_) => {
match api_key_env {
Some(env_var) => match std::env::var(env_var) {
Ok(key) => Ok(Some(key)),
Err(_) => Err(anyhow!(
"Environment variable '{}' not set. Please set the API key.",
env_var
)),
},
None => {
warnings.push(format!(
"No api_key_env specified for {}. The provider will attempt to use its \
default credential environment variable.",
provider_config.model_name()
));
Ok(None)
}
}
}
ProviderConfig::AzureOpenAI(_) | ProviderConfig::Custom(_) => Err(anyhow!(
"Internal error: unsupported provider reached resolve_api_key"
)),
}
}