baml_agent/
loop_detect.rs1pub struct LoopDetector {
16 last_signature: Option<String>,
17 repeat_count: usize,
18 abort_threshold: usize,
19 warn_threshold: usize,
20}
21
22#[derive(Debug, PartialEq)]
23pub enum LoopStatus {
24 Ok,
26 Warning(usize),
28 Abort(usize),
30}
31
32impl LoopDetector {
33 pub fn new(abort_threshold: usize) -> Self {
35 Self {
36 last_signature: None,
37 repeat_count: 0,
38 abort_threshold,
39 warn_threshold: abort_threshold / 2,
40 }
41 }
42
43 pub fn with_thresholds(warn_threshold: usize, abort_threshold: usize) -> Self {
45 Self {
46 last_signature: None,
47 repeat_count: 0,
48 abort_threshold,
49 warn_threshold,
50 }
51 }
52
53 pub fn check(&mut self, signature: &str) -> LoopStatus {
58 if self.last_signature.as_deref() == Some(signature) {
59 self.repeat_count += 1;
60 if self.repeat_count >= self.abort_threshold {
61 return LoopStatus::Abort(self.repeat_count);
62 }
63 if self.repeat_count >= self.warn_threshold {
64 return LoopStatus::Warning(self.repeat_count);
65 }
66 } else {
67 self.repeat_count = 1;
68 self.last_signature = Some(signature.into());
69 }
70 LoopStatus::Ok
71 }
72
73 pub fn reset(&mut self) {
75 self.last_signature = None;
76 self.repeat_count = 0;
77 }
78
79 pub fn repeat_count(&self) -> usize {
80 self.repeat_count
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn no_loop_different_sigs() {
90 let mut d = LoopDetector::new(6);
91 assert_eq!(d.check("a"), LoopStatus::Ok);
92 assert_eq!(d.check("b"), LoopStatus::Ok);
93 assert_eq!(d.check("c"), LoopStatus::Ok);
94 }
95
96 #[test]
97 fn warn_then_abort() {
98 let mut d = LoopDetector::new(6);
99 assert_eq!(d.check("x"), LoopStatus::Ok);
100 assert_eq!(d.check("x"), LoopStatus::Ok); assert_eq!(d.check("x"), LoopStatus::Warning(3)); assert_eq!(d.check("x"), LoopStatus::Warning(4));
103 assert_eq!(d.check("x"), LoopStatus::Warning(5));
104 assert_eq!(d.check("x"), LoopStatus::Abort(6)); }
106
107 #[test]
108 fn reset_clears() {
109 let mut d = LoopDetector::new(4);
110 d.check("x");
111 d.check("x");
112 d.check("x"); d.reset();
114 assert_eq!(d.check("x"), LoopStatus::Ok); }
116
117 #[test]
118 fn different_sig_resets_count() {
119 let mut d = LoopDetector::new(6);
120 d.check("x");
121 d.check("x");
122 d.check("x"); assert_eq!(d.check("y"), LoopStatus::Ok); assert_eq!(d.repeat_count(), 1);
125 }
126}