Skip to main content

vtcode_core/tools/
workflow_optimizer.rs

1//! Workflow optimizer: Uses patterns and ML features to improve tool execution.
2//!
3//! Learns from detected patterns and suggests optimizations:
4//! - Parallel execution of independent tools
5//! - Caching strategy adjustments
6//! - Tool sequence reordering
7//! - Redundancy elimination
8
9use crate::config::constants::tools;
10use crate::tools::pattern_detection::DetectedPattern;
11use hashbrown::HashMap;
12use serde_json::json;
13
14/// An optimization recommendation.
15#[derive(Clone, Debug)]
16pub struct Optimization {
17    /// What to optimize (e.g., "parallelize")
18    pub optimization_type: OptimizationType,
19    /// Tools involved
20    pub tools: Vec<String>,
21    /// Expected improvement
22    pub expected_improvement: f64,
23    /// Reason for recommendation
24    pub reason: String,
25    /// Confidence (0-1)
26    pub confidence: f64,
27}
28
29/// Type of optimization.
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum OptimizationType {
32    /// Run these tools in parallel instead of sequence
33    Parallelize,
34    /// Cache this tool's results
35    CacheResult,
36    /// Skip this tool (result available elsewhere)
37    SkipRedundant,
38    /// Reorder tools for efficiency
39    Reorder,
40    /// Batch similar operations
41    Batch,
42}
43
44/// Workflow optimization engine.
45pub struct WorkflowOptimizer {
46    /// Detected patterns to learn from
47    patterns: Vec<DetectedPattern>,
48    /// Feature vector for analysis
49    features: Vec<f64>,
50    /// Generated optimizations
51    optimizations: Vec<Optimization>,
52}
53
54impl WorkflowOptimizer {
55    /// Create optimizer from detector output.
56    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    /// Analyze patterns and generate optimizations.
64    fn analyze(&mut self) {
65        // Rule 1: High-frequency patterns can be parallelized
66        self.detect_parallelization();
67
68        // Rule 2: Repeated sequences should be cached
69        self.detect_caching_opportunities();
70
71        // Rule 3: Long patterns have redundancy
72        self.detect_redundancy();
73
74        // Rule 4: Tool diversity suggests reordering
75        self.detect_reordering();
76
77        // Sort by expected improvement (descending) - use sort_unstable for better perf
78        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    /// Detect tools that can be parallelized.
86    fn detect_parallelization(&mut self) {
87        for pattern in &self.patterns {
88            // If pattern has 2+ tools and high frequency, consider parallelizing
89            if pattern.sequence.len() >= 2 && pattern.frequency >= 3 {
90                // Check if tools are independent (this is a heuristic)
91                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    /// Detect opportunities for aggressive caching.
115    fn detect_caching_opportunities(&mut self) {
116        for pattern in &self.patterns {
117            if pattern.frequency >= 5 && pattern.avg_duration_ms > 100 {
118                // High frequency + slow tool = good cache candidate
119                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    /// Detect redundant tool calls.
138    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                // Tool appears in multiple patterns - possible redundancy
150                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    /// Detect suboptimal tool ordering.
162    fn detect_reordering(&mut self) {
163        // If pattern density is high, might benefit from reordering
164        if self.features.len() > 4 && self.features[4] > 0.7 {
165            // pattern_density > 70%
166            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    /// Check if tools likely have dependencies (simple heuristic).
177    fn tools_have_dependencies(&self, tools: &[String]) -> bool {
178        // Heuristic: grep likely depends on find, etc.
179        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    /// Get all optimizations.
197    pub fn optimizations(&self) -> &[Optimization] {
198        &self.optimizations
199    }
200
201    /// Get top-N optimizations.
202    pub fn top_optimizations(&self, n: usize) -> &[Optimization] {
203        &self.optimizations[..self.optimizations.len().min(n)]
204    }
205
206    /// Estimated total improvement if all optimizations applied.
207    pub fn estimated_total_improvement(&self) -> f64 {
208        self.optimizations
209            .iter()
210            .take(5) // Realistically apply top 5
211            .map(|o| o.expected_improvement * o.confidence)
212            .sum::<f64>()
213            .min(1.0)
214    }
215
216    /// Export optimizations as JSON for analysis.
217    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        // Should have recommendations
295        let has_recommendation = !optimizer.optimizations.is_empty();
296        assert!(has_recommendation);
297    }
298}