vtcode_core/tools/
pattern_detection.rs1use hashbrown::HashMap;
8use serde_json::{Value, json};
9
10const MAX_EVENTS: usize = 500;
12const ANALYZE_INTERVAL: usize = 10;
14
15#[derive(Clone, Debug)]
17pub struct ToolEvent {
18 pub tool_name: String,
19 pub success: bool,
20 pub duration_ms: u64,
21 pub timestamp: std::time::Instant,
22}
23
24#[derive(Clone, Debug)]
26pub struct DetectedPattern {
27 pub name: String,
28 pub sequence: Vec<String>,
29 pub frequency: usize,
30 pub success_rate: f64,
31 pub avg_duration_ms: u64,
32 pub confidence: f64,
33}
34
35pub struct PatternDetector {
37 events: Vec<ToolEvent>,
38 patterns: HashMap<String, DetectedPattern>,
39 sequence_length: usize,
40 events_since_analysis: usize,
42}
43
44impl PatternDetector {
45 pub fn new(sequence_length: usize) -> Self {
51 Self {
52 events: Vec::with_capacity(64),
53 patterns: HashMap::with_capacity(16),
54 sequence_length: sequence_length.max(1),
55 events_since_analysis: 0,
56 }
57 }
58
59 pub fn record_event(&mut self, event: ToolEvent) {
61 if self.events.len() >= MAX_EVENTS {
63 let drain_count = MAX_EVENTS / 10; self.events.drain(..drain_count);
65 }
66
67 self.events.push(event);
68 self.events_since_analysis += 1;
69
70 if self.events_since_analysis >= ANALYZE_INTERVAL {
72 self.analyze();
73 self.events_since_analysis = 0;
74 }
75 }
76
77 fn analyze(&mut self) {
79 if self.events.len() < self.sequence_length {
80 return;
81 }
82
83 let mut sequence_map: HashMap<Vec<&str>, Vec<&ToolEvent>> = HashMap::new();
84
85 for window in self.events.windows(self.sequence_length) {
87 let seq: Vec<&str> = window.iter().map(|e| e.tool_name.as_str()).collect();
88
89 let entry = sequence_map.entry(seq.clone()).or_default();
91 for event in window {
92 entry.push(event);
93 }
94 }
95
96 for (sequence, events) in sequence_map {
98 let frequency = events.len() / self.sequence_length;
101 if frequency >= 2 {
102 let success_count = events.iter().filter(|e| e.success).count();
104 let success_rate = success_count as f64 / events.len() as f64;
105 let avg_duration = events.iter().map(|e| e.duration_ms).sum::<u64>() / events.len() as u64;
106
107 let confidence = (success_rate * (frequency as f64 / 10.0).min(1.0)).min(1.0);
109
110 let sequence_vec = sequence.iter().map(|s| s.to_string()).collect::<Vec<String>>();
111 let pattern_name = format!("pattern_{:x}", hash_sequence(&sequence_vec));
112
113 self.patterns.insert(
114 pattern_name.clone(),
115 DetectedPattern {
116 name: pattern_name,
117 sequence: sequence_vec,
118 frequency,
119 success_rate,
120 avg_duration_ms: avg_duration,
121 confidence,
122 },
123 );
124 }
125 }
126 }
127
128 pub fn patterns(&self) -> Vec<DetectedPattern> {
130 let mut patterns: Vec<_> = self.patterns.values().cloned().collect();
131 patterns.sort_unstable_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal));
132 patterns
133 }
134
135 pub fn feature_vector(&self) -> Vec<f64> {
151 let mut features = Vec::with_capacity(5);
152
153 features.push(self.events.len() as f64);
155
156 let success_rate = self.events.iter().filter(|e| e.success).count() as f64 / self.events.len().max(1) as f64;
158 features.push(success_rate);
159
160 let avg_duration =
162 self.events.iter().map(|e| e.duration_ms).sum::<u64>() as f64 / self.events.len().max(1) as f64;
163 features.push(avg_duration);
164
165 let unique_tools = self
167 .events
168 .iter()
169 .map(|e| &e.tool_name)
170 .collect::<hashbrown::HashSet<_>>()
171 .len() as f64;
172 features.push(unique_tools);
173
174 let pattern_density = self.patterns.len() as f64 / self.events.len().max(1) as f64;
176 features.push(pattern_density);
177
178 clamp_features(&features)
180 }
181
182 pub fn reset(&mut self) {
184 self.events.clear();
185 self.patterns.clear();
186 self.events_since_analysis = 0;
187 }
188
189 pub fn to_json(&self) -> Value {
191 json!({
192 "event_count": self.events.len(),
193 "pattern_count": self.patterns.len(),
194 "patterns": self.patterns()
195 .iter()
196 .map(|p| json!({
197 "name": p.name,
198 "sequence": p.sequence,
199 "frequency": p.frequency,
200 "success_rate": p.success_rate,
201 "avg_duration_ms": p.avg_duration_ms,
202 "confidence": p.confidence,
203 }))
204 .collect::<Vec<_>>(),
205 "feature_vector": self.feature_vector(),
206 })
207 }
208}
209
210fn clamp_features(features: &[f64]) -> Vec<f64> {
215 features
216 .iter()
217 .enumerate()
218 .map(|(i, &f)| {
219 if i == 0 {
220 f } else {
222 f.clamp(0.0, 1.0)
223 }
224 })
225 .collect()
226}
227
228fn hash_sequence(seq: &[String]) -> u64 {
230 let mut hash: u64 = 0;
231 for s in seq {
232 for b in s.bytes() {
233 hash = hash.wrapping_mul(31).wrapping_add(b as u64);
234 }
235 }
236 hash
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::time::Instant;
243
244 #[test]
245 fn zero_window_is_clamped_to_one() {
246 let mut detector = PatternDetector::new(0);
249 let now = Instant::now();
250 for _ in 0..ANALYZE_INTERVAL {
251 detector.record_event(ToolEvent {
252 tool_name: "tool_a".into(),
253 success: true,
254 duration_ms: 10,
255 timestamp: now,
256 });
257 }
258
259 let patterns = detector.patterns();
260 assert!(patterns.iter().all(|p| p.sequence.len() == 1));
261 }
262
263 #[test]
264 fn test_pattern_detection() {
265 let mut detector = PatternDetector::new(2);
266
267 let now = Instant::now();
268
269 for _ in 0..6 {
271 detector.record_event(ToolEvent {
272 tool_name: "tool_a".into(),
273 success: true,
274 duration_ms: 100,
275 timestamp: now,
276 });
277 detector.record_event(ToolEvent {
278 tool_name: "tool_b".into(),
279 success: true,
280 duration_ms: 50,
281 timestamp: now,
282 });
283 }
284
285 let patterns = detector.patterns();
286 assert!(!patterns.is_empty());
287 assert!(patterns[0].sequence.len() == 2);
288 }
289
290 #[test]
291 fn test_feature_vector() {
292 let mut detector = PatternDetector::new(2);
293 let now = Instant::now();
294
295 for i in 0u64..5u64 {
296 detector.record_event(ToolEvent {
297 tool_name: format!("tool_{}", i % 2),
298 success: i % 2 == 0,
299 duration_ms: 50 + i * 10,
300 timestamp: now,
301 });
302 }
303
304 let features = detector.feature_vector();
305 assert_eq!(features.len(), 5);
306 assert!(features.iter().all(|f| *f >= 0.0));
307 }
308
309 #[test]
310 fn test_success_rate() {
311 let mut detector = PatternDetector::new(2);
312 let now = Instant::now();
313
314 detector.record_event(ToolEvent {
315 tool_name: "tool_a".into(),
316 success: true,
317 duration_ms: 100,
318 timestamp: now,
319 });
320 detector.record_event(ToolEvent {
321 tool_name: "tool_b".into(),
322 success: false,
323 duration_ms: 50,
324 timestamp: now,
325 });
326 detector.record_event(ToolEvent {
327 tool_name: "tool_a".into(),
328 success: true,
329 duration_ms: 100,
330 timestamp: now,
331 });
332
333 let features = detector.feature_vector();
334 assert!(features[1] > 0.0 && features[1] < 1.0);
336 }
337}