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