cc_sdk/
model_recommendation.rs

1//! Model selection recommendations for token optimization
2//!
3//! This module provides utilities to help choose the most cost-effective Claude model
4//! based on task complexity and requirements.
5
6use std::collections::HashMap;
7
8/// Model recommendation helper
9///
10/// Provides recommendations for which Claude model to use based on task type.
11/// You can use the default recommendations or provide custom mappings.
12#[derive(Debug, Clone)]
13pub struct ModelRecommendation {
14    recommendations: HashMap<String, String>,
15}
16
17impl ModelRecommendation {
18    /// Create with default recommendations
19    ///
20    /// Default mappings:
21    /// - "simple" / "fast" / "cheap" → claude-3-5-haiku-20241022 (fastest, cheapest)
22    /// - "balanced" / "general" / "latest" → claude-sonnet-4-5-20250929 (latest Sonnet, balanced performance/cost)
23    /// - "complex" / "best" / "quality" → opus (most capable)
24    ///
25    /// # Example
26    ///
27    /// ```rust
28    /// use cc_sdk::model_recommendation::ModelRecommendation;
29    ///
30    /// let recommender = ModelRecommendation::default();
31    /// let model = recommender.suggest("simple").unwrap();
32    /// assert_eq!(model, "claude-3-5-haiku-20241022");
33    /// ```
34    pub fn with_defaults() -> Self {
35        let mut map = HashMap::new();
36
37        // Simple/fast tasks - use Haiku (cheapest, fastest)
38        map.insert("simple".to_string(), "claude-3-5-haiku-20241022".to_string());
39        map.insert("fast".to_string(), "claude-3-5-haiku-20241022".to_string());
40        map.insert("cheap".to_string(), "claude-3-5-haiku-20241022".to_string());
41        map.insert("quick".to_string(), "claude-3-5-haiku-20241022".to_string());
42
43        // Balanced tasks - use Sonnet 4.5 (good balance, latest)
44        map.insert("balanced".to_string(), "claude-sonnet-4-5-20250929".to_string());
45        map.insert("general".to_string(), "claude-sonnet-4-5-20250929".to_string());
46        map.insert("normal".to_string(), "claude-sonnet-4-5-20250929".to_string());
47        map.insert("standard".to_string(), "claude-sonnet-4-5-20250929".to_string());
48        map.insert("latest".to_string(), "claude-sonnet-4-5-20250929".to_string());
49
50        // Complex/critical tasks - use Opus (most capable)
51        map.insert("complex".to_string(), "opus".to_string());
52        map.insert("best".to_string(), "opus".to_string());
53        map.insert("quality".to_string(), "opus".to_string());
54        map.insert("critical".to_string(), "opus".to_string());
55        map.insert("advanced".to_string(), "opus".to_string());
56
57        Self { recommendations: map }
58    }
59
60    /// Create with custom recommendations
61    ///
62    /// # Example
63    ///
64    /// ```rust
65    /// use cc_sdk::model_recommendation::ModelRecommendation;
66    /// use std::collections::HashMap;
67    ///
68    /// let mut custom_map = HashMap::new();
69    /// custom_map.insert("code_review".to_string(), "sonnet".to_string());
70    /// custom_map.insert("documentation".to_string(), "claude-3-5-haiku-20241022".to_string());
71    ///
72    /// let recommender = ModelRecommendation::custom(custom_map);
73    /// ```
74    pub fn custom(recommendations: HashMap<String, String>) -> Self {
75        Self { recommendations }
76    }
77
78    /// Get a model suggestion for a given task type
79    ///
80    /// Returns the recommended model name, or None if no recommendation exists.
81    ///
82    /// # Example
83    ///
84    /// ```rust
85    /// use cc_sdk::model_recommendation::ModelRecommendation;
86    ///
87    /// let recommender = ModelRecommendation::default();
88    ///
89    /// // For simple tasks, use Haiku
90    /// assert_eq!(recommender.suggest("simple"), Some("claude-3-5-haiku-20241022"));
91    ///
92    /// // For complex tasks, use Opus
93    /// assert_eq!(recommender.suggest("complex"), Some("opus"));
94    /// ```
95    pub fn suggest(&self, task_type: &str) -> Option<&str> {
96        self.recommendations.get(task_type).map(|s| s.as_str())
97    }
98
99    /// Add or update a recommendation
100    ///
101    /// # Example
102    ///
103    /// ```rust
104    /// use cc_sdk::model_recommendation::ModelRecommendation;
105    ///
106    /// let mut recommender = ModelRecommendation::default();
107    /// recommender.add("my_task", "sonnet");
108    /// assert_eq!(recommender.suggest("my_task"), Some("sonnet"));
109    /// ```
110    pub fn add(&mut self, task_type: impl Into<String>, model: impl Into<String>) {
111        self.recommendations.insert(task_type.into(), model.into());
112    }
113
114    /// Remove a recommendation
115    pub fn remove(&mut self, task_type: &str) -> Option<String> {
116        self.recommendations.remove(task_type)
117    }
118
119    /// Get all task types with recommendations
120    pub fn task_types(&self) -> Vec<&str> {
121        self.recommendations.keys().map(|s| s.as_str()).collect()
122    }
123
124    /// Get all available recommendations
125    pub fn all_recommendations(&self) -> &HashMap<String, String> {
126        &self.recommendations
127    }
128}
129
130impl Default for ModelRecommendation {
131    fn default() -> Self {
132        // Use our predefined defaults
133        ModelRecommendation::with_defaults()
134    }
135}
136
137/// Quick helper functions for common use cases
138/// Get the cheapest/fastest model (Haiku)
139pub fn cheapest_model() -> &'static str {
140    "claude-3-5-haiku-20241022"
141}
142
143/// Get the balanced model (Sonnet 4.5 - latest)
144pub fn balanced_model() -> &'static str {
145    "claude-sonnet-4-5-20250929"
146}
147
148/// Get the latest Sonnet model alias
149pub fn latest_sonnet() -> &'static str {
150    "claude-sonnet-4-5-20250929"
151}
152
153/// Get the most capable model (Opus)
154pub fn best_model() -> &'static str {
155    "opus"
156}
157
158/// Estimate relative cost multiplier for different models
159///
160/// Returns approximate cost multiplier relative to Haiku (1.0x).
161/// These are rough estimates and actual costs depend on usage patterns.
162///
163/// # Example
164///
165/// ```rust
166/// use cc_sdk::model_recommendation::estimate_cost_multiplier;
167///
168/// // Haiku is baseline (1.0x)
169/// assert_eq!(estimate_cost_multiplier("claude-3-5-haiku-20241022"), 1.0);
170///
171/// // Sonnet is ~5x more expensive
172/// assert_eq!(estimate_cost_multiplier("sonnet"), 5.0);
173///
174/// // Opus is ~15x more expensive
175/// assert_eq!(estimate_cost_multiplier("opus"), 15.0);
176/// ```
177pub fn estimate_cost_multiplier(model: &str) -> f64 {
178    match model {
179        // Haiku - baseline (cheapest)
180        "haiku" | "claude-3-5-haiku-20241022" => 1.0,
181
182        // Sonnet - ~5x more expensive than Haiku
183        "sonnet"
184        | "claude-sonnet-4-5-20250929"  // Sonnet 4.5 (latest)
185        | "claude-sonnet-4-20250514"    // Sonnet 4
186        | "claude-3-5-sonnet-20241022"  // Sonnet 3.5
187        => 5.0,
188
189        // Opus - ~15x more expensive than Haiku
190        "opus" | "claude-opus-4-1-20250805" => 15.0,
191
192        // Unknown - assume Sonnet level
193        _ => 5.0,
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_default_recommendations() {
203        let recommender = ModelRecommendation::default();
204
205        assert_eq!(recommender.suggest("simple"), Some("claude-3-5-haiku-20241022"));
206        assert_eq!(recommender.suggest("fast"), Some("claude-3-5-haiku-20241022"));
207        assert_eq!(recommender.suggest("balanced"), Some("claude-sonnet-4-5-20250929"));
208        assert_eq!(recommender.suggest("latest"), Some("claude-sonnet-4-5-20250929"));
209        assert_eq!(recommender.suggest("complex"), Some("opus"));
210        assert_eq!(recommender.suggest("unknown"), None);
211    }
212
213    #[test]
214    fn test_custom_recommendations() {
215        let mut map = HashMap::new();
216        map.insert("code_review".to_string(), "sonnet".to_string());
217
218        let recommender = ModelRecommendation::custom(map);
219        assert_eq!(recommender.suggest("code_review"), Some("sonnet"));
220    }
221
222    #[test]
223    fn test_add_remove() {
224        let mut recommender = ModelRecommendation::default();
225
226        recommender.add("my_task", "sonnet");
227        assert_eq!(recommender.suggest("my_task"), Some("sonnet"));
228
229        recommender.remove("my_task");
230        assert_eq!(recommender.suggest("my_task"), None);
231    }
232
233    #[test]
234    fn test_cost_multipliers() {
235        assert_eq!(estimate_cost_multiplier("haiku"), 1.0);
236        assert_eq!(estimate_cost_multiplier("sonnet"), 5.0);
237        assert_eq!(estimate_cost_multiplier("opus"), 15.0);
238    }
239
240    #[test]
241    fn test_quick_helpers() {
242        assert_eq!(cheapest_model(), "claude-3-5-haiku-20241022");
243        assert_eq!(balanced_model(), "claude-sonnet-4-5-20250929");
244        assert_eq!(latest_sonnet(), "claude-sonnet-4-5-20250929");
245        assert_eq!(best_model(), "opus");
246    }
247}