mostlyai-engine 1.0.0__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.
- mostlyai/engine/__init__.py +27 -0
- mostlyai/engine/_common.py +612 -0
- mostlyai/engine/_dtypes.py +58 -0
- mostlyai/engine/_encoding_types/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/text.py +41 -0
- mostlyai/engine/_encoding_types/tabular/__init__.py +13 -0
- mostlyai/engine/_encoding_types/tabular/categorical.py +115 -0
- mostlyai/engine/_encoding_types/tabular/character.py +155 -0
- mostlyai/engine/_encoding_types/tabular/datetime.py +254 -0
- mostlyai/engine/_encoding_types/tabular/itt.py +352 -0
- mostlyai/engine/_encoding_types/tabular/lat_long.py +353 -0
- mostlyai/engine/_encoding_types/tabular/numeric.py +548 -0
- mostlyai/engine/_language/__init__.py +17 -0
- mostlyai/engine/_language/common.py +110 -0
- mostlyai/engine/_language/encoding.py +162 -0
- mostlyai/engine/_language/engine/__init__.py +13 -0
- mostlyai/engine/_language/engine/base.py +51 -0
- mostlyai/engine/_language/engine/hf_engine.py +136 -0
- mostlyai/engine/_language/engine/vllm_engine.py +187 -0
- mostlyai/engine/_language/formatron_utils.py +114 -0
- mostlyai/engine/_language/generation.py +309 -0
- mostlyai/engine/_language/lstm.py +139 -0
- mostlyai/engine/_language/tokenizer_utils.py +144 -0
- mostlyai/engine/_language/training.py +718 -0
- mostlyai/engine/_memory.py +69 -0
- mostlyai/engine/_tabular/__init__.py +13 -0
- mostlyai/engine/_tabular/argn.py +1382 -0
- mostlyai/engine/_tabular/common.py +29 -0
- mostlyai/engine/_tabular/encoding.py +406 -0
- mostlyai/engine/_tabular/fairness.py +245 -0
- mostlyai/engine/_tabular/generation.py +1157 -0
- mostlyai/engine/_tabular/training.py +856 -0
- mostlyai/engine/_training_utils.py +159 -0
- mostlyai/engine/_workspace.py +225 -0
- mostlyai/engine/analysis.py +571 -0
- mostlyai/engine/domain.py +171 -0
- mostlyai/engine/encoding.py +42 -0
- mostlyai/engine/generation.py +110 -0
- mostlyai/engine/splitting.py +257 -0
- mostlyai/engine/training.py +104 -0
- mostlyai_engine-1.0.0.dist-info/METADATA +179 -0
- mostlyai_engine-1.0.0.dist-info/RECORD +45 -0
- mostlyai_engine-1.0.0.dist-info/WHEEL +4 -0
- mostlyai_engine-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Copyright 2025 MOSTLY AI
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import abc
|
|
16
|
+
import logging
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from opacus.accountants import IAccountant
|
|
20
|
+
import pandas as pd
|
|
21
|
+
import torch
|
|
22
|
+
from pydantic import BaseModel, Field, field_validator
|
|
23
|
+
|
|
24
|
+
from mostlyai.engine._workspace import Workspace
|
|
25
|
+
|
|
26
|
+
_LOG = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProgressMessage(BaseModel, extra="allow"):
|
|
30
|
+
epoch: float | None = Field(None, description="Current epoch number")
|
|
31
|
+
is_checkpoint: bool | int | None = Field(0, description="Whether this progress is a checkpoint")
|
|
32
|
+
steps: int | None = Field(None, description="Number of processed steps")
|
|
33
|
+
samples: int | None = Field(None, description="Number of processed samples")
|
|
34
|
+
trn_loss: float | None = Field(None, description="Training loss")
|
|
35
|
+
val_loss: float | None = Field(None, description="Validation loss")
|
|
36
|
+
total_time: float | None = Field(None, description="Elapsed total time (s)")
|
|
37
|
+
learn_rate: float | None = Field(None, description="Learning rate")
|
|
38
|
+
dp_eps: float | None = Field(None, description="Differential privacy epsilon")
|
|
39
|
+
dp_delta: float | None = Field(None, description="Differential privacy delta")
|
|
40
|
+
|
|
41
|
+
@field_validator("epoch", "trn_loss", "val_loss", "learn_rate", "total_time", "dp_eps", "dp_delta")
|
|
42
|
+
@classmethod
|
|
43
|
+
def round_float(cls, v, info) -> float:
|
|
44
|
+
field_decimal_places = {
|
|
45
|
+
"epoch": 2,
|
|
46
|
+
"trn_loss": 4,
|
|
47
|
+
"val_loss": 4,
|
|
48
|
+
"learn_rate": 6,
|
|
49
|
+
"total_time": 1,
|
|
50
|
+
"dp_eps": 2,
|
|
51
|
+
"dp_delta": 8,
|
|
52
|
+
}
|
|
53
|
+
if isinstance(v, float) and info.field_name in field_decimal_places:
|
|
54
|
+
return round(v, field_decimal_places[info.field_name])
|
|
55
|
+
return v
|
|
56
|
+
|
|
57
|
+
@field_validator("is_checkpoint")
|
|
58
|
+
@classmethod
|
|
59
|
+
def cast_to_int(cls, v) -> int:
|
|
60
|
+
return int(v)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class EarlyStopper:
|
|
64
|
+
"""
|
|
65
|
+
Stop training when val_loss stopped improving for a while
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, val_loss_patience: int) -> None:
|
|
69
|
+
self.val_loss_patience = val_loss_patience
|
|
70
|
+
self.best_loss = float("inf")
|
|
71
|
+
self.val_loss_cnt = 0
|
|
72
|
+
|
|
73
|
+
def __call__(self, val_loss: float) -> bool:
|
|
74
|
+
do_stop = False
|
|
75
|
+
# check val_loss
|
|
76
|
+
if not pd.isna(val_loss) and val_loss < self.best_loss:
|
|
77
|
+
# remember best val_loss
|
|
78
|
+
self.best_loss = val_loss
|
|
79
|
+
# reset counter
|
|
80
|
+
self.val_loss_cnt = 0
|
|
81
|
+
else:
|
|
82
|
+
self.val_loss_cnt += 1
|
|
83
|
+
if self.val_loss_cnt > self.val_loss_patience:
|
|
84
|
+
_LOG.info("early stopping: val_loss stopped improving")
|
|
85
|
+
do_stop = True
|
|
86
|
+
return do_stop
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ModelCheckpoint(abc.ABC):
|
|
90
|
+
"""
|
|
91
|
+
Save model weights for best model.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, workspace: Workspace, initial_best_val_loss: float = float("inf")) -> None:
|
|
95
|
+
self.workspace = workspace
|
|
96
|
+
self.best_val_loss = initial_best_val_loss
|
|
97
|
+
self.last_save_time = time.time()
|
|
98
|
+
self.save_count = 0
|
|
99
|
+
|
|
100
|
+
def optimizer_and_lr_scheduler_paths_exist(self) -> bool:
|
|
101
|
+
return self.workspace.model_optimizer_path.exists() and self.workspace.model_lr_scheduler_path.exists()
|
|
102
|
+
|
|
103
|
+
@abc.abstractmethod
|
|
104
|
+
def model_weights_path_exists(self) -> None:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def clear_checkpoint(self):
|
|
108
|
+
self.workspace.model_optimizer_path.unlink(missing_ok=True)
|
|
109
|
+
self.workspace.model_lr_scheduler_path.unlink(missing_ok=True)
|
|
110
|
+
self._clear_model_weights()
|
|
111
|
+
|
|
112
|
+
def save_checkpoint_if_best(
|
|
113
|
+
self,
|
|
114
|
+
val_loss: float,
|
|
115
|
+
model: torch.nn.Module,
|
|
116
|
+
optimizer: torch.optim.Optimizer | None = None,
|
|
117
|
+
lr_scheduler: torch.optim.lr_scheduler.LRScheduler | None = None,
|
|
118
|
+
dp_accountant: IAccountant | None = None,
|
|
119
|
+
) -> bool:
|
|
120
|
+
# save model weights if validation loss has improved
|
|
121
|
+
if val_loss < self.best_val_loss:
|
|
122
|
+
self.best_val_loss = val_loss
|
|
123
|
+
self.save_checkpoint(model, optimizer, lr_scheduler, dp_accountant)
|
|
124
|
+
return True
|
|
125
|
+
else:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def save_checkpoint(
|
|
129
|
+
self,
|
|
130
|
+
model: torch.nn.Module,
|
|
131
|
+
optimizer: torch.optim.Optimizer | None = None,
|
|
132
|
+
lr_scheduler: torch.optim.lr_scheduler.LRScheduler | None = None,
|
|
133
|
+
dp_accountant: IAccountant | None = None,
|
|
134
|
+
) -> None:
|
|
135
|
+
if optimizer is not None and lr_scheduler is not None:
|
|
136
|
+
torch.save(optimizer.state_dict(), self.workspace.model_optimizer_path)
|
|
137
|
+
torch.save(lr_scheduler.state_dict(), self.workspace.model_lr_scheduler_path)
|
|
138
|
+
if dp_accountant is not None:
|
|
139
|
+
torch.save(dp_accountant.state_dict(), self.workspace.model_dp_accountant_path)
|
|
140
|
+
self._save_model_weights(model)
|
|
141
|
+
self.last_save_time = time.time()
|
|
142
|
+
self.save_count += 1
|
|
143
|
+
|
|
144
|
+
def has_saved_once(self) -> bool:
|
|
145
|
+
return self.save_count > 0
|
|
146
|
+
|
|
147
|
+
@abc.abstractmethod
|
|
148
|
+
def _save_model_weights(self, model: torch.nn.Module) -> None:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
@abc.abstractmethod
|
|
152
|
+
def _clear_model_weights(self) -> None:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def check_early_training_exit(workspace: Workspace, trn_cnt: int, val_cnt: int) -> bool:
|
|
157
|
+
trn_files = workspace.encoded_data_trn.fetch_all()
|
|
158
|
+
val_files = workspace.encoded_data_val.fetch_all()
|
|
159
|
+
return any((len(trn_files) == 0, len(val_files) == 0, trn_cnt == 0, val_cnt == 0))
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Copyright 2025 MOSTLY AI
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
|
|
22
|
+
from mostlyai.engine._common import read_json, write_json
|
|
23
|
+
from mostlyai.engine.domain import ModelType
|
|
24
|
+
|
|
25
|
+
_LOG = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class PathDesc:
|
|
30
|
+
root_dir: Path | None = None
|
|
31
|
+
parts: list[str] | None = None
|
|
32
|
+
is_multiple: bool = False
|
|
33
|
+
fetch_multiple: Callable | None = None
|
|
34
|
+
read_handler: Callable | None = None
|
|
35
|
+
write_handler: Callable | None = None
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
all_parts = (list(self.root_dir.parts) if isinstance(self.root_dir, Path) else []) + (
|
|
39
|
+
self.parts if isinstance(self.parts, list) else []
|
|
40
|
+
)
|
|
41
|
+
self.path = Path(*all_parts)
|
|
42
|
+
|
|
43
|
+
def read(self):
|
|
44
|
+
if not self.read_handler:
|
|
45
|
+
raise RuntimeError(f"No read handler was defined for {self.path}")
|
|
46
|
+
return self.read_handler(self.path)
|
|
47
|
+
|
|
48
|
+
def write(self, data: Any):
|
|
49
|
+
if not self.write_handler:
|
|
50
|
+
raise RuntimeError(f"No write handler was defined for {self.path}")
|
|
51
|
+
return self.write_handler(data, self.path)
|
|
52
|
+
|
|
53
|
+
def fetch_all(self):
|
|
54
|
+
if not self.fetch_multiple:
|
|
55
|
+
raise RuntimeError(f"No fetch multiple handler was defined for {self.path}")
|
|
56
|
+
return self.fetch_multiple(self.path)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class JsonPathDesc(PathDesc):
|
|
61
|
+
read_handler: Callable | None = read_json
|
|
62
|
+
write_handler: Callable | None = write_json
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def fetch_sorted_glob(pattern: str):
|
|
66
|
+
def fetch_func(path: Path):
|
|
67
|
+
return sorted(list(path.glob(pattern)))
|
|
68
|
+
|
|
69
|
+
return fetch_func
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Workspace:
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
ws_path: str | Path,
|
|
76
|
+
):
|
|
77
|
+
self._ws_path: Path = Path(ws_path)
|
|
78
|
+
self._make_workspace_objects_and_paths()
|
|
79
|
+
|
|
80
|
+
def _make_workspace_objects_and_paths(self):
|
|
81
|
+
fetch_part_parquets = fetch_sorted_glob("part.*.parquet")
|
|
82
|
+
fetch_part_trn_parquets = fetch_sorted_glob("part.*-trn.parquet")
|
|
83
|
+
fetch_part_val_parquets = fetch_sorted_glob("part.*-val.parquet")
|
|
84
|
+
fetch_part_jsons = fetch_sorted_glob("part.*.json")
|
|
85
|
+
|
|
86
|
+
def make_path_desc(path_desc_cls: type | None = None, **kwargs):
|
|
87
|
+
path_desc_cls = path_desc_cls or PathDesc
|
|
88
|
+
return path_desc_cls(root_dir=self._ws_path, **kwargs)
|
|
89
|
+
|
|
90
|
+
def make_json_path_desc(**kwargs):
|
|
91
|
+
return make_path_desc(path_desc_cls=JsonPathDesc, **kwargs)
|
|
92
|
+
|
|
93
|
+
def make_stats_json_path_desc(**kwargs):
|
|
94
|
+
return make_path_desc(path_desc_cls=JsonPathDesc, **kwargs)
|
|
95
|
+
|
|
96
|
+
# split-related
|
|
97
|
+
tgt_data = ["OriginalData", "tgt-data"]
|
|
98
|
+
self.tgt_data_path = self._ws_path / Path(*tgt_data)
|
|
99
|
+
self.tgt_data = make_path_desc(
|
|
100
|
+
parts=tgt_data,
|
|
101
|
+
fetch_multiple=fetch_part_parquets,
|
|
102
|
+
)
|
|
103
|
+
self.tgt_trn_data = make_path_desc(
|
|
104
|
+
parts=tgt_data,
|
|
105
|
+
fetch_multiple=fetch_part_trn_parquets,
|
|
106
|
+
)
|
|
107
|
+
self.tgt_val_data = make_path_desc(
|
|
108
|
+
parts=tgt_data,
|
|
109
|
+
fetch_multiple=fetch_part_val_parquets,
|
|
110
|
+
)
|
|
111
|
+
tgt_meta = ["OriginalData", "tgt-meta"]
|
|
112
|
+
self.tgt_meta_path = self._ws_path / Path(*tgt_meta)
|
|
113
|
+
self.tgt_encoding_types = make_json_path_desc(parts=tgt_meta + ["encoding-types.json"])
|
|
114
|
+
self.tgt_keys = make_json_path_desc(parts=tgt_meta + ["keys.json"])
|
|
115
|
+
ctx_data = ["OriginalData", "ctx-data"]
|
|
116
|
+
self.ctx_data_path = self._ws_path / Path(*ctx_data)
|
|
117
|
+
self.ctx_data = make_path_desc(
|
|
118
|
+
parts=ctx_data,
|
|
119
|
+
fetch_multiple=fetch_part_parquets,
|
|
120
|
+
)
|
|
121
|
+
self.ctx_trn_data = make_path_desc(
|
|
122
|
+
parts=ctx_data,
|
|
123
|
+
fetch_multiple=fetch_part_trn_parquets,
|
|
124
|
+
)
|
|
125
|
+
self.ctx_val_data = make_path_desc(
|
|
126
|
+
parts=ctx_data,
|
|
127
|
+
fetch_multiple=fetch_part_val_parquets,
|
|
128
|
+
)
|
|
129
|
+
ctx_meta = ["OriginalData", "ctx-meta"]
|
|
130
|
+
self.ctx_meta_path = self._ws_path / Path(*ctx_meta)
|
|
131
|
+
self.ctx_encoding_types = make_json_path_desc(parts=ctx_meta + ["encoding-types.json"])
|
|
132
|
+
self.ctx_keys = make_json_path_desc(parts=ctx_meta + ["keys.json"])
|
|
133
|
+
|
|
134
|
+
# analyze-related
|
|
135
|
+
tgt_stats = ["ModelStore", "tgt-stats"]
|
|
136
|
+
self.tgt_stats_path = Path(self._ws_path) / Path(*tgt_stats)
|
|
137
|
+
self.tgt_all_stats = make_path_desc(
|
|
138
|
+
parts=tgt_stats,
|
|
139
|
+
fetch_multiple=fetch_part_jsons,
|
|
140
|
+
)
|
|
141
|
+
self.tgt_stats = make_stats_json_path_desc(parts=tgt_stats + ["stats.json"])
|
|
142
|
+
ctx_stats = ["ModelStore", "ctx-stats"]
|
|
143
|
+
self.ctx_stats_path = Path(self._ws_path) / Path(*ctx_stats)
|
|
144
|
+
self.ctx_all_stats = make_path_desc(
|
|
145
|
+
parts=ctx_stats,
|
|
146
|
+
fetch_multiple=fetch_part_jsons,
|
|
147
|
+
)
|
|
148
|
+
self.ctx_stats = make_stats_json_path_desc(parts=ctx_stats + ["stats.json"])
|
|
149
|
+
|
|
150
|
+
# Encode-related
|
|
151
|
+
encoded_data = ["OriginalData", "encoded-data"]
|
|
152
|
+
self.encoded_data_path = Path(self._ws_path) / Path(*encoded_data)
|
|
153
|
+
self.encoded_data_val = make_path_desc(parts=encoded_data, fetch_multiple=fetch_part_val_parquets)
|
|
154
|
+
self.encoded_data_trn = make_path_desc(parts=encoded_data, fetch_multiple=fetch_part_trn_parquets)
|
|
155
|
+
|
|
156
|
+
# Train-related
|
|
157
|
+
model_data = ["ModelStore", "model-data"]
|
|
158
|
+
self.model_path: Path = Path(self._ws_path) / Path(*model_data)
|
|
159
|
+
self.model_optimizer_path: Path = self.model_path / "optimizer.pt"
|
|
160
|
+
self.model_lr_scheduler_path: Path = self.model_path / "lr-scheduler.pt"
|
|
161
|
+
self.model_dp_accountant_path: Path = self.model_path / "dp-accountant.pt"
|
|
162
|
+
self.model_tabular_weights_path: Path = self.model_path / "model-weights.pt"
|
|
163
|
+
self.model_progress_messages_path: Path = self.model_path / "progress-messages.csv"
|
|
164
|
+
self.model_tabular_configs = make_json_path_desc(parts=model_data + ["model-configs.json"])
|
|
165
|
+
|
|
166
|
+
# Generate-related
|
|
167
|
+
generated_data = ["SyntheticData"]
|
|
168
|
+
self.generated_data_path = Path(self._ws_path) / Path(*generated_data)
|
|
169
|
+
self.generated_data = make_path_desc(parts=generated_data, fetch_multiple=fetch_part_parquets)
|
|
170
|
+
|
|
171
|
+
# Report-related
|
|
172
|
+
self.report_ctx_data_path = self._ws_path / "report-ctx-data"
|
|
173
|
+
self.report_ctx_data = make_path_desc(
|
|
174
|
+
parts=["report-ctx-data"],
|
|
175
|
+
fetch_multiple=fetch_part_parquets,
|
|
176
|
+
)
|
|
177
|
+
self.report_trn_ctx_data = make_path_desc(
|
|
178
|
+
parts=["report-ctx-data"],
|
|
179
|
+
fetch_multiple=fetch_part_trn_parquets,
|
|
180
|
+
)
|
|
181
|
+
self.report_val_ctx_data = make_path_desc(
|
|
182
|
+
parts=["report-ctx-data"],
|
|
183
|
+
fetch_multiple=fetch_part_val_parquets,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def ensure_workspace_dir(workspace_dir: str | Path) -> Path:
|
|
188
|
+
workspace_dir = Path(workspace_dir)
|
|
189
|
+
workspace_dir.mkdir(exist_ok=True, parents=True)
|
|
190
|
+
return workspace_dir
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def reset_dir(path: Path) -> None:
|
|
194
|
+
"""Create directory if not exists. Otherwise remove all files from it"""
|
|
195
|
+
if os.path.exists(path):
|
|
196
|
+
_LOG.info(f"clean `{path}`")
|
|
197
|
+
for file in os.listdir(path):
|
|
198
|
+
if os.path.isfile(os.path.join(path, file)):
|
|
199
|
+
os.remove(os.path.join(path, file))
|
|
200
|
+
else:
|
|
201
|
+
_LOG.info(f"create `{path}`")
|
|
202
|
+
os.makedirs(path)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def resolve_model_type(workspace_dir: str | Path) -> ModelType:
|
|
206
|
+
"""
|
|
207
|
+
Determine the model type based on encoding types in the workspace target metadata.
|
|
208
|
+
"""
|
|
209
|
+
workspace_dir = ensure_workspace_dir(workspace_dir)
|
|
210
|
+
workspace = Workspace(workspace_dir)
|
|
211
|
+
stats = workspace.tgt_stats.read()
|
|
212
|
+
columns = stats.get("columns", {})
|
|
213
|
+
if len(columns) == 0:
|
|
214
|
+
return ModelType.tabular
|
|
215
|
+
# fetch encoding type from the first column
|
|
216
|
+
encoding_type = next(iter(columns.values())).get("encoding_type")
|
|
217
|
+
if encoding_type.startswith(ModelType.tabular):
|
|
218
|
+
model_type = ModelType.tabular
|
|
219
|
+
elif encoding_type.startswith(ModelType.language):
|
|
220
|
+
model_type = ModelType.language
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Unknown encoding type, valid encoding types start with TABULAR_ or LANGUAGE_: {encoding_type}"
|
|
224
|
+
)
|
|
225
|
+
return model_type
|