scouter_types/genai/
alert.rs1use crate::error::TypeError;
2use crate::AlertCondition;
3use crate::{
4 dispatch::AlertDispatchType, AlertDispatchConfig, AlertThreshold, CommonCrons,
5 DispatchAlertDescription, OpsGenieDispatchConfig, SlackDispatchConfig, ValidateAlertConfig,
6};
7use core::fmt::Debug;
8use pyo3::prelude::*;
9use pyo3::types::PyString;
10use serde::{Deserialize, Serialize};
11
12#[pyclass]
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
14pub struct GenAIAlertConfig {
15 pub dispatch_config: AlertDispatchConfig,
16
17 #[pyo3(get, set)]
18 pub schedule: String,
19
20 #[pyo3(get, set)]
21 pub alert_condition: Option<AlertCondition>,
22}
23
24impl ValidateAlertConfig for GenAIAlertConfig {}
25
26#[pymethods]
27impl GenAIAlertConfig {
28 #[new]
29 #[pyo3(signature = (schedule=None, dispatch_config=None, alert_condition=None))]
30 pub fn new(
31 schedule: Option<&Bound<'_, PyAny>>,
32 dispatch_config: Option<&Bound<'_, PyAny>>,
33 alert_condition: Option<AlertCondition>,
34 ) -> Result<Self, TypeError> {
35 let alert_dispatch_config = match dispatch_config {
36 None => AlertDispatchConfig::default(),
37 Some(config) => {
38 if config.is_instance_of::<SlackDispatchConfig>() {
39 AlertDispatchConfig::Slack(config.extract::<SlackDispatchConfig>()?)
40 } else if config.is_instance_of::<OpsGenieDispatchConfig>() {
41 AlertDispatchConfig::OpsGenie(config.extract::<OpsGenieDispatchConfig>()?)
42 } else {
43 AlertDispatchConfig::default()
44 }
45 }
46 };
47
48 let schedule = match schedule {
49 Some(schedule) => {
50 if schedule.is_instance_of::<PyString>() {
51 schedule.to_string()
52 } else if schedule.is_instance_of::<CommonCrons>() {
53 schedule.extract::<CommonCrons>().unwrap().cron()
54 } else {
55 return Err(TypeError::InvalidScheduleError)?;
56 }
57 }
58 None => CommonCrons::EveryDay.cron(),
59 };
60
61 let schedule = Self::resolve_schedule(&schedule);
62
63 Ok(Self {
64 schedule,
65 dispatch_config: alert_dispatch_config,
66 alert_condition,
67 })
68 }
69
70 #[getter]
71 pub fn dispatch_type(&self) -> AlertDispatchType {
72 self.dispatch_config.dispatch_type()
73 }
74
75 #[getter]
76 pub fn dispatch_config<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
77 self.dispatch_config.config(py)
78 }
79}
80
81impl Default for GenAIAlertConfig {
82 fn default() -> GenAIAlertConfig {
83 Self {
84 dispatch_config: AlertDispatchConfig::default(),
85 schedule: CommonCrons::EveryDay.cron(),
86 alert_condition: None,
87 }
88 }
89}
90
91pub struct PromptComparisonMetricAlert {
92 pub metric_name: String,
93 pub training_metric_value: f64,
94 pub observed_metric_value: f64,
95 pub alert_threshold_value: Option<f64>,
96 pub alert_threshold: AlertThreshold,
97}
98
99impl PromptComparisonMetricAlert {
100 fn alert_description_header(&self) -> String {
101 let below_threshold = |boundary: Option<f64>| match boundary {
102 Some(b) => format!(
103 "The observed {} metric value has dropped below the threshold (initial value - {})",
104 self.metric_name, b
105 ),
106 None => format!(
107 "The {} metric value has dropped below the initial value",
108 self.metric_name
109 ),
110 };
111
112 let above_threshold = |boundary: Option<f64>| match boundary {
113 Some(b) => format!(
114 "The {} metric value has increased beyond the threshold (initial value + {})",
115 self.metric_name, b
116 ),
117 None => format!(
118 "The {} metric value has increased beyond the initial value",
119 self.metric_name
120 ),
121 };
122
123 let outside_threshold = |boundary: Option<f64>| match boundary {
124 Some(b) => format!(
125 "The {} metric value has fallen outside the threshold (initial value ± {})",
126 self.metric_name, b,
127 ),
128 None => format!(
129 "The metric value has fallen outside the initial value for {}",
130 self.metric_name
131 ),
132 };
133
134 match self.alert_threshold {
135 AlertThreshold::Below => below_threshold(self.alert_threshold_value),
136 AlertThreshold::Above => above_threshold(self.alert_threshold_value),
137 AlertThreshold::Outside => outside_threshold(self.alert_threshold_value),
138 }
139 }
140}
141
142impl DispatchAlertDescription for PromptComparisonMetricAlert {
143 fn create_alert_description(&self, _dispatch_type: AlertDispatchType) -> String {
145 let mut alert_description = String::new();
146 let header = format!("{}\n", self.alert_description_header());
147 alert_description.push_str(&header);
148
149 let current_metric = format!("Current Metric Value: {}\n", self.observed_metric_value);
150 let historical_metric = format!("Initial Metric Value: {}\n", self.training_metric_value);
151
152 alert_description.push_str(&historical_metric);
153 alert_description.push_str(¤t_metric);
154
155 alert_description
156 }
157}
158
159#[cfg(test)]
160#[cfg(feature = "mock")]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_alert_config() {
166 let dispatch_config = AlertDispatchConfig::OpsGenie(OpsGenieDispatchConfig {
168 team: "test-team".to_string(),
169 priority: "P5".to_string(),
170 });
171 let schedule = "0 0 * * * *".to_string();
172 let alert_condition = AlertCondition::new(5.0, AlertThreshold::Above, None);
173 let alert_config = GenAIAlertConfig {
174 dispatch_config,
175 schedule,
176 alert_condition: Some(alert_condition.clone()),
177 };
178 assert_eq!(alert_config.dispatch_type(), AlertDispatchType::OpsGenie);
179 }
180}