fiddler-client 3.1.0.dev1__py3-none-any.whl → 3.1.0.dev3__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.
- fiddler/VERSION +1 -1
- fiddler/__init__.py +2 -0
- fiddler/entities/alert_rule.py +5 -1
- fiddler/entities/events.py +51 -16
- fiddler/entities/model.py +29 -11
- fiddler/entities/webhook.py +18 -0
- fiddler/schemas/alert_rule.py +3 -2
- fiddler/schemas/custom_expression.py +7 -0
- fiddler/schemas/custom_features.py +30 -29
- fiddler/schemas/model.py +1 -0
- fiddler/schemas/model_spec.py +11 -9
- fiddler/tests/apis/test_alert_rule.py +44 -37
- fiddler/tests/apis/test_mixin.py +4 -1
- fiddler/tests/apis/test_model.py +7 -0
- fiddler/tests/apis/test_webhook.py +57 -0
- fiddler_client-3.1.0.dev3.dist-info/METADATA +46 -0
- {fiddler_client-3.1.0.dev1.dist-info → fiddler_client-3.1.0.dev3.dist-info}/RECORD +20 -20
- fiddler_client-3.1.0.dev1.dist-info/METADATA +0 -538
- {fiddler_client-3.1.0.dev1.dist-info → fiddler_client-3.1.0.dev3.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-3.1.0.dev1.dist-info → fiddler_client-3.1.0.dev3.dist-info}/WHEEL +0 -0
- {fiddler_client-3.1.0.dev1.dist-info → fiddler_client-3.1.0.dev3.dist-info}/top_level.txt +0 -0
fiddler/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.1.0.
|
|
1
|
+
3.1.0.dev3
|
fiddler/__init__.py
CHANGED
|
@@ -61,6 +61,7 @@ from fiddler.schemas.xai import ( # noqa
|
|
|
61
61
|
SqlSliceQueryDataSource,
|
|
62
62
|
)
|
|
63
63
|
from fiddler.schemas.xai_params import XaiParams # noqa
|
|
64
|
+
from fiddler.utils.helpers import group_by # noqa
|
|
64
65
|
from fiddler.utils.logger import set_logging # noqa
|
|
65
66
|
from fiddler.version import __version__ # noqa
|
|
66
67
|
|
|
@@ -141,4 +142,5 @@ __all__ = [
|
|
|
141
142
|
'ApiError',
|
|
142
143
|
# Utilities
|
|
143
144
|
'set_logging',
|
|
145
|
+
'group_by',
|
|
144
146
|
]
|
fiddler/entities/alert_rule.py
CHANGED
|
@@ -34,6 +34,7 @@ class AlertRule(
|
|
|
34
34
|
warning_threshold: float | None = None,
|
|
35
35
|
columns: list[str] | None = None,
|
|
36
36
|
baseline_id: UUID | str | None = None,
|
|
37
|
+
segment_id: UUID | str | None = None,
|
|
37
38
|
compare_bin_delta: int | None = None,
|
|
38
39
|
) -> None:
|
|
39
40
|
"""Construct a alert rule instance."""
|
|
@@ -49,6 +50,7 @@ class AlertRule(
|
|
|
49
50
|
self.critical_threshold = critical_threshold
|
|
50
51
|
self.condition = condition
|
|
51
52
|
self.bin_size = bin_size
|
|
53
|
+
self.segment_id = segment_id
|
|
52
54
|
|
|
53
55
|
self.id: UUID | None = None
|
|
54
56
|
self.project_id: UUID | None = None
|
|
@@ -84,7 +86,8 @@ class AlertRule(
|
|
|
84
86
|
critical_threshold=resp_obj.critical_threshold,
|
|
85
87
|
warning_threshold=resp_obj.warning_threshold,
|
|
86
88
|
columns=resp_obj.columns,
|
|
87
|
-
baseline_id=resp_obj.
|
|
89
|
+
baseline_id=resp_obj.baseline.id if resp_obj.baseline else None,
|
|
90
|
+
segment_id=resp_obj.segment.id if resp_obj.segment else None,
|
|
88
91
|
compare_bin_delta=resp_obj.compare_bin_delta,
|
|
89
92
|
)
|
|
90
93
|
|
|
@@ -210,6 +213,7 @@ class AlertRule(
|
|
|
210
213
|
'compare_to': self.compare_to,
|
|
211
214
|
'condition': self.condition,
|
|
212
215
|
'bin_size': self.bin_size,
|
|
216
|
+
'segment_id': self.segment_id,
|
|
213
217
|
'critical_threshold': self.critical_threshold,
|
|
214
218
|
'warning_threshold': self.warning_threshold,
|
|
215
219
|
'feature_names': self.columns,
|
fiddler/entities/events.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import tempfile
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, Callable
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
import pandas as pd
|
|
@@ -17,6 +17,9 @@ from fiddler.entities.job import Job
|
|
|
17
17
|
from fiddler.schemas.dataset import EnvType
|
|
18
18
|
from fiddler.schemas.events import EventsSource, FileSource
|
|
19
19
|
from fiddler.schemas.job import JobCompactResp
|
|
20
|
+
from fiddler.utils.logger import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class EventPublisher(ConnectionMixin):
|
|
@@ -51,40 +54,72 @@ class EventPublisher(ConnectionMixin):
|
|
|
51
54
|
|
|
52
55
|
:return: list[UUID] for list of dicts source and Job object for file path or dataframe source.
|
|
53
56
|
"""
|
|
57
|
+
|
|
58
|
+
publish_method = self._get_publish_method(source=source)
|
|
59
|
+
|
|
60
|
+
return publish_method(
|
|
61
|
+
source=source,
|
|
62
|
+
environment=environment,
|
|
63
|
+
dataset_name=dataset_name,
|
|
64
|
+
update=update,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _get_publish_method(
|
|
68
|
+
self, source: list[dict[str, Any]] | str | Path | pd.DataFrame
|
|
69
|
+
) -> Callable:
|
|
54
70
|
if isinstance(source, (str, Path)):
|
|
55
|
-
return self._publish_file
|
|
56
|
-
source=source,
|
|
57
|
-
environment=environment,
|
|
58
|
-
dataset_name=dataset_name,
|
|
59
|
-
update=update,
|
|
60
|
-
)
|
|
71
|
+
return self._publish_file
|
|
61
72
|
|
|
62
73
|
if isinstance(source, pd.DataFrame):
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
return self._publish_df
|
|
75
|
+
|
|
76
|
+
if isinstance(source, list):
|
|
77
|
+
return self._publish_stream
|
|
78
|
+
|
|
79
|
+
raise ValueError(f'Unsupported source - {type(source)}')
|
|
80
|
+
|
|
81
|
+
def _publish_df(
|
|
82
|
+
self,
|
|
83
|
+
source: pd.DataFrame,
|
|
84
|
+
environment: EnvType,
|
|
85
|
+
dataset_name: str | None = None,
|
|
86
|
+
update: bool = False,
|
|
87
|
+
) -> Job:
|
|
88
|
+
with tempfile.NamedTemporaryFile(suffix='.parquet') as temp_file:
|
|
89
|
+
try:
|
|
90
|
+
source.to_parquet(temp_file.name, index=False)
|
|
65
91
|
return self._publish_file(
|
|
66
92
|
source=temp_file.name,
|
|
67
93
|
environment=environment,
|
|
68
94
|
dataset_name=dataset_name,
|
|
69
95
|
update=update,
|
|
70
96
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
except Exception:
|
|
98
|
+
logger.warning(
|
|
99
|
+
'Failed to convert input dataframe to parquet format. Retrying as a CSV file.'
|
|
100
|
+
)
|
|
101
|
+
with tempfile.NamedTemporaryFile(suffix='.csv') as temp_file:
|
|
102
|
+
source.to_csv(temp_file.name, index=False)
|
|
103
|
+
return self._publish_file(
|
|
104
|
+
source=temp_file.name,
|
|
105
|
+
environment=environment,
|
|
106
|
+
dataset_name=dataset_name,
|
|
107
|
+
update=update,
|
|
108
|
+
)
|
|
76
109
|
|
|
77
110
|
def _publish_stream(
|
|
78
111
|
self,
|
|
79
112
|
source: list[dict[str, Any]],
|
|
113
|
+
environment: EnvType = EnvType.PRODUCTION,
|
|
114
|
+
dataset_name: str | None = None,
|
|
80
115
|
update: bool = False,
|
|
81
116
|
) -> list[UUID]:
|
|
82
117
|
event_ids = []
|
|
83
118
|
for i in tqdm(range(0, len(source), self.STREAM_LIMIT)):
|
|
84
119
|
response = self._publish_call(
|
|
85
120
|
source=EventsSource(events=source[i : i + self.STREAM_LIMIT]),
|
|
86
|
-
environment=
|
|
87
|
-
dataset_name=
|
|
121
|
+
environment=environment,
|
|
122
|
+
dataset_name=dataset_name,
|
|
88
123
|
update=update,
|
|
89
124
|
)
|
|
90
125
|
event_ids.extend(response.json()['data']['event_ids'])
|
fiddler/entities/model.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import builtins
|
|
4
|
-
import copy
|
|
5
4
|
import typing
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from datetime import datetime
|
|
@@ -33,6 +32,7 @@ from fiddler.schemas.model_spec import ModelSpec
|
|
|
33
32
|
from fiddler.schemas.model_task_params import ModelTaskParams
|
|
34
33
|
from fiddler.schemas.xai_params import XaiParams
|
|
35
34
|
from fiddler.utils.helpers import raise_not_found
|
|
35
|
+
from fiddler.utils.logger import get_logger
|
|
36
36
|
from fiddler.utils.model_generator import ModelGenerator
|
|
37
37
|
|
|
38
38
|
if typing.TYPE_CHECKING:
|
|
@@ -40,6 +40,9 @@ if typing.TYPE_CHECKING:
|
|
|
40
40
|
from fiddler.entities.dataset import Dataset
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
43
46
|
class Model(
|
|
44
47
|
BaseEntity,
|
|
45
48
|
CreatedByMixin,
|
|
@@ -307,7 +310,9 @@ class Model(
|
|
|
307
310
|
params = {'filter': _filter.json()}
|
|
308
311
|
|
|
309
312
|
for model in cls._paginate(url=cls._get_url(), params=params):
|
|
310
|
-
yield ModelCompact(
|
|
313
|
+
yield ModelCompact(
|
|
314
|
+
id=model['id'], name=model['name'], version=model['version']
|
|
315
|
+
)
|
|
311
316
|
|
|
312
317
|
def duplicate(self, version: str | None = None) -> Model:
|
|
313
318
|
"""
|
|
@@ -319,13 +324,21 @@ class Model(
|
|
|
319
324
|
:param version: Version name for the new instance
|
|
320
325
|
:return: Model instance
|
|
321
326
|
"""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
return Model(
|
|
328
|
+
name=self.name,
|
|
329
|
+
project_id=self.project_id,
|
|
330
|
+
schema=self.schema,
|
|
331
|
+
spec=self.spec,
|
|
332
|
+
version=version if version else self.version,
|
|
333
|
+
input_type=self.input_type,
|
|
334
|
+
task=self.task,
|
|
335
|
+
task_params=self.task_params,
|
|
336
|
+
description=self.description,
|
|
337
|
+
event_id_col=self.event_id_col,
|
|
338
|
+
event_ts_col=self.event_ts_col,
|
|
339
|
+
event_ts_format=self.event_ts_format,
|
|
340
|
+
xai_params=self.xai_params,
|
|
341
|
+
)
|
|
329
342
|
|
|
330
343
|
@property
|
|
331
344
|
def datasets(self) -> Iterator[Dataset]:
|
|
@@ -456,7 +469,7 @@ class Model(
|
|
|
456
469
|
:return: list[UUID] for list of dicts or dataframe source and Job object for
|
|
457
470
|
file path source.
|
|
458
471
|
"""
|
|
459
|
-
|
|
472
|
+
logger.info('Model[%s/%s] - Publishing events', self.name, self.version)
|
|
460
473
|
return self._event_publisher.publish(
|
|
461
474
|
source=source,
|
|
462
475
|
environment=environment,
|
|
@@ -548,6 +561,7 @@ class Model(
|
|
|
548
561
|
class ModelCompact:
|
|
549
562
|
id: UUID
|
|
550
563
|
name: str
|
|
564
|
+
version: str | None = None
|
|
551
565
|
|
|
552
566
|
def fetch(self) -> Model:
|
|
553
567
|
"""Fetch model instance"""
|
|
@@ -565,4 +579,8 @@ class ModelCompactMixin:
|
|
|
565
579
|
'response.'
|
|
566
580
|
)
|
|
567
581
|
|
|
568
|
-
return ModelCompact(
|
|
582
|
+
return ModelCompact(
|
|
583
|
+
id=response.model.id,
|
|
584
|
+
name=response.model.name,
|
|
585
|
+
version=response.model.version,
|
|
586
|
+
)
|
fiddler/entities/webhook.py
CHANGED
|
@@ -6,7 +6,9 @@ from uuid import UUID
|
|
|
6
6
|
|
|
7
7
|
from fiddler.decorators import handle_api_error
|
|
8
8
|
from fiddler.entities.base import BaseEntity
|
|
9
|
+
from fiddler.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
9
10
|
from fiddler.schemas.webhook import WebhookProvider, WebhookResp
|
|
11
|
+
from fiddler.utils.helpers import raise_not_found
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Webhook(BaseEntity):
|
|
@@ -81,6 +83,22 @@ class Webhook(BaseEntity):
|
|
|
81
83
|
response = cls._client().get(url=cls._get_url(id_))
|
|
82
84
|
return cls._from_response(response=response)
|
|
83
85
|
|
|
86
|
+
@classmethod
|
|
87
|
+
@handle_api_error
|
|
88
|
+
def from_name(cls, name: str) -> Webhook:
|
|
89
|
+
"""Get the webhook instance using webhook name"""
|
|
90
|
+
_filter = QueryCondition(
|
|
91
|
+
rules=[QueryRule(field='name', operator=OperatorType.EQUAL, value=name)]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
response = cls._client().get(
|
|
95
|
+
url=cls._get_url(), params={'filter': _filter.json()}
|
|
96
|
+
)
|
|
97
|
+
if response.json()['data']['total'] == 0:
|
|
98
|
+
raise_not_found('Webhook not found for the given identifier')
|
|
99
|
+
|
|
100
|
+
return cls._from_dict(data=response.json()['data']['items'][0])
|
|
101
|
+
|
|
84
102
|
@classmethod
|
|
85
103
|
@handle_api_error
|
|
86
104
|
def list(cls) -> Iterator[Webhook]:
|
fiddler/schemas/alert_rule.py
CHANGED
|
@@ -7,6 +7,7 @@ from pydantic import Field
|
|
|
7
7
|
from fiddler.constants.alert_rule import AlertCondition, BinSize, CompareTo, Priority
|
|
8
8
|
from fiddler.schemas.base import BaseModel
|
|
9
9
|
from fiddler.schemas.baseline import BaselineCompactResp
|
|
10
|
+
from fiddler.schemas.custom_expression import SegmentCompactResp
|
|
10
11
|
from fiddler.schemas.model import ModelCompactResp
|
|
11
12
|
from fiddler.schemas.project import ProjectCompactResp
|
|
12
13
|
|
|
@@ -17,14 +18,14 @@ class AlertRuleResp(BaseModel):
|
|
|
17
18
|
model: ModelCompactResp
|
|
18
19
|
project: ProjectCompactResp
|
|
19
20
|
baseline: Optional[BaselineCompactResp]
|
|
21
|
+
segment: Optional[SegmentCompactResp]
|
|
20
22
|
priority: Union[str, Priority]
|
|
21
23
|
compare_to: Union[str, CompareTo]
|
|
22
24
|
metric_id: Union[str, UUID]
|
|
23
25
|
critical_threshold: float
|
|
24
26
|
condition: Union[str, AlertCondition]
|
|
25
27
|
bin_size: Union[str, BinSize]
|
|
26
|
-
columns: Optional[List[str]]
|
|
27
|
-
baseline_id: Optional[UUID]
|
|
28
|
+
columns: Optional[List[str]] = Field(alias='feature_names')
|
|
28
29
|
compare_bin_delta: Optional[int]
|
|
29
30
|
warning_threshold: Optional[float]
|
|
30
31
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
1
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, validator
|
|
4
4
|
|
|
@@ -10,18 +10,12 @@ CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
|
|
|
10
10
|
|
|
11
11
|
class CustomFeature(BaseModel):
|
|
12
12
|
name: str
|
|
13
|
-
type:
|
|
14
|
-
n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
|
|
15
|
-
centroids: Optional[List] = None
|
|
16
|
-
|
|
17
|
-
@validator('n_clusters')
|
|
18
|
-
def validate_n_clusters(cls, value: int) -> int: # noqa: N805
|
|
19
|
-
if value < 0:
|
|
20
|
-
raise ValueError('n_clusters must be greater than 0')
|
|
21
|
-
return value
|
|
13
|
+
type: Any
|
|
22
14
|
|
|
23
15
|
class Config:
|
|
24
16
|
allow_mutation = False
|
|
17
|
+
use_enum_values = True
|
|
18
|
+
discriminator = 'type'
|
|
25
19
|
|
|
26
20
|
@classmethod
|
|
27
21
|
def from_columns(
|
|
@@ -53,12 +47,13 @@ class CustomFeature(BaseModel):
|
|
|
53
47
|
return_dict: Dict[str, Any] = {
|
|
54
48
|
'name': self.name,
|
|
55
49
|
'type': self.type.value,
|
|
56
|
-
'n_clusters': self.n_clusters,
|
|
57
50
|
}
|
|
58
51
|
if isinstance(self, Multivariate):
|
|
59
52
|
return_dict['columns'] = self.columns
|
|
53
|
+
return_dict['n_clusters'] = self.n_clusters
|
|
60
54
|
elif isinstance(self, VectorFeature):
|
|
61
55
|
return_dict['column'] = self.column
|
|
56
|
+
return_dict['n_clusters'] = self.n_clusters
|
|
62
57
|
if isinstance(self, (ImageEmbedding, TextEmbedding)):
|
|
63
58
|
return_dict['source_column'] = self.source_column
|
|
64
59
|
if isinstance(self, TextEmbedding):
|
|
@@ -74,7 +69,9 @@ class CustomFeature(BaseModel):
|
|
|
74
69
|
|
|
75
70
|
|
|
76
71
|
class Multivariate(CustomFeature):
|
|
77
|
-
type:
|
|
72
|
+
type: Literal['FROM_COLUMNS'] = CustomFeatureType.FROM_COLUMNS.value
|
|
73
|
+
n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
|
|
74
|
+
centroids: Optional[List] = None
|
|
78
75
|
columns: List[str]
|
|
79
76
|
monitor_components: bool = False
|
|
80
77
|
|
|
@@ -84,22 +81,31 @@ class Multivariate(CustomFeature):
|
|
|
84
81
|
raise ValueError('Multivariate columns must be greater than 1')
|
|
85
82
|
return value
|
|
86
83
|
|
|
84
|
+
@validator('n_clusters')
|
|
85
|
+
def validate_n_clusters(cls, value: int) -> int: # noqa: N805
|
|
86
|
+
if value < 0:
|
|
87
|
+
raise ValueError('n_clusters must be greater than 0')
|
|
88
|
+
return value
|
|
89
|
+
|
|
87
90
|
|
|
88
91
|
class VectorFeature(CustomFeature):
|
|
89
|
-
type:
|
|
90
|
-
|
|
92
|
+
type: Literal['FROM_VECTOR'] = CustomFeatureType.FROM_VECTOR.value
|
|
93
|
+
n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
|
|
94
|
+
centroids: Optional[List] = None
|
|
91
95
|
column: str
|
|
92
96
|
|
|
97
|
+
@validator('n_clusters')
|
|
98
|
+
def validate_n_clusters(cls, value: int) -> int: # noqa: N805
|
|
99
|
+
if value < 0:
|
|
100
|
+
raise ValueError('n_clusters must be greater than 0')
|
|
101
|
+
return value
|
|
102
|
+
|
|
93
103
|
|
|
94
104
|
class TextEmbedding(VectorFeature):
|
|
95
|
-
type:
|
|
105
|
+
type: Literal['FROM_TEXT_EMBEDDING'] = CustomFeatureType.FROM_TEXT_EMBEDDING.value # type: ignore
|
|
106
|
+
source_column: str
|
|
96
107
|
n_tags: Optional[int] = DEFAULT_NUM_TAGS
|
|
97
|
-
|
|
98
|
-
@validator('source_column')
|
|
99
|
-
def validate_source_column(cls, value: str) -> str: # noqa: N805
|
|
100
|
-
if value is None:
|
|
101
|
-
raise ValueError('source_column must be specified')
|
|
102
|
-
return value
|
|
108
|
+
tf_idf: Optional[Dict[str, List]] = None
|
|
103
109
|
|
|
104
110
|
@validator('n_tags')
|
|
105
111
|
def validate_n_tags(cls, value: int) -> int: # noqa: N805
|
|
@@ -109,13 +115,8 @@ class TextEmbedding(VectorFeature):
|
|
|
109
115
|
|
|
110
116
|
|
|
111
117
|
class ImageEmbedding(VectorFeature):
|
|
112
|
-
type:
|
|
113
|
-
|
|
114
|
-
@validator('source_column')
|
|
115
|
-
def validate_source_column(cls, value: str) -> str: # noqa: N805
|
|
116
|
-
if value is None:
|
|
117
|
-
raise ValueError('source_column must be specified')
|
|
118
|
-
return value
|
|
118
|
+
type: Literal['FROM_IMAGE_EMBEDDING'] = CustomFeatureType.FROM_IMAGE_EMBEDDING.value # type: ignore
|
|
119
|
+
source_column: str
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
class Enrichment(CustomFeature):
|
|
@@ -134,7 +135,7 @@ class Enrichment(CustomFeature):
|
|
|
134
135
|
"""
|
|
135
136
|
|
|
136
137
|
# Setting the feature type to ENRICHMENT
|
|
137
|
-
type:
|
|
138
|
+
type: Literal['ENRICHMENT'] = CustomFeatureType.ENRICHMENT.value
|
|
138
139
|
|
|
139
140
|
# List of input column names used to generate the enrichment
|
|
140
141
|
columns: List[str]
|
fiddler/schemas/model.py
CHANGED
fiddler/schemas/model_spec.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, Union
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from fiddler.schemas.custom_features import
|
|
5
|
+
from fiddler.schemas.custom_features import Multivariate, VectorFeature, TextEmbedding, ImageEmbedding, Enrichment
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ModelSpec(BaseModel):
|
|
@@ -11,20 +11,22 @@ class ModelSpec(BaseModel):
|
|
|
11
11
|
schema_version: int = 1
|
|
12
12
|
"""Schema version"""
|
|
13
13
|
|
|
14
|
-
inputs: List[str] =
|
|
14
|
+
inputs: List[str] = Field(default_factory=list)
|
|
15
15
|
"""Feature columns"""
|
|
16
16
|
|
|
17
|
-
outputs: List[str] =
|
|
17
|
+
outputs: List[str] = Field(default_factory=list)
|
|
18
18
|
"""Prediction columns"""
|
|
19
19
|
|
|
20
|
-
targets: List[str] =
|
|
20
|
+
targets: List[str] = Field(default_factory=list)
|
|
21
21
|
"""Label columns"""
|
|
22
22
|
|
|
23
|
-
decisions: List[str] =
|
|
23
|
+
decisions: List[str] = Field(default_factory=list)
|
|
24
24
|
"""Decisions columns"""
|
|
25
25
|
|
|
26
|
-
metadata: List[str] =
|
|
26
|
+
metadata: List[str] = Field(default_factory=list)
|
|
27
27
|
"""Metadata columns"""
|
|
28
28
|
|
|
29
|
-
custom_features: List[
|
|
29
|
+
custom_features: List[Union[Multivariate, VectorFeature, TextEmbedding, ImageEmbedding, Enrichment]] = Field(
|
|
30
|
+
default_factory=list
|
|
31
|
+
)
|
|
30
32
|
"""Custom feature definitions"""
|
|
@@ -28,54 +28,61 @@ from fiddler.utils.logger import set_logging
|
|
|
28
28
|
|
|
29
29
|
API_RESPONSE_200 = {
|
|
30
30
|
'data': {
|
|
31
|
-
'
|
|
32
|
-
'id': 620,
|
|
33
|
-
'created_at': '2023-11-28T08:04:12.651937+00:00',
|
|
31
|
+
'id': 794,
|
|
34
32
|
'organization_name': 'mainbuild',
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
33
|
+
'project_name': PROJECT_NAME,
|
|
34
|
+
'model_id': 285,
|
|
35
|
+
'name': 'test_rule',
|
|
36
|
+
'metric': 'jsd',
|
|
37
|
+
'metric_id': 'jsd',
|
|
38
|
+
'metric_display_name': 'Jensen-Shannon Distance',
|
|
39
|
+
'alert_type': 'drift',
|
|
40
|
+
'alert_type_display_name': 'Data Drift',
|
|
41
|
+
'priority': 'MEDIUM',
|
|
43
42
|
'baseline_name': BASELINE_NAME,
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
'feature_names': ['creditscore'],
|
|
44
|
+
'time_bucket': 3600000,
|
|
45
|
+
'category': None,
|
|
46
|
+
'bin_size': 'Hour',
|
|
47
|
+
'compare_to': 'raw_value',
|
|
48
48
|
'condition': 'greater',
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'name': 'Age Missing 1 Hr',
|
|
53
|
-
'model_name': MODEL_NAME,
|
|
54
|
-
'model': {
|
|
55
|
-
'id': MODEL_ID,
|
|
56
|
-
'name': MODEL_NAME,
|
|
57
|
-
},
|
|
49
|
+
'compare_period': None,
|
|
50
|
+
'warning_threshold': 1.0,
|
|
51
|
+
'critical_threshold': 2.3,
|
|
58
52
|
'is_active': True,
|
|
59
53
|
'created_by': 'admin@fiddler.ai',
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
|
|
65
|
-
'
|
|
54
|
+
'created_at': '2024-04-23T10:34:14.321895+00:00',
|
|
55
|
+
'last_updated': '2024-04-23T10:34:14.323554+00:00',
|
|
56
|
+
'model_name': 'bank_churn',
|
|
57
|
+
'enable_notification': True,
|
|
58
|
+
'segment': {
|
|
59
|
+
'id': '1c9b551a-808b-4ef0-b67b-e788eaf1e6de',
|
|
60
|
+
'organization_name': 'mainbuild',
|
|
61
|
+
'project_name': 'nick_test_project_11',
|
|
62
|
+
'model_name': 'bank_churn',
|
|
63
|
+
'name': 'xyzab',
|
|
64
|
+
'definition': 'Age > 10',
|
|
65
|
+
'description': 'meh',
|
|
66
|
+
'created_at': '2024-04-23T10:31:52.181550+00:00',
|
|
67
|
+
'created_by': {
|
|
68
|
+
'id': 1,
|
|
69
|
+
'full_name': 'Fiddler Administrator',
|
|
70
|
+
'email': 'admin@fiddler.ai',
|
|
71
|
+
},
|
|
66
72
|
},
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
73
|
+
'project': {'id': PROJECT_ID, 'name': PROJECT_NAME},
|
|
74
|
+
'model': {'id': MODEL_ID, 'name': MODEL_NAME, 'version': 'v1'},
|
|
75
|
+
'baseline': {'id': BASELINE_ID, 'name': BASELINE_NAME},
|
|
76
|
+
'compare_bin_delta': None,
|
|
77
|
+
'uuid': ALERT_RULE_ID,
|
|
71
78
|
'notifications': {
|
|
72
|
-
'emails': {'alert_config_uuid': ALERT_RULE_ID, 'email': 'admin@fiddler.ai'},
|
|
73
79
|
'pagerduty': {
|
|
74
|
-
'severity': '',
|
|
75
80
|
'alert_config_uuid': ALERT_RULE_ID,
|
|
76
81
|
'service': '',
|
|
82
|
+
'severity': '',
|
|
77
83
|
},
|
|
78
|
-
'
|
|
84
|
+
'emails': {'email': ''},
|
|
85
|
+
'webhooks': [],
|
|
79
86
|
},
|
|
80
87
|
},
|
|
81
88
|
'api_version': '2.0',
|
fiddler/tests/apis/test_mixin.py
CHANGED
|
@@ -20,6 +20,7 @@ from fiddler.tests.constants import (
|
|
|
20
20
|
DATASET_NAME,
|
|
21
21
|
MODEL_ID,
|
|
22
22
|
MODEL_NAME,
|
|
23
|
+
MODEL_VERSION,
|
|
23
24
|
PROJECT_ID,
|
|
24
25
|
PROJECT_NAME,
|
|
25
26
|
USER_EMAIL,
|
|
@@ -39,7 +40,9 @@ class DummyEntity(
|
|
|
39
40
|
def __init__(self) -> None:
|
|
40
41
|
self._resp = mock.MagicMock()
|
|
41
42
|
self._resp.project = ProjectCompactResp(id=PROJECT_ID, name=PROJECT_NAME)
|
|
42
|
-
self._resp.model = ModelCompactResp(
|
|
43
|
+
self._resp.model = ModelCompactResp(
|
|
44
|
+
id=MODEL_ID, name=MODEL_NAME, version=MODEL_VERSION
|
|
45
|
+
)
|
|
43
46
|
self._resp.dataset = DatasetCompactResp(
|
|
44
47
|
id=DATASET_ID, name=DATASET_NAME, type=EnvType.PRE_PRODUCTION
|
|
45
48
|
)
|
fiddler/tests/apis/test_model.py
CHANGED
|
@@ -471,6 +471,7 @@ def test_model_list_success() -> None:
|
|
|
471
471
|
models = Model.list(project_id=PROJECT_ID)
|
|
472
472
|
for model in models:
|
|
473
473
|
assert isinstance(model, ModelCompact)
|
|
474
|
+
assert model.version is not None
|
|
474
475
|
|
|
475
476
|
|
|
476
477
|
@responses.activate
|
|
@@ -654,12 +655,18 @@ def test_model_duplicate() -> None:
|
|
|
654
655
|
model = Model.get(id_=MODEL_ID)
|
|
655
656
|
|
|
656
657
|
assert model.id == UUID(MODEL_ID)
|
|
658
|
+
assert model._event_publisher.model_id == model.id
|
|
657
659
|
|
|
658
660
|
# Without version parameter
|
|
659
661
|
model_copy = model.duplicate()
|
|
662
|
+
|
|
660
663
|
assert model_copy.id is None
|
|
661
664
|
assert model_copy.version == model.version
|
|
662
665
|
|
|
666
|
+
with pytest.raises(AssertionError):
|
|
667
|
+
# since id is None
|
|
668
|
+
_ = model_copy._event_publisher
|
|
669
|
+
|
|
663
670
|
# With version parameter
|
|
664
671
|
new_version = 'v2'
|
|
665
672
|
model_copy = model.duplicate(version=new_version)
|