Skip to main content

mixtape_core/models/
mod.rs

1//! Pre-configured model definitions
2//!
3//! This module contains model structs for various LLM providers.
4//! Each model implements the `Model` trait and provider-specific traits
5//! like `BedrockModel` or `AnthropicModel`.
6//!
7//! Models are organized by vendor:
8//! - `claude` - Anthropic Claude models
9//! - `llama` - Meta Llama models
10//! - `nova` - Amazon Nova models
11//! - `mistral` - Mistral AI models
12//! - `cohere` - Cohere models
13//! - `qwen` - Alibaba Qwen models
14//! - `google` - Google models
15//! - `deepseek` - DeepSeek models
16//! - `kimi` - Moonshot Kimi models
17
18mod claude;
19mod cohere;
20mod deepseek;
21mod google;
22mod kimi;
23mod llama;
24mod mistral;
25mod nova;
26mod qwen;
27
28// Re-export all models at the module level
29pub use claude::*;
30pub use cohere::*;
31pub use deepseek::*;
32pub use google::*;
33pub use kimi::*;
34pub use llama::*;
35pub use mistral::*;
36pub use nova::*;
37pub use qwen::*;
38
39/// Macro to generate model structs with trait implementations
40///
41/// This macro creates a model struct that implements:
42/// - `Model` trait (always)
43/// - `BedrockModel` trait (always)
44/// - `AnthropicModel` trait (if `anthropic_id` is provided)
45///
46/// Optional fields:
47/// - `anthropic_id` - Anthropic API model ID (enables AnthropicModel trait)
48/// - `default_inference_profile` - Default inference profile for Bedrock (e.g., Global)
49macro_rules! define_model {
50    (
51        $(#[$meta:meta])*
52        $name:ident {
53            display_name: $display_name:expr,
54            bedrock_id: $bedrock_id:expr,
55            context_tokens: $context_tokens:expr,
56            output_tokens: $output_tokens:expr
57            $(, anthropic_id: $anthropic_id:expr)?
58            $(, default_inference_profile: $profile:expr)?
59        }
60    ) => {
61        $(#[$meta])*
62        #[derive(Debug, Clone, Copy, Default)]
63        pub struct $name;
64
65        impl $crate::model::Model for $name {
66            fn name(&self) -> &'static str {
67                $display_name
68            }
69
70            fn max_context_tokens(&self) -> usize {
71                $context_tokens
72            }
73
74            fn max_output_tokens(&self) -> usize {
75                $output_tokens
76            }
77
78            fn estimate_token_count(&self, text: &str) -> usize {
79                // Default heuristic: ~4 characters per token
80                text.len().div_ceil(4)
81            }
82        }
83
84        impl $crate::model::BedrockModel for $name {
85            fn bedrock_id(&self) -> &'static str {
86                $bedrock_id
87            }
88
89            $crate::models::define_model!(@inference_profile $($profile)?);
90        }
91
92        $(
93            impl $crate::model::AnthropicModel for $name {
94                fn anthropic_id(&self) -> &'static str {
95                    $anthropic_id
96                }
97            }
98        )?
99    };
100
101    // Helper: generate default_inference_profile method if profile is specified
102    (@inference_profile $profile:expr) => {
103        fn default_inference_profile(&self) -> $crate::model::InferenceProfile {
104            $profile
105        }
106    };
107
108    // Helper: no-op if no profile specified (uses trait default)
109    (@inference_profile) => {};
110}
111
112// Make the macro available to submodules
113pub(crate) use define_model;
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::model::{AnthropicModel, BedrockModel, InferenceProfile, Model};
119
120    #[test]
121    fn test_claude_implements_both_traits() {
122        let model = ClaudeSonnet4_5;
123
124        // Model trait
125        assert_eq!(model.name(), "Claude Sonnet 4.5");
126        assert_eq!(model.max_context_tokens(), 200_000);
127        assert_eq!(model.max_output_tokens(), 64_000);
128
129        // BedrockModel trait
130        assert!(model.bedrock_id().contains("claude-sonnet-4-5"));
131
132        // AnthropicModel trait
133        assert!(model.anthropic_id().contains("claude-sonnet-4-5"));
134    }
135
136    #[test]
137    fn test_nova_only_implements_bedrock() {
138        let model = NovaMicro;
139
140        // Model trait
141        assert_eq!(model.name(), "Nova Micro");
142
143        // BedrockModel trait
144        assert!(model.bedrock_id().contains("nova-micro"));
145
146        // NovaMicro does NOT implement AnthropicModel - compile-time check
147    }
148
149    #[test]
150    fn test_models_are_copy() {
151        let model = ClaudeSonnet4_5;
152        let copy = model;
153        assert_eq!(model.name(), copy.name());
154    }
155
156    #[test]
157    fn test_model_ids_are_valid() {
158        // Verify model ID format (no spaces, valid characters)
159        let models: Vec<&dyn BedrockModel> = vec![
160            &Claude3_7Sonnet,
161            &ClaudeOpus4,
162            &ClaudeSonnet4,
163            &ClaudeSonnet4_5,
164            &ClaudeHaiku4_5,
165            &ClaudeOpus4_5,
166            &ClaudeOpus4_1,
167            &ClaudeOpus4_6,
168            &NovaMicro,
169            &NovaLite,
170            &Nova2Lite,
171            &NovaPro,
172            &NovaPremier,
173            &Nova2Sonic,
174            &MistralLarge3,
175            &MagistralSmall,
176            &Ministral3B,
177            &Ministral8B,
178            &Ministral14B,
179            &PixtralLarge,
180            &VoxtralMini3B,
181            &VoxtralSmall24B,
182            &CohereCommandRPlus,
183            &Qwen3_235B,
184            &Qwen3Coder480B,
185            &Qwen3_32B,
186            &Qwen3Coder30B,
187            &Qwen3Next80B,
188            &Qwen3VL235B,
189            &Gemma3_27B,
190            &Gemma3_12B,
191            &Gemma3_4B,
192            &DeepSeekR1,
193            &DeepSeekV3_1,
194            &DeepSeekV3_2,
195            &KimiK2Thinking,
196            &KimiK2_5,
197            &Llama4Scout17B,
198            &Llama4Maverick17B,
199            &Llama3_3_70B,
200            &Llama3_2_90B,
201            &Llama3_2_11B,
202            &Llama3_2_3B,
203            &Llama3_2_1B,
204            &Llama3_1_405B,
205            &Llama3_1_70B,
206            &Llama3_1_8B,
207        ];
208
209        for model in models {
210            let id = model.bedrock_id();
211            assert!(
212                !id.contains(' '),
213                "Model ID should not contain spaces: {}",
214                id
215            );
216            assert!(
217                id.contains('.'),
218                "Model ID should contain provider prefix: {}",
219                id
220            );
221        }
222    }
223
224    #[test]
225    fn test_global_inference_profile_models() {
226        // Models that require Global inference profile should return it
227        let global_models: Vec<&dyn BedrockModel> = vec![
228            &ClaudeOpus4,
229            &ClaudeOpus4_1,
230            &ClaudeOpus4_5,
231            &ClaudeOpus4_6,
232            &ClaudeSonnet4,
233            &ClaudeSonnet4_5,
234            &ClaudeHaiku4_5,
235            &Nova2Lite,
236            &Nova2Sonic,
237        ];
238
239        for model in global_models {
240            assert_eq!(
241                model.default_inference_profile(),
242                InferenceProfile::Global,
243                "{} should have Global inference profile",
244                model.bedrock_id()
245            );
246        }
247    }
248
249    #[test]
250    fn test_default_inference_profile_models() {
251        // Models without an explicit profile should return None (the default)
252        let default_models: Vec<&dyn BedrockModel> = vec![
253            &Claude3_7Sonnet,
254            &NovaMicro,
255            &NovaLite,
256            &NovaPro,
257            &NovaPremier,
258            &MistralLarge3,
259            &Gemma3_27B,
260            &DeepSeekR1,
261            &KimiK2Thinking,
262        ];
263
264        for model in default_models {
265            assert_eq!(
266                model.default_inference_profile(),
267                InferenceProfile::None,
268                "{} should have None (default) inference profile",
269                model.bedrock_id()
270            );
271        }
272    }
273}