use engram::embedding::{EmbeddingProvider, MockEmbeddingProvider};
use engram::embedding_ollama::OllamaEmbeddingProvider;
use engram::llm::{LlmClient, MockLlmClient};
use engram::llm_anthropic::AnthropicLlmClient;
use engram::llm_command::CommandLlmClient;
use engram::llm_google::GoogleLlmClient;
use engram::llm_ollama::OllamaLlmClient;
use engram::llm_openai::OpenAiLlmClient;
#[derive(Clone, Debug)]
pub enum LlmBackend {
Mock,
Ollama { base_url: String, model: String },
OpenAiCompatible {
base_url: String,
api_key: String,
model: String,
},
Anthropic {
base_url: String,
api_key: String,
model: String,
},
Google {
base_url: String,
api_key: String,
model: String,
},
Command { command: String, timeout_secs: u64 },
}
impl LlmBackend {
pub fn build(&self) -> Box<dyn LlmClient> {
match self {
Self::Mock => Box::new(MockLlmClient::new(vec![serde_json::json!({"facts": []})])),
Self::Ollama { base_url, model } => Box::new(OllamaLlmClient::with_config(
base_url.clone(),
model.clone(),
)),
Self::OpenAiCompatible {
base_url,
api_key,
model,
} => Box::new(OpenAiLlmClient::with_config(
base_url.clone(),
api_key.clone(),
model.clone(),
)),
Self::Anthropic {
base_url,
api_key,
model,
} => Box::new(AnthropicLlmClient::with_config(
base_url.clone(),
api_key.clone(),
model.clone(),
)),
Self::Google {
base_url,
api_key,
model,
} => Box::new(GoogleLlmClient::with_config(
base_url.clone(),
api_key.clone(),
model.clone(),
)),
Self::Command {
command,
timeout_secs,
} => Box::new(CommandLlmClient::new(command.clone()).with_timeout(*timeout_secs)),
}
}
pub fn describe(&self) -> String {
match self {
Self::Mock => "mock (returns empty facts)".to_string(),
Self::Ollama { base_url, model } => format!("ollama {model} at {base_url}"),
Self::OpenAiCompatible {
base_url, model, ..
} => format!("openai-compatible {model} at {base_url}"),
Self::Anthropic {
base_url, model, ..
} => format!("anthropic {model} at {base_url}"),
Self::Google {
base_url, model, ..
} => format!("google {model} at {base_url}"),
Self::Command {
command,
timeout_secs,
} => {
let shown: String = command.chars().take(60).collect();
format!("command `{shown}` (timeout={timeout_secs}s)")
}
}
}
}
#[derive(Clone, Debug)]
pub enum EmbeddingBackend {
Mock { dims: usize },
Ollama {
base_url: String,
model: String,
dims: usize,
},
}
impl EmbeddingBackend {
pub fn build(&self) -> Box<dyn EmbeddingProvider> {
match self {
Self::Mock { dims } => Box::new(MockEmbeddingProvider::new(*dims)),
Self::Ollama {
base_url,
model,
dims,
} => Box::new(OllamaEmbeddingProvider::with_config(base_url, model, *dims)),
}
}
pub fn dimensions(&self) -> usize {
match self {
Self::Mock { dims } => *dims,
Self::Ollama { dims, .. } => *dims,
}
}
pub fn describe(&self) -> String {
match self {
Self::Mock { dims } => format!("mock ({dims}d)"),
Self::Ollama {
base_url,
model,
dims,
} => format!("ollama {model} at {base_url} ({dims}d)"),
}
}
}
#[derive(Clone, Debug)]
pub struct BackendConfig {
pub llm: LlmBackend,
pub embedding: EmbeddingBackend,
}
impl BackendConfig {
pub fn mock() -> Self {
Self {
llm: LlmBackend::Mock,
embedding: EmbeddingBackend::Mock { dims: 64 },
}
}
pub fn is_mock(&self) -> bool {
matches!(self.llm, LlmBackend::Mock)
|| matches!(self.embedding, EmbeddingBackend::Mock { .. })
}
}