use crate::adapter::{ModelAdapter, ModelError, ModelRequest, ModelResponse, StructuredRequest};
use std::collections::HashMap;
use std::sync::Arc;
pub struct ModelRegistry {
adapters: HashMap<String, Arc<dyn ModelAdapter>>,
prefix_routes: Vec<(String, String)>,
default: Option<String>,
}
impl ModelRegistry {
pub fn new() -> Self {
Self {
adapters: HashMap::new(),
prefix_routes: Vec::new(),
default: None,
}
}
pub fn register(mut self, adapter: Arc<dyn ModelAdapter>) -> Self {
let name = adapter.system_name().to_string();
self.adapters.insert(name, adapter);
self
}
pub fn route_prefix(mut self, prefix: impl Into<String>, system: impl Into<String>) -> Self {
self.prefix_routes.push((prefix.into(), system.into()));
self
}
pub fn with_default(mut self, system: impl Into<String>) -> Self {
self.default = Some(system.into());
self
}
fn resolve(&self, model: &str) -> Option<Arc<dyn ModelAdapter>> {
for (prefix, system) in &self.prefix_routes {
if model.starts_with(prefix.as_str()) {
if let Some(adapter) = self.adapters.get(system) {
return Some(Arc::clone(adapter));
}
}
}
if let Some(default) = &self.default {
return self.adapters.get(default).map(Arc::clone);
}
if self.adapters.len() == 1 {
return self.adapters.values().next().map(Arc::clone);
}
None
}
pub async fn chat(&self, request: ModelRequest) -> Result<ModelResponse, ModelError> {
let model = request.config.model.clone().unwrap_or_default();
let adapter = self
.resolve(&model)
.ok_or_else(|| ModelError::Network(format!("no adapter for model: {model}")))?;
adapter.chat(request).await
}
pub async fn structured_output(
&self,
request: StructuredRequest,
) -> Result<ModelResponse, ModelError> {
let model = request.config.model.clone().unwrap_or_default();
let adapter = self
.resolve(&model)
.ok_or_else(|| ModelError::Network(format!("no adapter for model: {model}")))?;
adapter.structured_output(request).await
}
}
impl Default for ModelRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn registry_from_env() -> ModelRegistry {
use crate::{
anthropic::AnthropicAdapter, google::GoogleAdapter, ollama::OllamaAdapter,
openai::OpenAiAdapter,
};
let mut registry = ModelRegistry::new()
.route_prefix("claude-", "anthropic")
.route_prefix("gpt-", "openai")
.route_prefix("o1-", "openai")
.route_prefix("o3-", "openai")
.route_prefix("gemini-", "google")
.route_prefix("google/", "google")
.route_prefix("llama", "ollama")
.route_prefix("qwen", "ollama")
.route_prefix("gemma", "ollama")
.route_prefix("phi", "ollama")
.route_prefix("mistral", "ollama")
.route_prefix("codellama", "ollama")
.route_prefix("deepseek", "ollama")
.route_prefix("nomic-", "ollama");
if let Ok(adapter) = AnthropicAdapter::from_env() {
registry = registry.register(Arc::new(adapter));
registry = registry.with_default("anthropic");
}
if let Ok(adapter) = OpenAiAdapter::from_env() {
registry = registry.register(Arc::new(adapter));
if registry.default.is_none() {
registry = registry.with_default("openai");
}
}
if let Ok(adapter) = GoogleAdapter::from_env() {
registry = registry.register(Arc::new(adapter));
if registry.default.is_none() {
registry = registry.with_default("google");
}
}
if let Ok(adapter) = OllamaAdapter::from_env() {
registry = registry.register(Arc::new(adapter));
if registry.default.is_none() {
registry = registry.with_default("ollama");
}
}
registry
}