1use crate::config::constants::tools;
10use crate::tools::pattern_detection::DetectedPattern;
11use hashbrown::HashMap;
12use serde_json::json;
13
14#[derive(Clone, Debug)]
16pub struct Optimization {
17 pub optimization_type: OptimizationType,
19 pub tools: Vec<String>,
21 pub expected_improvement: f64,
23 pub reason: String,
25 pub confidence: f64,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum OptimizationType {
32 Parallelize,
34 CacheResult,
36 SkipRedundant,
38 Reorder,
40 Batch,
42}
43
44pub struct WorkflowOptimizer {
46 patterns: Vec<DetectedPattern>,
48 features: Vec<f64>,
50 optimizations: Vec<Optimization>,
52}
53
54impl WorkflowOptimizer {
55 pub fn from_detector(patterns: Vec<DetectedPattern>, features: Vec<f64>) -> Self {
57 let mut optimizer = Self { patterns, features, optimizations: Vec::new() };
58
59 optimizer.analyze();
60 optimizer
61 }
62
63 fn analyze(&mut self) {
65 self.detect_parallelization();
67
68 self.detect_caching_opportunities();
70
71 self.detect_redundancy();
73
74 self.detect_reordering();
76
77 self.optimizations.sort_unstable_by(|a, b| {
79 b.expected_improvement
80 .partial_cmp(&a.expected_improvement)
81 .unwrap_or(std::cmp::Ordering::Equal)
82 });
83 }
84
85 fn detect_parallelization(&mut self) {
87 for pattern in &self.patterns {
88 if pattern.sequence.len() >= 2 && pattern.frequency >= 3 {
90 let tools_are_likely_independent = !self.tools_have_dependencies(&pattern.sequence);
92
93 if tools_are_likely_independent && pattern.success_rate > 0.95 {
94 let improvement = (pattern.frequency as f64 / 10.0).min(0.5);
95 let confidence = pattern.confidence;
96
97 self.optimizations.push(Optimization {
98 optimization_type: OptimizationType::Parallelize,
99 tools: pattern.sequence.clone(),
100 expected_improvement: improvement,
101 reason: format!(
102 "Tools {:?} appear {} times together with {:.0}% success rate",
103 pattern.sequence,
104 pattern.frequency,
105 pattern.success_rate * 100.0
106 ),
107 confidence,
108 });
109 }
110 }
111 }
112 }
113
114 fn detect_caching_opportunities(&mut self) {
116 for pattern in &self.patterns {
117 if pattern.frequency >= 5 && pattern.avg_duration_ms > 100 {
118 for tool in &pattern.sequence {
120 let improvement = (pattern.avg_duration_ms as f64 / 1000.0).min(0.8);
121
122 self.optimizations.push(Optimization {
123 optimization_type: OptimizationType::CacheResult,
124 tools: vec![tool.clone()],
125 expected_improvement: improvement,
126 reason: format!(
127 "Tool '{}' appears {} times, takes {}ms each",
128 tool, pattern.frequency, pattern.avg_duration_ms
129 ),
130 confidence: pattern.confidence * pattern.success_rate,
131 });
132 }
133 }
134 }
135 }
136
137 fn detect_redundancy(&mut self) {
139 let mut tool_call_counts: HashMap<String, usize> = HashMap::new();
140
141 for pattern in &self.patterns {
142 for tool in &pattern.sequence {
143 *tool_call_counts.entry(tool.clone()).or_insert(0) += 1;
144 }
145 }
146
147 for (tool, count) in tool_call_counts {
148 if count >= 3 {
149 self.optimizations.push(Optimization {
151 optimization_type: OptimizationType::SkipRedundant,
152 tools: vec![tool.clone()],
153 expected_improvement: 0.2,
154 reason: format!("Tool '{tool}' appears in {count} different patterns"),
155 confidence: 0.5,
156 });
157 }
158 }
159 }
160
161 fn detect_reordering(&mut self) {
163 if self.features.len() > 4 && self.features[4] > 0.7 {
165 self.optimizations.push(Optimization {
167 optimization_type: OptimizationType::Reorder,
168 tools: vec!["(workflow)".into()],
169 expected_improvement: 0.15,
170 reason: "High pattern density suggests workflow has repeating structure".into(),
171 confidence: 0.6,
172 });
173 }
174 }
175
176 fn tools_have_dependencies(&self, tools: &[String]) -> bool {
178 let dependencies = vec![(tools::GREP_FILE, "find_files"), ("edit_file", tools::LIST_FILES)];
180
181 for (i, tool1) in tools.iter().enumerate() {
182 for (j, tool2) in tools.iter().enumerate() {
183 if i != j {
184 for (dep1, dep2) in &dependencies {
185 if tool1.contains(dep1) && tool2.contains(dep2) {
186 return true;
187 }
188 }
189 }
190 }
191 }
192
193 false
194 }
195
196 pub fn optimizations(&self) -> &[Optimization] {
198 &self.optimizations
199 }
200
201 pub fn top_optimizations(&self, n: usize) -> &[Optimization] {
203 &self.optimizations[..self.optimizations.len().min(n)]
204 }
205
206 pub fn estimated_total_improvement(&self) -> f64 {
208 self.optimizations
209 .iter()
210 .take(5) .map(|o| o.expected_improvement * o.confidence)
212 .sum::<f64>()
213 .min(1.0)
214 }
215
216 pub fn to_json(&self) -> serde_json::Value {
218 json!({
219 "total_optimizations": self.optimizations.len(),
220 "estimated_improvement": self.estimated_total_improvement(),
221 "optimizations": self.optimizations
222 .iter()
223 .take(5)
224 .map(|o| json!({
225 "type": format!("{:?}", o.optimization_type),
226 "tools": o.tools,
227 "improvement": o.expected_improvement,
228 "confidence": o.confidence,
229 "reason": o.reason,
230 }))
231 .collect::<Vec<_>>(),
232 })
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::tools::pattern_detection::DetectedPattern;
240
241 #[test]
242 fn test_optimizer_from_patterns() {
243 let patterns = vec![DetectedPattern {
244 name: "test".into(),
245 sequence: vec!["find".into(), "grep".into()],
246 frequency: 5,
247 success_rate: 0.95,
248 avg_duration_ms: 150,
249 confidence: 0.7,
250 }];
251
252 let features = vec![5.0, 0.95, 150.0, 2.0, 0.6];
253 let optimizer = WorkflowOptimizer::from_detector(patterns, features);
254
255 assert!(!optimizer.optimizations.is_empty());
256 }
257
258 #[test]
259 fn test_optimizer_improvement_estimate() {
260 let patterns = vec![DetectedPattern {
261 name: "test".into(),
262 sequence: vec!["find".into(), "grep".into()],
263 frequency: 5,
264 success_rate: 0.95,
265 avg_duration_ms: 150,
266 confidence: 0.7,
267 }];
268
269 let features = vec![5.0, 0.95, 150.0, 2.0, 0.6];
270 let optimizer = WorkflowOptimizer::from_detector(patterns, features);
271
272 let improvement = optimizer.estimated_total_improvement();
273 assert!(improvement > 0.0);
274 assert!(improvement <= 1.0);
275 }
276
277 #[test]
278 fn test_optimizer_recommendations() {
279 let patterns = vec![DetectedPattern {
280 name: "p1".into(),
281 sequence: vec![tools::LIST_FILES.into(), "find_files".into(), "grep_file".into()],
282 frequency: 10,
283 success_rate: 0.98,
284 avg_duration_ms: 200,
285 confidence: 0.8,
286 }];
287
288 let features = vec![10.0, 0.98, 200.0, 3.0, 0.75];
289 let optimizer = WorkflowOptimizer::from_detector(patterns, features);
290
291 let top = optimizer.top_optimizations(3);
292 assert!(!top.is_empty());
293
294 let has_recommendation = !optimizer.optimizations.is_empty();
296 assert!(has_recommendation);
297 }
298}