razeri_plugin_api/
model.rs1use std::collections::HashMap;
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
16pub struct ModelCapabilities {
17 pub max_context_tokens: u32,
19 pub supports_json_mode: bool,
21 pub supports_tools: bool,
23 pub supports_streaming: bool,
25}
26
27pub trait LlmModelAdapter: Send + Sync {
34 fn model_id(&self) -> &str;
36
37 fn provider_name(&self) -> &str;
39
40 fn display_name(&self) -> &str;
42
43 fn capabilities(&self) -> ModelCapabilities;
45}
46
47pub struct ModelRegistry {
51 adapters: HashMap<(String, String), Arc<dyn LlmModelAdapter>>,
52}
53
54impl ModelRegistry {
55 #[must_use]
57 pub fn new() -> Self {
58 Self { adapters: HashMap::new() }
59 }
60
61 pub fn register(&mut self, adapter: Arc<dyn LlmModelAdapter>) {
63 let key = (adapter.provider_name().to_string(), adapter.model_id().to_string());
64 self.adapters.insert(key, adapter);
65 }
66
67 #[must_use]
69 pub fn get(&self, provider_name: &str, model_id: &str) -> Option<&Arc<dyn LlmModelAdapter>> {
70 self.adapters.get(&(provider_name.to_string(), model_id.to_string()))
71 }
72
73 #[must_use]
75 pub fn list_for_provider(&self, provider_name: &str) -> Vec<Arc<dyn LlmModelAdapter>> {
76 self.adapters
77 .iter()
78 .filter(|((p, _), _)| p == provider_name)
79 .map(|(_, adapter)| Arc::clone(adapter))
80 .collect()
81 }
82}
83
84impl Default for ModelRegistry {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 struct TestModelAdapter {
95 provider: &'static str,
96 model: &'static str,
97 display: &'static str,
98 caps: ModelCapabilities,
99 }
100
101 impl LlmModelAdapter for TestModelAdapter {
102 fn model_id(&self) -> &str {
103 self.model
104 }
105
106 fn provider_name(&self) -> &str {
107 self.provider
108 }
109
110 fn display_name(&self) -> &str {
111 self.display
112 }
113
114 fn capabilities(&self) -> ModelCapabilities {
115 self.caps.clone()
116 }
117 }
118
119 fn default_caps() -> ModelCapabilities {
120 ModelCapabilities {
121 max_context_tokens: 8192,
122 supports_json_mode: true,
123 supports_tools: false,
124 supports_streaming: true,
125 }
126 }
127
128 fn make_adapter(
129 provider: &'static str,
130 model: &'static str,
131 display: &'static str,
132 ) -> Arc<dyn LlmModelAdapter> {
133 Arc::new(TestModelAdapter { provider, model, display, caps: default_caps() })
134 }
135
136 #[test]
137 fn model_adapter_capabilities_exposed() {
138 let adapter = make_adapter("ollama", "llama3.2:3b", "Llama 3.2 3B");
139 let caps = adapter.capabilities();
140 assert_eq!(caps.max_context_tokens, 8192);
141 assert!(caps.supports_json_mode);
142 assert!(!caps.supports_tools);
143 assert!(caps.supports_streaming);
144 }
145
146 #[test]
147 fn model_registry_provider_model_lookup() {
148 let mut registry = ModelRegistry::new();
149 registry.register(make_adapter("ollama", "llama3.2:3b", "Llama 3.2 3B"));
150
151 assert!(registry.get("ollama", "llama3.2:3b").is_some());
152 assert!(registry.get("ollama", "nonexistent").is_none());
153 assert!(registry.get("openai", "llama3.2:3b").is_none());
154 }
155
156 #[test]
157 fn model_registry_lists_models_by_provider() {
158 let mut registry = ModelRegistry::new();
159 registry.register(make_adapter("ollama", "llama3.2:3b", "Llama 3.2 3B"));
160 registry.register(make_adapter("ollama", "qwen2.5:7b", "Qwen 2.5 7B"));
161 registry.register(make_adapter("openai", "gpt-4o", "GPT-4o"));
162
163 let ollama_models = registry.list_for_provider("ollama");
164 assert_eq!(ollama_models.len(), 2);
165
166 let openai_models = registry.list_for_provider("openai");
167 assert_eq!(openai_models.len(), 1);
168 }
169}