arthur-common 0.0.0.post0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. arthur_common/__init__.py +0 -0
  2. arthur_common/aggregations/__init__.py +2 -0
  3. arthur_common/aggregations/aggregator.py +304 -0
  4. arthur_common/aggregations/functions/README.md +26 -0
  5. arthur_common/aggregations/functions/__init__.py +25 -0
  6. arthur_common/aggregations/functions/agentic_aggregations.py +1891 -0
  7. arthur_common/aggregations/functions/categorical_count.py +139 -0
  8. arthur_common/aggregations/functions/confusion_matrix.py +521 -0
  9. arthur_common/aggregations/functions/inference_count.py +112 -0
  10. arthur_common/aggregations/functions/inference_count_by_class.py +286 -0
  11. arthur_common/aggregations/functions/inference_null_count.py +130 -0
  12. arthur_common/aggregations/functions/mean_absolute_error.py +164 -0
  13. arthur_common/aggregations/functions/mean_squared_error.py +164 -0
  14. arthur_common/aggregations/functions/multiclass_confusion_matrix.py +287 -0
  15. arthur_common/aggregations/functions/multiclass_inference_count_by_class.py +118 -0
  16. arthur_common/aggregations/functions/numeric_stats.py +135 -0
  17. arthur_common/aggregations/functions/numeric_sum.py +135 -0
  18. arthur_common/aggregations/functions/py.typed +0 -0
  19. arthur_common/aggregations/functions/shield_aggregations.py +1161 -0
  20. arthur_common/aggregations/py.typed +0 -0
  21. arthur_common/config/__init__.py +0 -0
  22. arthur_common/config/config.py +42 -0
  23. arthur_common/config/settings.yaml +4 -0
  24. arthur_common/models/__init__.py +0 -0
  25. arthur_common/models/agent_governance_schemas.py +170 -0
  26. arthur_common/models/audit_log_schemas.py +42 -0
  27. arthur_common/models/common_schemas.py +214 -0
  28. arthur_common/models/connectors.py +80 -0
  29. arthur_common/models/constants.py +24 -0
  30. arthur_common/models/datasets.py +14 -0
  31. arthur_common/models/enums.py +203 -0
  32. arthur_common/models/llm_model_providers.py +374 -0
  33. arthur_common/models/metric_schemas.py +63 -0
  34. arthur_common/models/metrics.py +297 -0
  35. arthur_common/models/py.typed +0 -0
  36. arthur_common/models/request_schemas.py +927 -0
  37. arthur_common/models/response_schemas.py +855 -0
  38. arthur_common/models/schema_definitions.py +656 -0
  39. arthur_common/models/task_eval_schemas.py +150 -0
  40. arthur_common/models/task_job_specs.py +102 -0
  41. arthur_common/py.typed +0 -0
  42. arthur_common/tools/__init__.py +0 -0
  43. arthur_common/tools/aggregation_analyzer.py +274 -0
  44. arthur_common/tools/aggregation_loader.py +59 -0
  45. arthur_common/tools/duckdb_data_loader.py +401 -0
  46. arthur_common/tools/duckdb_utils.py +32 -0
  47. arthur_common/tools/functions.py +46 -0
  48. arthur_common/tools/py.typed +0 -0
  49. arthur_common/tools/schema_inferer.py +122 -0
  50. arthur_common/tools/time_utils.py +33 -0
  51. arthur_common-0.0.0.post0.dist-info/METADATA +65 -0
  52. arthur_common-0.0.0.post0.dist-info/RECORD +53 -0
  53. arthur_common-0.0.0.post0.dist-info/WHEEL +4 -0
