Skip to main content

batty_cli/shim/
meta_detector.rs

1use std::collections::VecDeque;
2
3const WINDOW_CAPACITY: usize = 10;
4const REDIRECT_FAILURE_THRESHOLD: u8 = 2;
5const META_PATTERNS: [&str; 5] = [
6    "I apologize",
7    "I cannot",
8    "As an AI",
9    "Let me explain why",
10    "I understand your frustration",
11];
12
13pub struct MetaDetector {
14    window: VecDeque<bool>,
15    redirect_failures: u8,
16}
17
18impl MetaDetector {
19    pub fn new() -> Self {
20        Self {
21            window: VecDeque::with_capacity(WINDOW_CAPACITY),
22            redirect_failures: 0,
23        }
24    }
25
26    pub fn push_line(&mut self, line: &str) -> bool {
27        let is_meta = META_PATTERNS.iter().any(|pattern| line.contains(pattern));
28
29        if self.window.len() == WINDOW_CAPACITY {
30            self.window.pop_front();
31        }
32        self.window.push_back(is_meta);
33
34        if is_meta {
35            self.redirect_failures = self.redirect_failures.saturating_add(1);
36        } else {
37            self.redirect_failures = 0;
38        }
39
40        self.window.iter().filter(|&&flag| flag).count() >= 5
41    }
42
43    pub fn should_restart(&self) -> bool {
44        self.redirect_failures >= REDIRECT_FAILURE_THRESHOLD
45    }
46
47    pub fn reset(&mut self) {
48        self.window.clear();
49        self.redirect_failures = 0;
50    }
51}
52
53impl Default for MetaDetector {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::MetaDetector;
62
63    #[test]
64    fn detects_meta_patterns() {
65        let mut detector = MetaDetector::new();
66
67        assert!(!detector.push_line("I apologize, but I cannot help with that."));
68        assert!(!detector.should_restart());
69        detector.reset();
70        assert!(!detector.push_line("As an AI, I have to refuse."));
71        assert!(!detector.should_restart());
72        detector.push_line("normal output");
73        assert!(!detector.should_restart());
74    }
75
76    #[test]
77    fn triggers_after_five_meta_lines_in_window() {
78        let mut detector = MetaDetector::new();
79
80        for _ in 0..4 {
81            assert!(!detector.push_line("I apologize for the confusion."));
82        }
83        assert!(detector.push_line("Let me explain why this is blocked."));
84
85        detector.reset();
86
87        for _ in 0..5 {
88            detector.push_line("normal output");
89        }
90        for _ in 0..4 {
91            assert!(!detector.push_line("I understand your frustration."));
92        }
93        assert!(detector.push_line("As an AI, I need to stop here."));
94    }
95
96    #[test]
97    fn restart_triggers_after_two_failed_redirects() {
98        let mut detector = MetaDetector::new();
99
100        assert!(!detector.should_restart());
101        detector.push_line("I cannot continue with that request.");
102        assert!(!detector.should_restart());
103        detector.push_line("I apologize, but I cannot do that.");
104        assert!(detector.should_restart());
105
106        detector.reset();
107        assert!(!detector.should_restart());
108    }
109}