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}