arthur-common 2.4.24__tar.gz
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.
- arthur_common-2.4.24/PKG-INFO +80 -0
- arthur_common-2.4.24/README.md +53 -0
- arthur_common-2.4.24/pyproject.toml +92 -0
- arthur_common-2.4.24/src/arthur_common/__init__.py +0 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/__init__.py +2 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/aggregator.py +304 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/README.md +26 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/__init__.py +25 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/agentic_aggregations.py +1645 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/categorical_count.py +139 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/confusion_matrix.py +521 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/inference_count.py +112 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/inference_count_by_class.py +286 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/inference_null_count.py +130 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/mean_absolute_error.py +164 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/mean_squared_error.py +164 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/multiclass_confusion_matrix.py +287 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/multiclass_inference_count_by_class.py +118 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/numeric_stats.py +135 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/numeric_sum.py +135 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/py.typed +0 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/functions/shield_aggregations.py +1116 -0
- arthur_common-2.4.24/src/arthur_common/aggregations/py.typed +0 -0
- arthur_common-2.4.24/src/arthur_common/config/__init__.py +0 -0
- arthur_common-2.4.24/src/arthur_common/config/config.py +42 -0
- arthur_common-2.4.24/src/arthur_common/config/settings.yaml +4 -0
- arthur_common-2.4.24/src/arthur_common/models/__init__.py +0 -0
- arthur_common-2.4.24/src/arthur_common/models/common_schemas.py +214 -0
- arthur_common-2.4.24/src/arthur_common/models/connectors.py +57 -0
- arthur_common-2.4.24/src/arthur_common/models/constants.py +24 -0
- arthur_common-2.4.24/src/arthur_common/models/datasets.py +14 -0
- arthur_common-2.4.24/src/arthur_common/models/enums.py +177 -0
- arthur_common-2.4.24/src/arthur_common/models/llm_model_providers.py +374 -0
- arthur_common-2.4.24/src/arthur_common/models/metric_schemas.py +63 -0
- arthur_common-2.4.24/src/arthur_common/models/metrics.py +297 -0
- arthur_common-2.4.24/src/arthur_common/models/py.typed +0 -0
- arthur_common-2.4.24/src/arthur_common/models/request_schemas.py +888 -0
- arthur_common-2.4.24/src/arthur_common/models/response_schemas.py +802 -0
- arthur_common-2.4.24/src/arthur_common/models/schema_definitions.py +648 -0
- arthur_common-2.4.24/src/arthur_common/models/task_eval_schemas.py +148 -0
- arthur_common-2.4.24/src/arthur_common/models/task_job_specs.py +94 -0
- arthur_common-2.4.24/src/arthur_common/py.typed +0 -0
- arthur_common-2.4.24/src/arthur_common/tools/__init__.py +0 -0
- arthur_common-2.4.24/src/arthur_common/tools/aggregation_analyzer.py +274 -0
- arthur_common-2.4.24/src/arthur_common/tools/aggregation_loader.py +59 -0
- arthur_common-2.4.24/src/arthur_common/tools/duckdb_data_loader.py +362 -0
- arthur_common-2.4.24/src/arthur_common/tools/duckdb_utils.py +32 -0
- arthur_common-2.4.24/src/arthur_common/tools/functions.py +46 -0
- arthur_common-2.4.24/src/arthur_common/tools/py.typed +0 -0
- arthur_common-2.4.24/src/arthur_common/tools/schema_inferer.py +122 -0
- arthur_common-2.4.24/src/arthur_common/tools/time_utils.py +33 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: arthur-common
|
|
3
|
+
Version: 2.4.24
|
|
4
|
+
Summary: Utility code common to Arthur platform components.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Arthur
|
|
7
|
+
Author-email: engineering@arthur.ai
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: datasketches (>=5.1.0)
|
|
14
|
+
Requires-Dist: duckdb (>=1.1.3)
|
|
15
|
+
Requires-Dist: fastapi (>=0.115.8)
|
|
16
|
+
Requires-Dist: fsspec (>=2024.10.0)
|
|
17
|
+
Requires-Dist: litellm (>=1.77.7,<2.0.0)
|
|
18
|
+
Requires-Dist: openinference-semantic-conventions (>=0.1.12,<0.2.0)
|
|
19
|
+
Requires-Dist: pandas (>=2.2.2,<3.0.0)
|
|
20
|
+
Requires-Dist: pydantic (>=2)
|
|
21
|
+
Requires-Dist: simple-settings (>=1.2.0)
|
|
22
|
+
Requires-Dist: types-python-dateutil (>=2.9.0)
|
|
23
|
+
Requires-Dist: types-requests (>=2.32.0.20241016)
|
|
24
|
+
Requires-Dist: typing-extensions (>=4.7.1)
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Arthur Common
|
|
28
|
+
|
|
29
|
+
Arthur Common is a library that contains common operations between Arthur platform services.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
To install the package, use [Poetry](https://python-poetry.org/):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
poetry add arthur-common
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
or pip
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install arthur-common
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Python 3.13
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
To set up the development environment, ensure you have [Poetry](https://python-poetry.org/) installed, then run:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
poetry env use 3.13
|
|
55
|
+
poetry install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Running Tests
|
|
59
|
+
|
|
60
|
+
This project uses [pytest](https://pytest.org/) for testing. To run the tests, execute:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
poetry run pytest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Release process
|
|
67
|
+
1. Merge changes into `main` branch
|
|
68
|
+
2. Go to **Actions** -> **Arthur Common Version Bump**
|
|
69
|
+
3. Click **Run workflow**. The workflow will create a new commit with the version bump, push it back to the same branch it is triggered on (default `main`), and start the release process
|
|
70
|
+
4. Watch in [GitHub Actions](https://github.com/arthur-ai/arthur-common/actions) for Arthur Common Release to run
|
|
71
|
+
5. Update package version in your project (arthur-engine)
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
This project is licensed under the MIT License.
|
|
76
|
+
|
|
77
|
+
## Authors
|
|
78
|
+
|
|
79
|
+
- Arthur <engineering@arthur.ai>
|
|
80
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Arthur Common
|
|
2
|
+
|
|
3
|
+
Arthur Common is a library that contains common operations between Arthur platform services.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To install the package, use [Poetry](https://python-poetry.org/):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
poetry add arthur-common
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or pip
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install arthur-common
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Python 3.13
|
|
22
|
+
|
|
23
|
+
## Development
|
|
24
|
+
|
|
25
|
+
To set up the development environment, ensure you have [Poetry](https://python-poetry.org/) installed, then run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
poetry env use 3.13
|
|
29
|
+
poetry install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Running Tests
|
|
33
|
+
|
|
34
|
+
This project uses [pytest](https://pytest.org/) for testing. To run the tests, execute:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
poetry run pytest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Release process
|
|
41
|
+
1. Merge changes into `main` branch
|
|
42
|
+
2. Go to **Actions** -> **Arthur Common Version Bump**
|
|
43
|
+
3. Click **Run workflow**. The workflow will create a new commit with the version bump, push it back to the same branch it is triggered on (default `main`), and start the release process
|
|
44
|
+
4. Watch in [GitHub Actions](https://github.com/arthur-ai/arthur-common/actions) for Arthur Common Release to run
|
|
45
|
+
5. Update package version in your project (arthur-engine)
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
This project is licensed under the MIT License.
|
|
50
|
+
|
|
51
|
+
## Authors
|
|
52
|
+
|
|
53
|
+
- Arthur <engineering@arthur.ai>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "arthur-common"
|
|
3
|
+
version = "2.4.24"
|
|
4
|
+
description = "Utility code common to Arthur platform components."
|
|
5
|
+
authors = ["Arthur <engineering@arthur.ai>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.12"
|
|
11
|
+
pydantic = ">=2"
|
|
12
|
+
typing-extensions = ">=4.7.1"
|
|
13
|
+
pandas = ">=2.2.2,<3.0.0"
|
|
14
|
+
duckdb = ">=1.1.3"
|
|
15
|
+
datasketches = ">=5.1.0"
|
|
16
|
+
types-requests = ">=2.32.0.20241016"
|
|
17
|
+
types-python-dateutil = ">=2.9.0"
|
|
18
|
+
fsspec = ">=2024.10.0"
|
|
19
|
+
litellm = "^1.77.7"
|
|
20
|
+
fastapi = ">=0.115.8"
|
|
21
|
+
simple-settings = ">=1.2.0"
|
|
22
|
+
openinference-semantic-conventions = "^0.1.12"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
[tool.poetry.group.dev.dependencies]
|
|
26
|
+
pytest = "^8.3.5"
|
|
27
|
+
responses = "0.25.7"
|
|
28
|
+
pytest-xdist = "3.6.1"
|
|
29
|
+
pytest-cov = "^6.1.1"
|
|
30
|
+
pre-commit = "^4.2.0"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[tool.poetry.group.linters.dependencies]
|
|
34
|
+
autoflake = "^2.3.1"
|
|
35
|
+
isort = "^6.0.1"
|
|
36
|
+
black = "^25.1.0"
|
|
37
|
+
mypy = "^1.17.0"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
[tool.autoflake]
|
|
41
|
+
remove-all-unused-imports = true
|
|
42
|
+
in-place = true
|
|
43
|
+
recursive = true
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
[tool.isort]
|
|
47
|
+
profile = "black"
|
|
48
|
+
src_paths = ["src"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[tool.black]
|
|
52
|
+
target-version = ['py312', 'py313']
|
|
53
|
+
include = '\.pyi?$'
|
|
54
|
+
extend-exclude = '''
|
|
55
|
+
/(
|
|
56
|
+
# directories
|
|
57
|
+
\.eggs
|
|
58
|
+
| \.git
|
|
59
|
+
06
|
|
60
|
+
| \.hg
|
|
61
|
+
| \.mypy_cache
|
|
62
|
+
| \.tox
|
|
63
|
+
| \.venv
|
|
64
|
+
| build
|
|
65
|
+
| dist
|
|
66
|
+
)/
|
|
67
|
+
'''
|
|
68
|
+
|
|
69
|
+
[tool.mypy]
|
|
70
|
+
ignore_missing_imports = true
|
|
71
|
+
implicit_reexport = true
|
|
72
|
+
explicit_package_bases = true
|
|
73
|
+
strict = true
|
|
74
|
+
exclude = ["clients/python", "alembic_app_db", "alembic_ts_db", "tests"]
|
|
75
|
+
namespace_packages = true
|
|
76
|
+
mypy_path = "src"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
[tool.pytest.ini_options]
|
|
80
|
+
pythonpath = ["src"]
|
|
81
|
+
testpaths = ["tests"]
|
|
82
|
+
|
|
83
|
+
[tool.setuptools.packages.find]
|
|
84
|
+
where = ["src"]
|
|
85
|
+
include = ["arthur_common*"]
|
|
86
|
+
|
|
87
|
+
[tool.setuptools.package-data]
|
|
88
|
+
"pkgname" = ["py.typed"]
|
|
89
|
+
|
|
90
|
+
[build-system]
|
|
91
|
+
requires = ["poetry-core"]
|
|
92
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -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
|
|
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=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
|