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