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 {
47 Self {
48 events: Vec::with_capacity(64),
49 patterns: HashMap::with_capacity(16),
50 sequence_length,
51 events_since_analysis: 0,
52 }
53 }
54
55 pub fn record_event(&mut self, event: ToolEvent) {
57 if self.events.len() >= MAX_EVENTS {
59 let drain_count = MAX_EVENTS / 10; self.events.drain(..drain_count);
61 }
62
63 self.events.push(event);
64 self.events_since_analysis += 1;
65
66 if self.events_since_analysis >= ANALYZE_INTERVAL {
68 self.analyze();
69 self.events_since_analysis = 0;
70 }
71 }
72
73 fn analyze(&mut self) {
75 if self.events.len() < self.sequence_length {
76 return;
77 }
78
79 let mut sequence_map: HashMap<Vec<&str>, Vec<&ToolEvent>> = HashMap::new();
80
81 for window in self.events.windows(self.sequence_length) {
83 let seq: Vec<&str> = window.iter().map(|e| e.tool_name.as_str()).collect();
84
85 let entry = sequence_map.entry(seq.clone()).or_default();
87 for event in window {
88 entry.push(event);
89 }
90 }
91
92 for (sequence, events) in sequence_map {
94 let frequency = events.len() / self.sequence_length;
97 if frequency >= 2 {
98 let success_count = events.iter().filter(|e| e.success).count();
100 let success_rate = success_count as f64 / events.len() as f64;
101 let avg_duration = events.iter().map(|e| e.duration_ms).sum::<u64>() / events.len() as u64;
102
103 let confidence = (success_rate * (frequency as f64 / 10.0).min(1.0)).min(1.0);
105
106 let sequence_vec = sequence.iter().map(|s| s.to_string()).collect::<Vec<String>>();
107 let pattern_name = format!("pattern_{:x}", hash_sequence(&sequence_vec));
108
109 self.patterns.insert(
110 pattern_name.clone(),
111 DetectedPattern {
112 name: pattern_name,
113 sequence: sequence_vec,
114 frequency,
115 success_rate,
116 avg_duration_ms: avg_duration,
117 confidence,
118 },
119 );
120 }
121 }
122 }
123
124 pub fn patterns(&self) -> Vec<DetectedPattern> {
126 let mut patterns: Vec<_> = self.patterns.values().cloned().collect();
127 patterns.sort_unstable_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal));
128 patterns
129 }
130
131 pub fn feature_vector(&self) -> Vec<f64> {
147 let mut features = Vec::with_capacity(5);
148
149 features.push(self.events.len() as f64);
151
152 let success_rate = self.events.iter().filter(|e| e.success).count() as f64 / self.events.len().max(1) as f64;
154 features.push(success_rate);
155
156 let avg_duration =
158 self.events.iter().map(|e| e.duration_ms).sum::<u64>() as f64 / self.events.len().max(1) as f64;
159 features.push(avg_duration);
160
161 let unique_tools = self
163 .events
164 .iter()
165 .map(|e| &e.tool_name)
166 .collect::<hashbrown::HashSet<_>>()
167 .len() as f64;
168 features.push(unique_tools);
169
170 let pattern_density = self.patterns.len() as f64 / self.events.len().max(1) as f64;
172 features.push(pattern_density);
173
174 clamp_features(&features)
176 }
177
178 pub fn reset(&mut self) {
180 self.events.clear();
181 self.patterns.clear();
182 self.events_since_analysis = 0;
183 }
184
185 pub fn to_json(&self) -> Value {
187 json!({
188 "event_count": self.events.len(),
189 "pattern_count": self.patterns.len(),
190 "patterns": self.patterns()
191 .iter()
192 .map(|p| json!({
193 "name": p.name,
194 "sequence": p.sequence,
195 "frequency": p.frequency,
196 "success_rate": p.success_rate,
197 "avg_duration_ms": p.avg_duration_ms,
198 "confidence": p.confidence,
199 }))
200 .collect::<Vec<_>>(),
201 "feature_vector": self.feature_vector(),
202 })
203 }
204}
205
206fn clamp_features(features: &[f64]) -> Vec<f64> {
211 features
212 .iter()
213 .enumerate()
214 .map(|(i, &f)| {
215 if i == 0 {
216 f } else {
218 f.clamp(0.0, 1.0)
219 }
220 })
221 .collect()
222}
223
224fn hash_sequence(seq: &[String]) -> u64 {
226 let mut hash: u64 = 0;
227 for s in seq {
228 for b in s.bytes() {
229 hash = hash.wrapping_mul(31).wrapping_add(b as u64);
230 }
231 }
232 hash
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use std::time::Instant;
239
240 #[test]
241 fn test_pattern_detection() {
242 let mut detector = PatternDetector::new(2);
243
244 let now = Instant::now();
245
246 for _ in 0..6 {
248 detector.record_event(ToolEvent {
249 tool_name: "tool_a".into(),
250 success: true,
251 duration_ms: 100,
252 timestamp: now,
253 });
254 detector.record_event(ToolEvent {
255 tool_name: "tool_b".into(),
256 success: true,
257 duration_ms: 50,
258 timestamp: now,
259 });
260 }
261
262 let patterns = detector.patterns();
263 assert!(!patterns.is_empty());
264 assert!(patterns[0].sequence.len() == 2);
265 }
266
267 #[test]
268 fn test_feature_vector() {
269 let mut detector = PatternDetector::new(2);
270 let now = Instant::now();
271
272 for i in 0u64..5u64 {
273 detector.record_event(ToolEvent {
274 tool_name: format!("tool_{}", i % 2),
275 success: i % 2 == 0,
276 duration_ms: 50 + i * 10,
277 timestamp: now,
278 });
279 }
280
281 let features = detector.feature_vector();
282 assert_eq!(features.len(), 5);
283 assert!(features.iter().all(|f| *f >= 0.0));
284 }
285
286 #[test]
287 fn test_success_rate() {
288 let mut detector = PatternDetector::new(2);
289 let now = Instant::now();
290
291 detector.record_event(ToolEvent {
292 tool_name: "tool_a".into(),
293 success: true,
294 duration_ms: 100,
295 timestamp: now,
296 });
297 detector.record_event(ToolEvent {
298 tool_name: "tool_b".into(),
299 success: false,
300 duration_ms: 50,
301 timestamp: now,
302 });
303 detector.record_event(ToolEvent {
304 tool_name: "tool_a".into(),
305 success: true,
306 duration_ms: 100,
307 timestamp: now,
308 });
309
310 let features = detector.feature_vector();
311 assert!(features[1] > 0.0 && features[1] < 1.0);
313 }
314}