jamjet_models/
registry.rs1use crate::adapter::{ModelAdapter, ModelError, ModelRequest, ModelResponse, StructuredRequest};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10pub struct ModelRegistry {
16 adapters: HashMap<String, Arc<dyn ModelAdapter>>,
17 prefix_routes: Vec<(String, String)>,
19 default: Option<String>,
20}
21
22impl ModelRegistry {
23 pub fn new() -> Self {
24 Self {
25 adapters: HashMap::new(),
26 prefix_routes: Vec::new(),
27 default: None,
28 }
29 }
30
31 pub fn register(mut self, adapter: Arc<dyn ModelAdapter>) -> Self {
33 let name = adapter.system_name().to_string();
34 self.adapters.insert(name, adapter);
35 self
36 }
37
38 pub fn route_prefix(mut self, prefix: impl Into<String>, system: impl Into<String>) -> Self {
40 self.prefix_routes.push((prefix.into(), system.into()));
41 self
42 }
43
44 pub fn with_default(mut self, system: impl Into<String>) -> Self {
46 self.default = Some(system.into());
47 self
48 }
49
50 fn resolve(&self, model: &str) -> Option<Arc<dyn ModelAdapter>> {
52 for (prefix, system) in &self.prefix_routes {
54 if model.starts_with(prefix.as_str()) {
55 if let Some(adapter) = self.adapters.get(system) {
56 return Some(Arc::clone(adapter));
57 }
58 }
59 }
60 if let Some(default) = &self.default {
62 return self.adapters.get(default).map(Arc::clone);
63 }
64 if self.adapters.len() == 1 {
66 return self.adapters.values().next().map(Arc::clone);
67 }
68 None
69 }
70
71 pub async fn chat(&self, request: ModelRequest) -> Result<ModelResponse, ModelError> {
73 let model = request.config.model.clone().unwrap_or_default();
74 let adapter = self
75 .resolve(&model)
76 .ok_or_else(|| ModelError::Network(format!("no adapter for model: {model}")))?;
77 adapter.chat(request).await
78 }
79
80 pub async fn structured_output(
82 &self,
83 request: StructuredRequest,
84 ) -> Result<ModelResponse, ModelError> {
85 let model = request.config.model.clone().unwrap_or_default();
86 let adapter = self
87 .resolve(&model)
88 .ok_or_else(|| ModelError::Network(format!("no adapter for model: {model}")))?;
89 adapter.structured_output(request).await
90 }
91}
92
93impl Default for ModelRegistry {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99pub fn registry_from_env() -> ModelRegistry {
111 use crate::{
112 anthropic::AnthropicAdapter, google::GoogleAdapter, ollama::OllamaAdapter,
113 openai::OpenAiAdapter,
114 };
115
116 let mut registry = ModelRegistry::new()
117 .route_prefix("claude-", "anthropic")
118 .route_prefix("gpt-", "openai")
119 .route_prefix("o1-", "openai")
120 .route_prefix("o3-", "openai")
121 .route_prefix("gemini-", "google")
122 .route_prefix("google/", "google")
123 .route_prefix("llama", "ollama")
125 .route_prefix("qwen", "ollama")
126 .route_prefix("gemma", "ollama")
127 .route_prefix("phi", "ollama")
128 .route_prefix("mistral", "ollama")
129 .route_prefix("codellama", "ollama")
130 .route_prefix("deepseek", "ollama")
131 .route_prefix("nomic-", "ollama");
132
133 if let Ok(adapter) = AnthropicAdapter::from_env() {
134 registry = registry.register(Arc::new(adapter));
135 registry = registry.with_default("anthropic");
136 }
137
138 if let Ok(adapter) = OpenAiAdapter::from_env() {
139 registry = registry.register(Arc::new(adapter));
140 if registry.default.is_none() {
141 registry = registry.with_default("openai");
142 }
143 }
144
145 if let Ok(adapter) = GoogleAdapter::from_env() {
146 registry = registry.register(Arc::new(adapter));
147 if registry.default.is_none() {
148 registry = registry.with_default("google");
149 }
150 }
151
152 if let Ok(adapter) = OllamaAdapter::from_env() {
155 registry = registry.register(Arc::new(adapter));
156 if registry.default.is_none() {
157 registry = registry.with_default("ollama");
158 }
159 }
160
161 registry
162}