Skip to main content

cel_context/
confidence.rs

1use serde::{Deserialize, Serialize};
2
3/// Confidence thresholds for agent behavior.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ConfidenceThresholds {
6    /// Above this: act immediately (default 0.9).
7    pub act_immediately: f64,
8    /// Above this: act and log for review (default 0.7).
9    pub act_and_log: f64,
10    /// Above this: act cautiously, verify result (default 0.5).
11    /// Below this threshold: pause and notify user.
12    pub act_cautiously: f64,
13}
14
15impl Default for ConfidenceThresholds {
16    fn default() -> Self {
17        Self {
18            act_immediately: 0.9,
19            act_and_log: 0.7,
20            act_cautiously: 0.5,
21        }
22    }
23}
24
25/// What behavior the agent should exhibit given a confidence score.
26#[derive(Debug, Clone, PartialEq)]
27pub enum ConfidenceBehavior {
28    /// 0.9-1.0: Act immediately, no hesitation.
29    ActImmediately,
30    /// 0.7-0.9: Act and log for review.
31    ActAndLog,
32    /// 0.5-0.7: Act cautiously, verify result.
33    ActCautiously,
34    /// Below 0.5: Pause, notify user, wait for instruction.
35    PauseAndNotify,
36}
37
38impl ConfidenceThresholds {
39    /// Determine behavior for a given confidence score.
40    pub fn behavior_for(&self, confidence: f64) -> ConfidenceBehavior {
41        if confidence >= self.act_immediately {
42            ConfidenceBehavior::ActImmediately
43        } else if confidence >= self.act_and_log {
44            ConfidenceBehavior::ActAndLog
45        } else if confidence >= self.act_cautiously {
46            ConfidenceBehavior::ActCautiously
47        } else {
48            ConfidenceBehavior::PauseAndNotify
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_confidence_thresholds_default_values() {
59        let thresholds = ConfidenceThresholds::default();
60        assert_eq!(thresholds.act_immediately, 0.9);
61        assert_eq!(thresholds.act_and_log, 0.7);
62        assert_eq!(thresholds.act_cautiously, 0.5);
63    }
64
65    #[test]
66    fn test_confidence_behavior_mapping() {
67        let thresholds = ConfidenceThresholds::default();
68
69        assert_eq!(
70            thresholds.behavior_for(0.95),
71            ConfidenceBehavior::ActImmediately
72        );
73        assert_eq!(
74            thresholds.behavior_for(0.9),
75            ConfidenceBehavior::ActImmediately
76        );
77        assert_eq!(thresholds.behavior_for(0.85), ConfidenceBehavior::ActAndLog);
78        assert_eq!(thresholds.behavior_for(0.7), ConfidenceBehavior::ActAndLog);
79        assert_eq!(
80            thresholds.behavior_for(0.6),
81            ConfidenceBehavior::ActCautiously
82        );
83        assert_eq!(
84            thresholds.behavior_for(0.5),
85            ConfidenceBehavior::ActCautiously
86        );
87        assert_eq!(
88            thresholds.behavior_for(0.3),
89            ConfidenceBehavior::PauseAndNotify
90        );
91        assert_eq!(
92            thresholds.behavior_for(0.0),
93            ConfidenceBehavior::PauseAndNotify
94        );
95    }
96
97    #[test]
98    fn test_boundary_values_exact() {
99        let thresholds = ConfidenceThresholds::default();
100
101        // Exactly at boundaries
102        assert_eq!(
103            thresholds.behavior_for(0.9),
104            ConfidenceBehavior::ActImmediately
105        );
106        assert_eq!(thresholds.behavior_for(0.7), ConfidenceBehavior::ActAndLog);
107        assert_eq!(
108            thresholds.behavior_for(0.5),
109            ConfidenceBehavior::ActCautiously
110        );
111
112        // Just below boundaries
113        assert_eq!(
114            thresholds.behavior_for(0.8999),
115            ConfidenceBehavior::ActAndLog
116        );
117        assert_eq!(
118            thresholds.behavior_for(0.6999),
119            ConfidenceBehavior::ActCautiously
120        );
121        assert_eq!(
122            thresholds.behavior_for(0.4999),
123            ConfidenceBehavior::PauseAndNotify
124        );
125    }
126
127    #[test]
128    fn test_extreme_values() {
129        let thresholds = ConfidenceThresholds::default();
130
131        assert_eq!(
132            thresholds.behavior_for(1.0),
133            ConfidenceBehavior::ActImmediately
134        );
135        assert_eq!(
136            thresholds.behavior_for(0.0),
137            ConfidenceBehavior::PauseAndNotify
138        );
139    }
140
141    #[test]
142    fn test_negative_confidence() {
143        let thresholds = ConfidenceThresholds::default();
144        // Negative values should result in PauseAndNotify (lowest tier)
145        assert_eq!(
146            thresholds.behavior_for(-0.1),
147            ConfidenceBehavior::PauseAndNotify
148        );
149        assert_eq!(
150            thresholds.behavior_for(-1.0),
151            ConfidenceBehavior::PauseAndNotify
152        );
153    }
154
155    #[test]
156    fn test_over_one_confidence() {
157        let thresholds = ConfidenceThresholds::default();
158        // Values > 1.0 should still map to ActImmediately
159        assert_eq!(
160            thresholds.behavior_for(1.5),
161            ConfidenceBehavior::ActImmediately
162        );
163        assert_eq!(
164            thresholds.behavior_for(100.0),
165            ConfidenceBehavior::ActImmediately
166        );
167    }
168
169    #[test]
170    fn test_custom_thresholds() {
171        let thresholds = ConfidenceThresholds {
172            act_immediately: 0.95,
173            act_and_log: 0.8,
174            act_cautiously: 0.6,
175        };
176
177        assert_eq!(
178            thresholds.behavior_for(0.96),
179            ConfidenceBehavior::ActImmediately
180        );
181        assert_eq!(thresholds.behavior_for(0.94), ConfidenceBehavior::ActAndLog);
182        assert_eq!(
183            thresholds.behavior_for(0.79),
184            ConfidenceBehavior::ActCautiously
185        );
186        assert_eq!(
187            thresholds.behavior_for(0.59),
188            ConfidenceBehavior::PauseAndNotify
189        );
190    }
191
192    #[test]
193    fn test_serialization_roundtrip() {
194        let thresholds = ConfidenceThresholds::default();
195        let json = serde_json::to_string(&thresholds).unwrap();
196        let deserialized: ConfidenceThresholds = serde_json::from_str(&json).unwrap();
197
198        assert_eq!(deserialized.act_immediately, thresholds.act_immediately);
199        assert_eq!(deserialized.act_and_log, thresholds.act_and_log);
200        assert_eq!(deserialized.act_cautiously, thresholds.act_cautiously);
201    }
202
203    #[test]
204    fn test_behavior_is_monotonic() {
205        // As confidence increases, behavior should only get more permissive (or stay same)
206        let thresholds = ConfidenceThresholds::default();
207        let behaviors: Vec<ConfidenceBehavior> = (0..=100)
208            .map(|i| thresholds.behavior_for(i as f64 / 100.0))
209            .collect();
210
211        let rank = |b: &ConfidenceBehavior| -> u8 {
212            match b {
213                ConfidenceBehavior::PauseAndNotify => 0,
214                ConfidenceBehavior::ActCautiously => 1,
215                ConfidenceBehavior::ActAndLog => 2,
216                ConfidenceBehavior::ActImmediately => 3,
217            }
218        };
219
220        for i in 1..behaviors.len() {
221            assert!(
222                rank(&behaviors[i]) >= rank(&behaviors[i - 1]),
223                "Behavior should be monotonically non-decreasing with confidence. \
224                 At {}/100: {:?}, at {}/100: {:?}",
225                i - 1,
226                behaviors[i - 1],
227                i,
228                behaviors[i]
229            );
230        }
231    }
232}