File without changes
@@ -0,0 +1,2 @@
1
+ from .aggregator import * # noqa
2
+ from .functions import * # noqa
@@ -0,0 +1,304 @@
1
+ import os
2
+ import re
3
+ from abc import ABC, abstractmethod
4
+ from base64 import b64encode
5
+ from typing import Any, Type, Union, cast
6
+
7
+ import pandas as pd
8
+ from datasketches import kll_floats_sketch
9
+ from duckdb import DuckDBPyConnection
10
+
11
+ from arthur_common.models.metrics import *
12
+
13
+
14
+ class AggregationFunction(ABC):
15
+ FEATURE_FLAG_NAME: str | None = None
16
+
17
+ @staticmethod
18
+ @abstractmethod
19
+ def id() -> UUID:
20
+ raise NotImplementedError
21
+
22
+ @staticmethod
23
+ @abstractmethod
24
+ def display_name() -> str:
25
+ raise NotImplementedError
26
+
27
+ @staticmethod
28
+ @abstractmethod
29
+ def description() -> str:
30
+ raise NotImplementedError
31
+
32
+ @abstractmethod
33
+ def aggregation_type(self) -> Type[SketchMetric] | Type[NumericMetric]:
34
+ raise NotImplementedError
35
+
36
+ @staticmethod
37
+ @abstractmethod
38
+ def reported_aggregations() -> list[BaseReportedAggregation]:
39
+ """Returns the list of aggregations reported by the aggregate function."""
40
+ raise NotImplementedError
41
+
42
+ @staticmethod
43
+ def get_innermost_segmentation_columns(segmentation_cols: list[str]) -> list[str]:
44
+ """
45
+ Extracts the innermost column name for nested segmentation columns or
46
+ returns the top-level column name for non-nested segmentation columns.
47
+ """
48
+ for i, col in enumerate(segmentation_cols):
49
+ # extract the innermost column for escaped column names (e.g. '"nested.col"."name"')
50
+ # otherwise return the name since it's a top-level column
51
+ if col.startswith('"') and col.endswith('"'):
52
+ identifier = col[1:-1]
53
+ identifier_split_in_struct_fields = re.split(r'"\."', identifier)
54
+
55
+ # For nested columns, take just the innermost field name
56
+ # Otherwise for top-level columns, take the whole name
57
+ if len(identifier_split_in_struct_fields) > 1:
58
+ innermost_field = identifier_split_in_struct_fields[-1]
59
+ segmentation_cols[i] = innermost_field.replace('""', '"')
60
+ else:
61
+ segmentation_cols[i] = identifier.replace('""', '"')
62
+ else:
63
+ segmentation_cols[i] = col
64
+
65
+ return segmentation_cols
66
+
67
+ @abstractmethod
68
+ def aggregate(
69
+ self,
70
+ ddb_conn: DuckDBPyConnection,
71
+ *args: Any,
72
+ **kwargs: Any,
73
+ ) -> Union[list[SketchMetric], list[NumericMetric]]:
74
+ raise NotImplementedError
75
+
76
+ @staticmethod
77
+ def string_to_dimension(name: str, value: str | None) -> Dimension:
78
+ if value is None:
79
+ value = "null"
80
+ return Dimension(name=name, value=str(value))
81
+
82
+ def is_feature_flag_enabled(self, feature_flag_name: str) -> bool:
83
+ if feature_flag_name is None:
84
+ value = os.getenv(self.FEATURE_FLAG_NAME, "false")
85
+ else:
86
+ value = os.getenv(feature_flag_name, "false")
87
+ return value.lower() in ("true", "1", "yes")
88
+
89
+
90
+ class NumericAggregationFunction(AggregationFunction, ABC):
91
+ def aggregation_type(self) -> Type[NumericMetric]:
92
+ return NumericMetric
93
+
94
+ @abstractmethod
95
+ def aggregate(
96
+ self,
97
+ ddb_conn: DuckDBPyConnection,
98
+ *args: Any,
99
+ **kwargs: Any,
100
+ ) -> list[NumericMetric]:
101
+ raise NotImplementedError
102
+
103
+ @staticmethod
104
+ def group_query_results_to_numeric_metrics(
105
+ data: pd.DataFrame,
106
+ value_col: str,
107
+ dim_columns: list[str],
108
+ timestamp_col: str,
109
+ ) -> list[NumericTimeSeries]:
110
+ """
111
+ Convert a grouped dataframe with repeated dimensions to internal numeric metric definition.
112
+
113
+ At a high level, the query results are already grouped, however,
114
+ the order isn't guaranteed that groups are sequential (this requires an explicit ORDER BY on the source query.)
115
+ What this function does is group by the indicated dimensions list, and from each group extract the dimension values once.
116
+ From there, iterate over the group turning each data point to a *Point. At the end, this single instance of the group metrics
117
+ and the list of points (values) are merged to one *TimeSeries
118
+ """
119
+ if not dim_columns:
120
+ return [
121
+ NumericAggregationFunction._dimensionless_query_results_to_numeric_metrics(
122
+ data,
123
+ value_col,
124
+ timestamp_col,
125
+ ),
126
+ ]
127
+
128
+ # get innermost column name for nested segmentation columns
129
+ dim_columns = AggregationFunction.get_innermost_segmentation_columns(
130
+ dim_columns,
131
+ )
132
+
133
+ calculated_metrics: list[NumericTimeSeries] = []
134
+ # make sure dropna is False or rows with "null" as a dimension value will be dropped
135
+ groups = data.groupby(dim_columns, dropna=False)
136
+ for _, group in groups:
137
+ dimensions: list[Dimension] = []
138
+ # Get the first row of the group to determine the group level dimensions
139
+ dims_row = group.iloc[0]
140
+ for dim in dim_columns:
141
+ d = AggregationFunction.string_to_dimension(
142
+ name=dim,
143
+ value=dims_row[dim],
144
+ )
145
+ dimensions.append(d)
146
+
147
+ values: list[NumericPoint] = []
148
+ for _, row in group.iterrows():
149
+ # Skip NaN values
150
+ if pd.notna(row[value_col]):
151
+ values.append(
152
+ NumericPoint(
153
+ timestamp=row[timestamp_col], value=row[value_col]
154
+ ),
155
+ )
156
+ # Only add the series if it has values
157
+ if values:
158
+ calculated_metrics.append(
159
+ NumericTimeSeries(values=values, dimensions=dimensions),
160
+ )
161
+
162
+ return calculated_metrics
163
+
164
+ @staticmethod
165
+ def _dimensionless_query_results_to_numeric_metrics(
166
+ data: pd.DataFrame,
167
+ value_col: str,
168
+ timestamp_col: str,
169
+ ) -> NumericTimeSeries:
170
+ """
171
+ Convert a dimensionless time / value series to internal numeric metric definition.
172
+ """
173
+ values: list[NumericPoint] = []
174
+ for _, row in data.iterrows():
175
+ # Skip NaN values
176
+ if pd.notna(row[value_col]):
177
+ values.append(
178
+ NumericPoint(timestamp=row[timestamp_col], value=row[value_col]),
179
+ )
180
+ return NumericTimeSeries(values=values, dimensions=[])
181
+
182
+ @staticmethod
183
+ def series_to_metric(
184
+ metric_name: str,
185
+ series: list[NumericTimeSeries],
186
+ ) -> NumericMetric:
187
+ return NumericMetric(name=metric_name, numeric_series=series)
188
+
189
+
190
+ class SketchAggregationFunction(AggregationFunction, ABC):
191
+ def aggregation_type(self) -> Type[SketchMetric]:
192
+ return SketchMetric
193
+
194
+ @abstractmethod
195
+ def aggregate(
196
+ self,
197
+ ddb_conn: DuckDBPyConnection,
198
+ *args: Any,
199
+ **kwargs: Any,
200
+ ) -> list[SketchMetric]:
201
+ raise NotImplementedError
202
+
203
+ @staticmethod
204
+ def group_query_results_to_sketch_metrics(
205
+ data: pd.DataFrame,
206
+ value_col: str,
207
+ dim_columns: list[str],
208
+ timestamp_col: str,
209
+ ) -> list[SketchTimeSeries]:
210
+ """
211
+ Convert a grouped dataframe with repeated dimensions to internal sketch metric definition.
212
+
213
+ For sketch data, what we're doing is grouping the raw row data into the dimensions we care about.
214
+ Within each group, we extract the dimensions once. Within this single dimension group,
215
+ we group the data into 5min intervals. Within each interval, the data point we care to sketch is added to the sketch.
216
+
217
+ """
218
+
219
+ calculated_metrics: list[SketchTimeSeries] = []
220
+
221
+ # get innermost column name for nested segmentation columns
222
+ dim_columns = AggregationFunction.get_innermost_segmentation_columns(
223
+ dim_columns,
224
+ )
225
+
226
+ if dim_columns:
227
+ # make sure dropna is False or rows with "null" as a dimension value will be dropped
228
+ # call _group_to_series for each grouped DF
229
+ groups = data.groupby(dim_columns, dropna=False)
230
+ for _, group in groups:
231
+ calculated_metrics.append(
232
+ SketchAggregationFunction._group_to_series(
233
+ group,
234
+ timestamp_col,
235
+ dim_columns,
236
+ value_col,
237
+ ),
238
+ )
239
+ else:
240
+ calculated_metrics.append(
241
+ SketchAggregationFunction._group_to_series(
242
+ data,
243
+ timestamp_col,
244
+ dim_columns,
245
+ value_col,
246
+ ),
247
+ )
248
+
249
+ return calculated_metrics
250
+
251
+ @staticmethod
252
+ def _group_to_series(
253
+ group: pd.DataFrame,
254
+ timestamp_col: str,
255
+ dim_columns: list[str],
256
+ value_col: str,
257
+ ) -> SketchTimeSeries:
258
+ def to_sketch(col: pd.Series) -> Optional[kll_floats_sketch]:
259
+ if not len(col):
260
+ return None
261
+ s = kll_floats_sketch()
262
+ for v in col.values:
263
+ s.update(v)
264
+ return s
265
+
266
+ dimensions: list[Dimension] = []
267
+ if dim_columns:
268
+ # Get the first row of the group to determine the group level dimensions
269
+ dims_row = group.iloc[0]
270
+ for dim in dim_columns:
271
+ d = AggregationFunction.string_to_dimension(
272
+ name=dim, value=dims_row[dim]
273
+ )
274
+ dimensions.append(d)
275
+
276
+ values: list[SketchPoint] = []
277
+
278
+ # Group query results into 5min buckets
279
+ group[timestamp_col] = pd.to_datetime(group[timestamp_col])
280
+ group.set_index(timestamp_col, inplace=True)
281
+ # make sure dropna is False or rows with "null" as a dimension value will be dropped
282
+ time_bucketed_groups = group.groupby(pd.Grouper(freq="5min"), dropna=False)
283
+
284
+ for group_timestamp, time_bucket_group in time_bucketed_groups:
285
+ # Don't generate metrics on empty buckets
286
+ if time_bucket_group.empty:
287
+ continue
288
+ sketch = to_sketch(time_bucket_group[value_col])
289
+ if sketch is not None:
290
+ values.append(
291
+ SketchPoint(
292
+ timestamp=cast(pd.Timestamp, group_timestamp),
293
+ value=b64encode(sketch.serialize()).decode(),
294
+ ),
295
+ )
296
+
297
+ return SketchTimeSeries(values=values, dimensions=dimensions)
298
+
299
+ @staticmethod
300
+ def series_to_metric(
301
+ metric_name: str,
302
+ series: list[SketchTimeSeries],
303
+ ) -> SketchMetric:
304
+ return SketchMetric(name=metric_name, sketch_series=series)
@@ -0,0 +1,26 @@
1
+ | Class Name | UUID | Name |
2
+ |------------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------------------------------------|
3
+ | BinaryClassifierCountThresholdClassAggregationFunction | 00000000-0000-0000-0000-000000000020 | Binary Classification Count by Class - Probability Threshold |
4
+ | BinaryClassifierCountByClassAggregationFunction | 00000000-0000-0000-0000-00000000001f | Binary Classification Count by Class - Class Label |
5
+ | BinaryClassifierProbabilityThresholdConfusionMatrixAggregationFunction | 00000000-0000-0000-0000-00000000001e | Binary Classification Confusion Matrix - Probability Threshold |
6
+ | BinaryClassifierStringLabelConfusionMatrixAggregationFunction | 00000000-0000-0000-0000-00000000001d | Binary Classification Confusion Matrix - String Types |
7
+ | BinaryClassifierIntBoolConfusionMatrixAggregationFunction | 00000000-0000-0000-0000-00000000001c | Binary Classification Confusion Matrix - Int/Bool Types |
8
+ | NumericSumAggregationFunction | 00000000-0000-0000-0000-00000000000f | Numeric Sum |
9
+ | MeanAbsoluteErrorAggregationFunction | 00000000-0000-0000-0000-00000000000e | Mean Absolute Error |
10
+ | MeanSquaredErrorAggregationFunction | 00000000-0000-0000-0000-000000000010 | Mean Squared Error |
11
+ | NumericSketchAggregationFunction | 00000000-0000-0000-0000-00000000000d | Numeric Distribution |
12
+ | CategoricalCountAggregationFunction | 00000000-0000-0000-0000-00000000000c | Category Count |
13
+ | InferenceNullCountAggregationFunction | 00000000-0000-0000-0000-00000000000b | Null Value Count |
14
+ | InferenceCountAggregationFunction | 00000000-0000-0000-0000-00000000000a | Inference Count |
15
+ | ShieldInferenceRuleLatencyAggregation | 00000000-0000-0000-0000-000000000009 | Rule Latency Distribution |
16
+ | ShieldInferenceRuleClaimFailCountAggregation | 00000000-0000-0000-0000-000000000008 | Claim Count Distribution - Invalid Claims |
17
+ | ShieldInferenceRuleClaimPassCountAggregation | 00000000-0000-0000-0000-000000000007 | Claim Count Distribution - Valid Claims |
18
+ | ShieldInferenceRuleClaimCountAggregation | 00000000-0000-0000-0000-000000000006 | Claim Count Distribution |
19
+ | ShieldInferenceRulePIIDataScoreAggregation | 00000000-0000-0000-0000-000000000005 | PII Score Distribution |
20
+ | ShieldInferenceRuleToxicityScoreAggregation | 00000000-0000-0000-0000-000000000004 | Toxicity Distribution |
21
+ | ShieldInferenceHallucinationCountAggregation | 00000000-0000-0000-0000-000000000003 | Hallucination Count |
22
+ | ShieldInferenceRuleCountAggregation | 00000000-0000-0000-0000-000000000002 | Rule Result Count |
23
+ | ShieldInferencePassFailCountAggregation | 00000000-0000-0000-0000-000000000001 | Inference Count |
24
+ | ShieldInferenceTokenCountAggregation | 00000000-0000-0000-0000-000000000021 | Token Count |
25
+ | MulticlassClassifierCountByClassAggregationFunction | 64a338fb-6c99-4c40-ba39-81ab8baa8687 | Multiclass Classification Count by Class - Class Label |
26
+ | MulticlassClassifierStringLabelSingleClassConfusionMatrixAggregationFunction | dc728927-6928-4a3b-b174-8c1ec8b58d62 | Multiclass Classification Confusion Matrix Single Class - String Class Label Prediction |
@@ -0,0 +1,25 @@
1
+ import importlib.util
2
+ import inspect
3
+ import os
4
+
5
+ package_dir = os.path.dirname(__file__)
6
+
7
+ # Peter 05/08/2024: This is some code I swiped from stackoverflow that iterated through the package directory here looking at .py files
8
+ # It reads each file and imports the classes to add them to the "globals" which we can think of as importing into this namespace
9
+ # By doing that, everything is exported and ready to be read as members of this `functions` package.
10
+ # TLDR: this does what you would think `from . import *` does
11
+ # Benefit here is any file with any class is added to the "exports", so nothing needs to be done after dropping a file in here
12
+ for filename in os.listdir(package_dir):
13
+ if filename.endswith(".py") and filename != "__init__.py":
14
+ module_name = filename[:-3] # Remove the .py extension to get the module name
15
+ module_path = os.path.join(package_dir, filename)
16
+
17
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
18
+ if not spec:
19
+ continue
20
+ module = importlib.util.module_from_spec(spec)
21
+ if spec.loader:
22
+ spec.loader.exec_module(module)
23
+ for name, value in module.__dict__.items():
24
+ if inspect.isclass(value) and not name.startswith("_"):
25
+ globals()[name] = value