1use pyo3::exceptions::PyRuntimeError;
2use pyo3::pyclass::PyClassGuardError;
3use pyo3::PyErr;
4use pythonize::PythonizeError;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum UtilError {
9 #[error("Failed to get parent path")]
10 GetParentPathError,
11
12 #[error("Failed to create directory")]
13 CreateDirectoryError,
14
15 #[error("Failed to read to create path")]
16 CreatePathError,
17
18 #[error(transparent)]
19 IoError(#[from] std::io::Error),
20
21 #[error(transparent)]
22 SerdeJsonError(#[from] serde_json::Error),
23}
24
25impl From<UtilError> for PyErr {
26 fn from(err: UtilError) -> PyErr {
27 let msg = err.to_string();
28 PyRuntimeError::new_err(msg)
29 }
30}
31
32#[derive(Error, Debug)]
33pub enum TypeError {
34 #[error("{0}")]
35 Error(String),
36
37 #[error("Start time must be before end time")]
38 StartTimeError,
39
40 #[error("Invalid schedule")]
41 InvalidScheduleError,
42
43 #[error("Invalid PSI threshold configuration")]
44 InvalidPsiThresholdError,
45
46 #[error("Invalid time interval for converting to start and end times")]
47 InvalidTimeIntervalError,
48
49 #[error("Invalid alert dispatch configuration")]
50 InvalidDispatchConfigError,
51
52 #[error("Invalid equal width binning method")]
53 InvalidEqualWidthBinningMethodError,
54
55 #[error("Missing space argument")]
56 MissingSpaceError,
57
58 #[error("Missing name argument")]
59 MissingNameError,
60
61 #[error("Missing version argument")]
62 MissingVersionError,
63
64 #[error("Missing uid argument")]
65 MissingUidError,
66
67 #[error("Missing alert_config argument")]
68 MissingAlertConfigError,
69
70 #[error("No metrics found")]
71 NoMetricsError,
72
73 #[error(transparent)]
74 SerdeJsonError(#[from] serde_json::Error),
75
76 #[error("Invalid number")]
77 InvalidNumberError,
78
79 #[error("Root must be an object")]
80 RootMustBeObject,
81
82 #[error("Unsupported type: {0}")]
83 UnsupportedType(String),
84
85 #[error("Failed to downcast Python object: {0}")]
86 DowncastError(String),
87
88 #[error("Invalid data type")]
89 InvalidDataType,
90
91 #[error("Missing value for string feature")]
92 MissingStringValueError,
93
94 #[error("{0}")]
95 PyError(String),
96
97 #[error(
98 "Unsupported feature type. Feature must be an integer, float or string. Received: {0}"
99 )]
100 UnsupportedFeatureTypeError(String),
101
102 #[error("Unsupported features type. Features must be a list of Feature instances or a dictionary of key value pairs. Received: {0}")]
103 UnsupportedFeaturesTypeError(String),
104
105 #[error("Unsupported metrics type. Metrics must be a list of Metric instances or a dictionary of key value pairs. Received: {0}")]
106 UnsupportedMetricsTypeError(String),
107
108 #[error("{0}")]
109 InvalidParameterError(String),
110
111 #[error("{0}")]
112 InvalidBinCountError(String),
113
114 #[error("{0}")]
115 InvalidValueError(String),
116
117 #[error("Empty Array Detected: {0}")]
118 EmptyArrayError(String),
119
120 #[error("Invalid binning strategy")]
121 InvalidBinningStrategyError,
122
123 #[error("Unsupported status. Status must be one of: All, Pending or Processed. Received: {0}")]
124 InvalidStatusError(String),
125
126 #[error("Failed to supply either input or response for the genai record")]
127 MissingInputOrResponse,
128
129 #[error("Invalid context type. Context must be a PyDict or a Pydantic BaseModel")]
130 MustBeDictOrBaseModel,
131
132 #[error("Failed to check if the context is a Pydantic BaseModel. Error: {0}")]
133 FailedToCheckPydanticModel(String),
134
135 #[error("Failed to import pydantic module. Error: {0}")]
136 FailedToImportPydantic(String),
137
138 #[error("Unsupported Python object type for conversion")]
139 UnsupportedPyObjectType,
140
141 #[error("Invalid dictionary key type. Dictionary keys must be strings, int, float or bool")]
142 InvalidDictKeyType,
143
144 #[error("Invalid compressions type")]
145 InvalidCompressionTypeError,
146
147 #[error("Invalid evaluation task type: {0}")]
148 InvalidEvalType(String),
149
150 #[error("Compression type not supported: {0}")]
151 CompressionTypeNotSupported(String),
152
153 #[error("Missing dependency: {0}")]
154 MissingDependency(String),
155
156 #[error("Expected a Python dict")]
157 ExpectedPyDict,
158
159 #[error("List contains an item that is neither AssertionTask nor LLMJudgeTask")]
160 InvalidAssertionTaskType,
161
162 #[error("{0}")]
163 FailedToCreateProfile(String),
164
165 #[error("Duplicate task IDs found in evaluation tasks")]
166 DuplicateTaskIds,
167
168 #[error("Key not found: {key}")]
169 KeyNotFound { key: String },
170
171 #[error("{0}")]
172 InvalidLength(String),
173
174 #[error(transparent)]
175 FromHexError(#[from] hex::FromHexError),
176
177 #[error(transparent)]
178 StdIOError(#[from] std::io::Error),
179
180 #[error(transparent)]
181 SerdeYamlError(#[from] serde_yaml::Error),
182
183 #[error("Array {index} out of bounds for length {length}")]
184 IndexOutOfBounds { index: isize, length: usize },
185
186 #[error("Expected an integer index or a slice")]
187 IndexOrSliceExpected,
188
189 #[error(transparent)]
190 PotatoTypeError(#[from] potato_head::TypeError),
191}
192
193impl From<pythonize::PythonizeError> for TypeError {
194 fn from(err: PythonizeError) -> Self {
195 TypeError::PyError(err.to_string())
196 }
197}
198
199impl<'a, 'py> From<pyo3::CastError<'a, 'py>> for TypeError {
200 fn from(err: pyo3::CastError<'a, 'py>) -> Self {
201 TypeError::DowncastError(err.to_string())
202 }
203}
204
205impl From<TypeError> for PyErr {
206 fn from(err: TypeError) -> PyErr {
207 let msg = err.to_string();
208 PyRuntimeError::new_err(msg)
209 }
210}
211
212impl From<PyErr> for TypeError {
213 fn from(err: PyErr) -> TypeError {
214 TypeError::PyError(err.to_string())
215 }
216}
217
218impl<'a, 'py> From<PyClassGuardError<'a, 'py>> for TypeError {
219 fn from(err: PyClassGuardError<'a, 'py>) -> Self {
220 TypeError::PyError(err.to_string())
221 }
222}
223
224#[derive(Error, Debug)]
225pub enum ContractError {
226 #[error(transparent)]
227 TypeError(#[from] TypeError),
228
229 #[error("{0}")]
230 PyError(String),
231}
232
233impl From<ContractError> for PyErr {
234 fn from(err: ContractError) -> PyErr {
235 let msg = err.to_string();
236 PyRuntimeError::new_err(msg)
237 }
238}
239
240impl From<PyErr> for ContractError {
241 fn from(err: PyErr) -> ContractError {
242 ContractError::PyError(err.to_string())
243 }
244}
245
246#[derive(Error, Debug)]
247pub enum RecordError {
248 #[error("Unable to extract record into any known ServerRecord variant")]
249 ExtractionError,
250
251 #[error("No server records found")]
252 EmptyServerRecordsError,
253
254 #[error(transparent)]
255 SerdeJsonError(#[from] serde_json::Error),
256
257 #[error("Unexpected record type")]
258 InvalidDriftTypeError,
259
260 #[error("{0}")]
261 PyError(String),
262
263 #[error("Failed to supply either input or response for the genai record")]
264 MissingInputOrResponse,
265
266 #[error(transparent)]
267 PotatoUtilError(#[from] potato_head::UtilError),
268
269 #[error(transparent)]
270 TypeError(#[from] TypeError),
271
272 #[error("Invalid context type. Context must be dictionary or Pydantic BaseModel")]
273 MustBeDictOrBaseModel,
274
275 #[error("Failed to downcast Python object: {0}")]
276 DowncastError(String),
277
278 #[error("{0}")]
279 SliceError(String),
280
281 #[error(transparent)]
282 FromHexError(#[from] hex::FromHexError),
283}
284
285impl<'a, 'py> From<pyo3::CastError<'a, 'py>> for RecordError {
286 fn from(err: pyo3::CastError) -> Self {
287 RecordError::DowncastError(err.to_string())
288 }
289}
290
291impl From<pythonize::PythonizeError> for RecordError {
292 fn from(err: PythonizeError) -> Self {
293 RecordError::PyError(err.to_string())
294 }
295}
296
297impl From<RecordError> for PyErr {
298 fn from(err: RecordError) -> PyErr {
299 let msg = err.to_string();
300 PyRuntimeError::new_err(msg)
301 }
302}
303
304impl From<PyErr> for RecordError {
305 fn from(err: PyErr) -> RecordError {
306 RecordError::PyError(err.to_string())
307 }
308}
309
310impl<'a, 'py> From<PyClassGuardError<'a, 'py>> for RecordError {
311 fn from(err: PyClassGuardError<'a, 'py>) -> Self {
312 RecordError::PyError(err.to_string())
313 }
314}
315
316#[derive(Error, Debug)]
317pub enum ProfileError {
318 #[error(transparent)]
319 SerdeJsonError(#[from] serde_json::Error),
320
321 #[error("Features and array are not the same length")]
322 FeatureArrayLengthError,
323
324 #[error("Unexpected record type")]
325 InvalidDriftTypeError,
326
327 #[error(transparent)]
328 UtilError(#[from] UtilError),
329
330 #[error(transparent)]
331 TypeError(#[from] TypeError),
332
333 #[error(transparent)]
334 IoError(#[from] std::io::Error),
335
336 #[error("Missing sample argument")]
337 MissingSampleError,
338
339 #[error("Missing sample size argument")]
340 MissingSampleSizeError,
341
342 #[error("Custom alert thresholds have not been set")]
343 CustomThresholdNotSetError,
344
345 #[error("Custom alert threshold not found")]
346 CustomAlertThresholdNotFound,
347
348 #[error("{0}")]
349 PyError(String),
350
351 #[error("Invalid binning strategy")]
352 InvalidBinningStrategyError,
353
354 #[error("Missing evaluation workflow")]
355 MissingWorkflowError,
356
357 #[error("Invalid argument for workflow. Argument must be a Workflow object")]
358 InvalidWorkflowType,
359
360 #[error(transparent)]
361 AgentError(#[from] potato_head::AgentError),
362
363 #[error(transparent)]
364 WorkflowError(#[from] potato_head::WorkflowError),
365
366 #[error("Invalid metric name found: {0}")]
367 InvalidMetricNameError(String),
368
369 #[error("No AssertionTasks or LLMJudgeTasks found in the workflow")]
370 EmptyTaskList,
371
372 #[error("LLM Metric requires at least one bound parameter")]
373 NeedAtLeastOneBoundParameterError(String),
374
375 #[error(
376 "Missing prompt in LLM Metric. If providing a list of metrics, prompt must be present"
377 )]
378 MissingPromptError(String),
379
380 #[error("No tasks found in the workflow when validating: {0}")]
381 NoTasksFoundError(String),
382
383 #[error("No metrics found for the output task: {0}")]
384 MetricNotFoundForOutputTask(String),
385
386 #[error("Metric not found in profile LLM metrics: {0}")]
387 MetricNotFound(String),
388
389 #[error(transparent)]
390 PotatoTypeError(#[from] potato_head::TypeError),
391
392 #[error("Invalid task type. Expected either AssertionTask or LLMJudgeTask: {0}")]
393 InvalidTaskType(String),
394
395 #[error("Detected circular dependency in evaluation tasks")]
396 CircularDependency,
397}
398
399impl From<ProfileError> for PyErr {
400 fn from(err: ProfileError) -> PyErr {
401 let msg = err.to_string();
402 PyRuntimeError::new_err(msg)
403 }
404}
405
406impl From<PyErr> for ProfileError {
407 fn from(err: PyErr) -> ProfileError {
408 ProfileError::PyError(err.to_string())
409 }
410}
411
412impl<'a, 'py> From<PyClassGuardError<'a, 'py>> for ProfileError {
413 fn from(err: PyClassGuardError<'a, 'py>) -> Self {
414 ProfileError::PyError(err.to_string())
415 }
416}
417
418impl<'a, 'py> From<pyo3::CastError<'a, 'py>> for ProfileError {
419 fn from(err: pyo3::CastError) -> Self {
420 ProfileError::PyError(err.to_string())
421 }
422}