mcli-framework 7.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""MLflow Configuration for Stock Recommendation System"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
import mlflow
|
|
8
|
+
from mlflow.tracking import MlflowClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MLflowConfig:
|
|
12
|
+
"""Configuration class for MLflow tracking and model registry"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
tracking_uri: Optional[str] = None,
|
|
17
|
+
experiment_name: str = "politician-trading-stock-recommendations",
|
|
18
|
+
artifact_root: Optional[str] = None,
|
|
19
|
+
):
|
|
20
|
+
self.tracking_uri = tracking_uri or self._default_tracking_uri()
|
|
21
|
+
self.experiment_name = experiment_name
|
|
22
|
+
self.artifact_root = artifact_root or self._default_artifact_root()
|
|
23
|
+
self._client = None
|
|
24
|
+
|
|
25
|
+
def _default_tracking_uri(self) -> str:
|
|
26
|
+
"""Get default MLflow tracking URI"""
|
|
27
|
+
project_root = Path(__file__).parent.parent.parent.parent.parent
|
|
28
|
+
return f"file://{project_root}/mlruns"
|
|
29
|
+
|
|
30
|
+
def _default_artifact_root(self) -> str:
|
|
31
|
+
"""Get default artifact storage location"""
|
|
32
|
+
project_root = Path(__file__).parent.parent.parent.parent.parent
|
|
33
|
+
return f"{project_root}/artifacts"
|
|
34
|
+
|
|
35
|
+
def setup_tracking(self) -> None:
|
|
36
|
+
"""Initialize MLflow tracking configuration"""
|
|
37
|
+
mlflow.set_tracking_uri(self.tracking_uri)
|
|
38
|
+
|
|
39
|
+
# Create or get experiment
|
|
40
|
+
try:
|
|
41
|
+
experiment = mlflow.get_experiment_by_name(self.experiment_name)
|
|
42
|
+
if experiment is None:
|
|
43
|
+
experiment_id = mlflow.create_experiment(
|
|
44
|
+
name=self.experiment_name, artifact_location=self.artifact_root
|
|
45
|
+
)
|
|
46
|
+
print(f"Created new experiment: {self.experiment_name} (ID: {experiment_id})")
|
|
47
|
+
else:
|
|
48
|
+
experiment_id = experiment.experiment_id
|
|
49
|
+
print(f"Using existing experiment: {self.experiment_name} (ID: {experiment_id})")
|
|
50
|
+
|
|
51
|
+
mlflow.set_experiment(self.experiment_name)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error setting up MLflow experiment: {e}")
|
|
55
|
+
raise
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def client(self) -> MlflowClient:
|
|
59
|
+
"""Get MLflow client instance"""
|
|
60
|
+
if self._client is None:
|
|
61
|
+
self._client = MlflowClient(tracking_uri=self.tracking_uri)
|
|
62
|
+
return self._client
|
|
63
|
+
|
|
64
|
+
def get_model_registry_uri(self) -> str:
|
|
65
|
+
"""Get model registry URI"""
|
|
66
|
+
return self.tracking_uri
|
|
67
|
+
|
|
68
|
+
def log_model_metadata(self, run_id: str, metadata: Dict[str, Any]) -> None:
|
|
69
|
+
"""Log additional metadata for a model"""
|
|
70
|
+
for key, value in metadata.items():
|
|
71
|
+
self.client.log_param(run_id, key, value)
|
|
72
|
+
|
|
73
|
+
def register_model(
|
|
74
|
+
self,
|
|
75
|
+
model_uri: str,
|
|
76
|
+
model_name: str,
|
|
77
|
+
tags: Optional[Dict[str, str]] = None,
|
|
78
|
+
description: Optional[str] = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Register a model in MLflow Model Registry"""
|
|
81
|
+
try:
|
|
82
|
+
model_version = mlflow.register_model(model_uri=model_uri, name=model_name, tags=tags)
|
|
83
|
+
|
|
84
|
+
if description:
|
|
85
|
+
self.client.update_model_version(
|
|
86
|
+
name=model_name, version=model_version.version, description=description
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
print(f"Registered model: {model_name}, version: {model_version.version}")
|
|
90
|
+
return model_version
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Error registering model: {e}")
|
|
94
|
+
raise
|
|
95
|
+
|
|
96
|
+
def transition_model_stage(
|
|
97
|
+
self, model_name: str, version: str, stage: str, archive_existing_versions: bool = False
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Transition model to a different stage"""
|
|
100
|
+
try:
|
|
101
|
+
self.client.transition_model_version_stage(
|
|
102
|
+
name=model_name,
|
|
103
|
+
version=version,
|
|
104
|
+
stage=stage,
|
|
105
|
+
archive_existing_versions=archive_existing_versions,
|
|
106
|
+
)
|
|
107
|
+
print(f"Transitioned {model_name} v{version} to {stage}")
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
print(f"Error transitioning model stage: {e}")
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Global configuration instance
|
|
115
|
+
mlflow_config = MLflowConfig()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_mlflow_config() -> MLflowConfig:
|
|
119
|
+
"""Get the global MLflow configuration instance"""
|
|
120
|
+
return mlflow_config
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def setup_mlflow() -> None:
|
|
124
|
+
"""Setup MLflow tracking and experiment"""
|
|
125
|
+
mlflow_config.setup_tracking()
|
|
126
|
+
print(f"MLflow tracking URI: {mlflow_config.tracking_uri}")
|
|
127
|
+
print(f"Artifact root: {mlflow_config.artifact_root}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
setup_mlflow()
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Unified MLOps Manager for Stock Recommendation System"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import joblib
|
|
11
|
+
import mlflow
|
|
12
|
+
import mlflow.pytorch
|
|
13
|
+
import mlflow.sklearn
|
|
14
|
+
from mlflow.tracking import MlflowClient
|
|
15
|
+
|
|
16
|
+
from .dvc_config import DVCConfig, get_dvc_config
|
|
17
|
+
from .mlflow_config import MLflowConfig, get_mlflow_config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MLOpsManager:
|
|
21
|
+
"""Unified manager for MLflow and DVC operations"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self, mlflow_config: Optional[MLflowConfig] = None, dvc_config: Optional[DVCConfig] = None
|
|
25
|
+
):
|
|
26
|
+
self.mlflow_config = mlflow_config or get_mlflow_config()
|
|
27
|
+
self.dvc_config = dvc_config or get_dvc_config()
|
|
28
|
+
self._setup_completed = False
|
|
29
|
+
|
|
30
|
+
def setup(self) -> None:
|
|
31
|
+
"""Initialize MLOps infrastructure"""
|
|
32
|
+
if self._setup_completed:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
print("Setting up MLOps infrastructure...")
|
|
36
|
+
|
|
37
|
+
# Setup MLflow
|
|
38
|
+
self.mlflow_config.setup_tracking()
|
|
39
|
+
|
|
40
|
+
# Setup DVC
|
|
41
|
+
self.dvc_config.setup_data_directories()
|
|
42
|
+
|
|
43
|
+
self._setup_completed = True
|
|
44
|
+
print("✅ MLOps infrastructure setup completed")
|
|
45
|
+
|
|
46
|
+
def start_experiment_run(
|
|
47
|
+
self,
|
|
48
|
+
run_name: Optional[str] = None,
|
|
49
|
+
tags: Optional[Dict[str, str]] = None,
|
|
50
|
+
description: Optional[str] = None,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Start a new MLflow experiment run"""
|
|
53
|
+
if not self._setup_completed:
|
|
54
|
+
self.setup()
|
|
55
|
+
|
|
56
|
+
run_tags = tags or {}
|
|
57
|
+
if description:
|
|
58
|
+
run_tags["description"] = description
|
|
59
|
+
|
|
60
|
+
run = mlflow.start_run(run_name=run_name, tags=run_tags)
|
|
61
|
+
print(f"Started MLflow run: {run.info.run_id}")
|
|
62
|
+
return run.info.run_id
|
|
63
|
+
|
|
64
|
+
def log_parameters(self, params: Dict[str, Any]) -> None:
|
|
65
|
+
"""Log parameters to MLflow"""
|
|
66
|
+
for key, value in params.items():
|
|
67
|
+
mlflow.log_param(key, value)
|
|
68
|
+
|
|
69
|
+
def log_metrics(self, metrics: Dict[str, float], step: Optional[int] = None) -> None:
|
|
70
|
+
"""Log metrics to MLflow"""
|
|
71
|
+
for key, value in metrics.items():
|
|
72
|
+
mlflow.log_metric(key, value, step=step)
|
|
73
|
+
|
|
74
|
+
def log_artifacts(self, artifact_path: Path, artifact_name: Optional[str] = None) -> None:
|
|
75
|
+
"""Log artifacts to MLflow"""
|
|
76
|
+
if artifact_path.is_file():
|
|
77
|
+
mlflow.log_artifact(str(artifact_path), artifact_name)
|
|
78
|
+
else:
|
|
79
|
+
mlflow.log_artifacts(str(artifact_path), artifact_name)
|
|
80
|
+
|
|
81
|
+
def save_and_log_model(
|
|
82
|
+
self,
|
|
83
|
+
model: Any,
|
|
84
|
+
model_name: str,
|
|
85
|
+
model_type: str = "pytorch",
|
|
86
|
+
signature: Optional[Any] = None,
|
|
87
|
+
input_example: Optional[Any] = None,
|
|
88
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
89
|
+
) -> str:
|
|
90
|
+
"""Save model locally and log to MLflow"""
|
|
91
|
+
# Create model directory
|
|
92
|
+
model_dir = self.dvc_config.models_dir / model_type / model_name
|
|
93
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
|
|
95
|
+
# Save model based on type
|
|
96
|
+
if model_type == "pytorch":
|
|
97
|
+
import torch
|
|
98
|
+
|
|
99
|
+
model_path = model_dir / "model.pt"
|
|
100
|
+
torch.save(model.state_dict(), model_path)
|
|
101
|
+
|
|
102
|
+
# Log to MLflow
|
|
103
|
+
mlflow.pytorch.log_model(
|
|
104
|
+
pytorch_model=model,
|
|
105
|
+
artifact_path=f"models/{model_name}",
|
|
106
|
+
signature=signature,
|
|
107
|
+
input_example=input_example,
|
|
108
|
+
extra_files=[str(model_path)] if model_path.exists() else None,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
elif model_type == "sklearn":
|
|
112
|
+
model_path = model_dir / "model.pkl"
|
|
113
|
+
joblib.dump(model, model_path)
|
|
114
|
+
|
|
115
|
+
# Log to MLflow
|
|
116
|
+
mlflow.sklearn.log_model(
|
|
117
|
+
sk_model=model,
|
|
118
|
+
artifact_path=f"models/{model_name}",
|
|
119
|
+
signature=signature,
|
|
120
|
+
input_example=input_example,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
# Generic pickle save
|
|
125
|
+
model_path = model_dir / "model.pkl"
|
|
126
|
+
with open(model_path, "wb") as f:
|
|
127
|
+
pickle.dump(model, f)
|
|
128
|
+
|
|
129
|
+
# Log as generic artifact
|
|
130
|
+
mlflow.log_artifact(str(model_path), f"models/{model_name}")
|
|
131
|
+
|
|
132
|
+
# Save metadata
|
|
133
|
+
if metadata:
|
|
134
|
+
metadata_path = model_dir / "metadata.json"
|
|
135
|
+
with open(metadata_path, "w") as f:
|
|
136
|
+
json.dump(metadata, f, indent=2, default=str)
|
|
137
|
+
mlflow.log_artifact(str(metadata_path), f"models/{model_name}")
|
|
138
|
+
|
|
139
|
+
# Add to DVC tracking
|
|
140
|
+
self.dvc_config.add_data_to_dvc(model_dir)
|
|
141
|
+
|
|
142
|
+
print(f"Model saved and logged: {model_name} ({model_type})")
|
|
143
|
+
return str(model_path)
|
|
144
|
+
|
|
145
|
+
def register_model(
|
|
146
|
+
self,
|
|
147
|
+
model_name: str,
|
|
148
|
+
stage: str = "Staging",
|
|
149
|
+
description: Optional[str] = None,
|
|
150
|
+
tags: Optional[Dict[str, str]] = None,
|
|
151
|
+
) -> str:
|
|
152
|
+
"""Register model in MLflow Model Registry"""
|
|
153
|
+
# Get the current run
|
|
154
|
+
run = mlflow.active_run()
|
|
155
|
+
if not run:
|
|
156
|
+
raise Exception("No active MLflow run. Start a run first.")
|
|
157
|
+
|
|
158
|
+
model_uri = f"runs:/{run.info.run_id}/models/{model_name}"
|
|
159
|
+
|
|
160
|
+
# Register model
|
|
161
|
+
model_version = self.mlflow_config.register_model(
|
|
162
|
+
model_uri=model_uri, model_name=model_name, tags=tags, description=description
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Transition to requested stage
|
|
166
|
+
if stage != "None":
|
|
167
|
+
self.mlflow_config.transition_model_stage(
|
|
168
|
+
model_name=model_name, version=model_version.version, stage=stage
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return model_version.version
|
|
172
|
+
|
|
173
|
+
def load_model(
|
|
174
|
+
self, model_name: str, version: Optional[str] = None, stage: Optional[str] = None
|
|
175
|
+
) -> Any:
|
|
176
|
+
"""Load model from MLflow Model Registry"""
|
|
177
|
+
if version:
|
|
178
|
+
model_uri = f"models:/{model_name}/{version}"
|
|
179
|
+
elif stage:
|
|
180
|
+
model_uri = f"models:/{model_name}/{stage}"
|
|
181
|
+
else:
|
|
182
|
+
model_uri = f"models:/{model_name}/latest"
|
|
183
|
+
|
|
184
|
+
return mlflow.pytorch.load_model(model_uri)
|
|
185
|
+
|
|
186
|
+
def end_run(self, status: str = "FINISHED") -> None:
|
|
187
|
+
"""End the current MLflow run"""
|
|
188
|
+
mlflow.end_run(status=status)
|
|
189
|
+
|
|
190
|
+
def version_data(self, data_path: Path, message: Optional[str] = None) -> str:
|
|
191
|
+
"""Version data using DVC"""
|
|
192
|
+
self.dvc_config.add_data_to_dvc(data_path, message)
|
|
193
|
+
return self.dvc_config.get_data_version(data_path) or "unknown"
|
|
194
|
+
|
|
195
|
+
def create_ml_pipeline_stage(
|
|
196
|
+
self,
|
|
197
|
+
stage_name: str,
|
|
198
|
+
script_path: str,
|
|
199
|
+
dependencies: List[str],
|
|
200
|
+
outputs: List[str],
|
|
201
|
+
parameters: Optional[List[str]] = None,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Create a DVC pipeline stage for ML workflow"""
|
|
204
|
+
command = f"python {script_path}"
|
|
205
|
+
|
|
206
|
+
self.dvc_config.create_pipeline_stage(
|
|
207
|
+
stage_name=stage_name,
|
|
208
|
+
command=command,
|
|
209
|
+
dependencies=dependencies,
|
|
210
|
+
outputs=outputs,
|
|
211
|
+
parameters=parameters,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def run_ml_pipeline(self, stage_name: Optional[str] = None) -> None:
|
|
215
|
+
"""Run DVC ML pipeline"""
|
|
216
|
+
self.dvc_config.run_pipeline(stage_name)
|
|
217
|
+
|
|
218
|
+
def get_experiment_metrics(self, experiment_name: str) -> List[Dict[str, Any]]:
|
|
219
|
+
"""Get metrics from all runs in an experiment"""
|
|
220
|
+
experiment = mlflow.get_experiment_by_name(experiment_name)
|
|
221
|
+
if not experiment:
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
runs = mlflow.search_runs(experiment_ids=[experiment.experiment_id])
|
|
225
|
+
return runs.to_dict("records")
|
|
226
|
+
|
|
227
|
+
def compare_models(
|
|
228
|
+
self, model_names: List[str], metric_name: str = "accuracy"
|
|
229
|
+
) -> Dict[str, Any]:
|
|
230
|
+
"""Compare models by a specific metric"""
|
|
231
|
+
comparison = {}
|
|
232
|
+
|
|
233
|
+
for model_name in model_names:
|
|
234
|
+
try:
|
|
235
|
+
# Get latest version metrics
|
|
236
|
+
latest_versions = self.mlflow_config.client.get_latest_versions(
|
|
237
|
+
model_name, stages=["Production", "Staging"]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if latest_versions:
|
|
241
|
+
version = latest_versions[0]
|
|
242
|
+
run = self.mlflow_config.client.get_run(version.run_id)
|
|
243
|
+
comparison[model_name] = {
|
|
244
|
+
"version": version.version,
|
|
245
|
+
"stage": version.current_stage,
|
|
246
|
+
"metric_value": run.data.metrics.get(metric_name, 0.0),
|
|
247
|
+
"run_id": version.run_id,
|
|
248
|
+
}
|
|
249
|
+
except Exception as e:
|
|
250
|
+
comparison[model_name] = {"error": str(e)}
|
|
251
|
+
|
|
252
|
+
return comparison
|
|
253
|
+
|
|
254
|
+
def cleanup_old_runs(self, days_old: int = 30) -> None:
|
|
255
|
+
"""Clean up old experiment runs"""
|
|
256
|
+
# This would implement cleanup logic for old runs
|
|
257
|
+
# For now, just a placeholder
|
|
258
|
+
print(f"Cleanup of runs older than {days_old} days would be implemented here")
|
|
259
|
+
|
|
260
|
+
def get_system_info(self) -> Dict[str, Any]:
|
|
261
|
+
"""Get MLOps system information"""
|
|
262
|
+
return {
|
|
263
|
+
"mlflow_tracking_uri": self.mlflow_config.tracking_uri,
|
|
264
|
+
"mlflow_experiment": self.mlflow_config.experiment_name,
|
|
265
|
+
"dvc_project_root": str(self.dvc_config.project_root),
|
|
266
|
+
"data_directory": str(self.dvc_config.data_dir),
|
|
267
|
+
"models_directory": str(self.dvc_config.models_dir),
|
|
268
|
+
"setup_completed": self._setup_completed,
|
|
269
|
+
"timestamp": datetime.now().isoformat(),
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Global MLOps manager instance
|
|
274
|
+
mlops_manager = MLOpsManager()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def get_mlops_manager() -> MLOpsManager:
|
|
278
|
+
"""Get the global MLOps manager instance"""
|
|
279
|
+
return mlops_manager
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
# Test the MLOps setup
|
|
284
|
+
manager = get_mlops_manager()
|
|
285
|
+
manager.setup()
|
|
286
|
+
|
|
287
|
+
print("\n" + "=" * 50)
|
|
288
|
+
print("MLOPs System Information:")
|
|
289
|
+
print("=" * 50)
|
|
290
|
+
|
|
291
|
+
system_info = manager.get_system_info()
|
|
292
|
+
for key, value in system_info.items():
|
|
293
|
+
print(f"{key}: {value}")
|