use semantic_memory::{MemoryStore, MemoryConfig, EmbeddingConfig, SearchConfig};
use semantic_memory::embedder::{OllamaEmbedder, MockEmbedder, Embedder};
#[cfg(feature = "candle-embedder")]
use semantic_memory::embedder::CandleEmbedder;
use std::path::PathBuf;
use tokio::runtime::Handle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmbedderBackend {
Candle,
Ollama,
Mock,
}
impl Default for EmbedderBackend {
fn default() -> Self {
#[cfg(feature = "candle-embedder")]
{
EmbedderBackend::Candle
}
#[cfg(not(feature = "candle-embedder"))]
{
EmbedderBackend::Ollama
}
}
}
impl std::str::FromStr for EmbedderBackend {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"candle" => Ok(EmbedderBackend::Candle),
"ollama" => Ok(EmbedderBackend::Ollama),
"mock" => Ok(EmbedderBackend::Mock),
other => Err(format!(
"unknown embedder '{other}', expected: candle, ollama, or mock"
)),
}
}
}
pub struct MemoryBridge {
pub store: MemoryStore,
}
pub struct BridgeConfig {
pub memory_dir: PathBuf,
pub embedder_backend: EmbedderBackend,
pub embedding_url: String,
pub embedding_model: String,
pub embedding_dims: usize,
}
impl BridgeConfig {
pub fn from_args(
memory_dir: &str,
embedder_backend: EmbedderBackend,
embedding_url: Option<&str>,
embedding_model: Option<&str>,
embedding_dims: Option<usize>,
) -> Self {
Self {
memory_dir: PathBuf::from(memory_dir),
embedder_backend,
embedding_url: embedding_url.unwrap_or("http://localhost:11434").to_string(),
embedding_model: embedding_model.unwrap_or("nomic-embed-text").to_string(),
embedding_dims: embedding_dims.unwrap_or(768),
}
}
}
impl MemoryBridge {
pub fn open(config: BridgeConfig) -> anyhow::Result<Self> {
let embedding_config = EmbeddingConfig {
ollama_url: config.embedding_url,
model: config.embedding_model,
dimensions: config.embedding_dims,
batch_size: 32,
timeout_secs: 60,
};
let embedder: Box<dyn Embedder> = match config.embedder_backend {
EmbedderBackend::Mock => {
Box::new(MockEmbedder::new(config.embedding_dims))
}
EmbedderBackend::Ollama => {
Box::new(OllamaEmbedder::try_new(&embedding_config)?)
}
EmbedderBackend::Candle => {
#[cfg(feature = "candle-embedder")]
{
let hf_model_id = match embedding_config.model.as_str() {
"nomic-embed-text" | "nomic-embed-text-v1.5" => {
"nomic-ai/nomic-embed-text-v1.5"
}
other => other,
};
let candle_config = EmbeddingConfig {
model: hf_model_id.to_string(),
..embedding_config.clone()
};
Box::new(CandleEmbedder::try_new(&candle_config)?)
}
#[cfg(not(feature = "candle-embedder"))]
{
anyhow::bail!(
"candle embedder requested but the 'candle-embedder' feature \
is not enabled. Rebuild with --features candle-embedder \
or use --embedder ollama"
)
}
}
};
let mem_config = MemoryConfig {
base_dir: config.memory_dir,
embedding: embedding_config,
search: SearchConfig::default(),
..Default::default()
};
let store = MemoryStore::open_with_embedder(mem_config, embedder)?;
Ok(Self { store })
}
pub fn handle() -> Handle {
Handle::current()
}
}