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}