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