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