Skip to main content

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-haiku-4-5-20251001 (fastest, cheapest)
22    /// - "balanced" / "general" / "latest" → claude-sonnet-4-5-20250929 (latest Sonnet, balanced performance/cost)
23    /// - "complex" / "best" / "quality" → claude-opus-4-6 (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-haiku-4-5-20251001");
33    /// ```
34    pub fn with_defaults() -> Self {
35        let mut map = HashMap::new();
36
37        // Simple/fast tasks - use Haiku 4.5 (cheapest, fastest)
38        map.insert("simple".to_string(), "claude-haiku-4-5-20251001".to_string());
39        map.insert("fast".to_string(), "claude-haiku-4-5-20251001".to_string());
40        map.insert("cheap".to_string(), "claude-haiku-4-5-20251001".to_string());
41        map.insert("quick".to_string(), "claude-haiku-4-5-20251001".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 4.6 (most capable)
51        map.insert("complex".to_string(), "claude-opus-4-6".to_string());
52        map.insert("best".to_string(), "claude-opus-4-6".to_string());
53        map.insert("quality".to_string(), "claude-opus-4-6".to_string());
54        map.insert("critical".to_string(), "claude-opus-4-6".to_string());
55        map.insert("advanced".to_string(), "claude-opus-4-6".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-haiku-4-5-20251001"));
91    ///
92    /// // For complex tasks, use Opus
93    /// assert_eq!(recommender.suggest("complex"), Some("claude-opus-4-6"));
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 4.5)
139pub fn cheapest_model() -> &'static str {
140    "claude-haiku-4-5-20251001"
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 4.6)
154pub fn best_model() -> &'static str {
155    "claude-opus-4-6"
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-haiku-4-5-20251001"), 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"
181        | "claude-haiku-4-5-20251001"  // Haiku 4.5 (latest)
182        | "claude-3-5-haiku-20241022"  // Haiku 3.5
183        => 1.0,
184
185        // Sonnet - ~5x more expensive than Haiku
186        "sonnet"
187        | "claude-sonnet-4-5-20250929"  // Sonnet 4.5 (latest)
188        | "claude-sonnet-4-20250514"    // Sonnet 4
189        | "claude-3-5-sonnet-20241022"  // Sonnet 3.5
190        => 5.0,
191
192        // Opus - ~15x more expensive than Haiku
193        "opus"
194        | "claude-opus-4-6"            // Opus 4.6 (latest)
195        | "claude-opus-4-1-20250805"   // Opus 4.1
196        | "claude-opus-4-20250514"     // Opus 4
197        => 15.0,
198
199        // Unknown - assume Sonnet level
200        _ => 5.0,
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_default_recommendations() {
210        let recommender = ModelRecommendation::default();
211
212        assert_eq!(recommender.suggest("simple"), Some("claude-haiku-4-5-20251001"));
213        assert_eq!(recommender.suggest("fast"), Some("claude-haiku-4-5-20251001"));
214        assert_eq!(recommender.suggest("balanced"), Some("claude-sonnet-4-5-20250929"));
215        assert_eq!(recommender.suggest("latest"), Some("claude-sonnet-4-5-20250929"));
216        assert_eq!(recommender.suggest("complex"), Some("claude-opus-4-6"));
217        assert_eq!(recommender.suggest("unknown"), None);
218    }
219
220    #[test]
221    fn test_custom_recommendations() {
222        let mut map = HashMap::new();
223        map.insert("code_review".to_string(), "sonnet".to_string());
224
225        let recommender = ModelRecommendation::custom(map);
226        assert_eq!(recommender.suggest("code_review"), Some("sonnet"));
227    }
228
229    #[test]
230    fn test_add_remove() {
231        let mut recommender = ModelRecommendation::default();
232
233        recommender.add("my_task", "sonnet");
234        assert_eq!(recommender.suggest("my_task"), Some("sonnet"));
235
236        recommender.remove("my_task");
237        assert_eq!(recommender.suggest("my_task"), None);
238    }
239
240    #[test]
241    fn test_cost_multipliers() {
242        // Aliases
243        assert_eq!(estimate_cost_multiplier("haiku"), 1.0);
244        assert_eq!(estimate_cost_multiplier("sonnet"), 5.0);
245        assert_eq!(estimate_cost_multiplier("opus"), 15.0);
246        // Full model IDs
247        assert_eq!(estimate_cost_multiplier("claude-haiku-4-5-20251001"), 1.0);
248        assert_eq!(estimate_cost_multiplier("claude-sonnet-4-5-20250929"), 5.0);
249        assert_eq!(estimate_cost_multiplier("claude-opus-4-6"), 15.0);
250    }
251
252    #[test]
253    fn test_quick_helpers() {
254        assert_eq!(cheapest_model(), "claude-haiku-4-5-20251001");
255        assert_eq!(balanced_model(), "claude-sonnet-4-5-20250929");
256        assert_eq!(latest_sonnet(), "claude-sonnet-4-5-20250929");
257        assert_eq!(best_model(), "claude-opus-4-6");
258    }
259}