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,714 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Lightweight Model Server for MCLI
|
|
4
|
+
|
|
5
|
+
A minimal model server that downloads and runs extremely small and efficient models
|
|
6
|
+
directly from the internet without requiring Ollama or heavy dependencies.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tarfile
|
|
15
|
+
import tempfile
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
import zipfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
import requests
|
|
24
|
+
|
|
25
|
+
# Add the parent directory to the path so we can import the model service
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
27
|
+
|
|
28
|
+
# Import only what we need to avoid circular imports
|
|
29
|
+
from mcli.lib.logger.logger import get_logger
|
|
30
|
+
|
|
31
|
+
# Ultra-lightweight models (under 1B parameters)
|
|
32
|
+
LIGHTWEIGHT_MODELS = {
|
|
33
|
+
"distilbert-base-uncased": {
|
|
34
|
+
"name": "DistilBERT Base",
|
|
35
|
+
"description": "Distilled BERT model, 66M parameters, extremely fast",
|
|
36
|
+
"model_url": "https://huggingface.co/distilbert-base-uncased/resolve/main/pytorch_model.bin",
|
|
37
|
+
"tokenizer_url": "https://huggingface.co/distilbert-base-uncased/resolve/main/tokenizer.json",
|
|
38
|
+
"config_url": "https://huggingface.co/distilbert-base-uncased/resolve/main/config.json",
|
|
39
|
+
"model_type": "text-classification",
|
|
40
|
+
"parameters": "66M",
|
|
41
|
+
"size_mb": 260,
|
|
42
|
+
"efficiency_score": 10.0,
|
|
43
|
+
"accuracy_score": 7.0,
|
|
44
|
+
"tags": ["classification", "tiny", "fast"],
|
|
45
|
+
},
|
|
46
|
+
"microsoft/DialoGPT-small": {
|
|
47
|
+
"name": "DialoGPT Small",
|
|
48
|
+
"description": "Microsoft's small conversational model, 117M parameters",
|
|
49
|
+
"model_url": "https://huggingface.co/microsoft/DialoGPT-small/resolve/main/pytorch_model.bin",
|
|
50
|
+
"tokenizer_url": "https://huggingface.co/microsoft/DialoGPT-small/resolve/main/tokenizer.json",
|
|
51
|
+
"config_url": "https://huggingface.co/microsoft/DialoGPT-small/resolve/main/config.json",
|
|
52
|
+
"model_type": "text-generation",
|
|
53
|
+
"parameters": "117M",
|
|
54
|
+
"size_mb": 470,
|
|
55
|
+
"efficiency_score": 9.8,
|
|
56
|
+
"accuracy_score": 6.5,
|
|
57
|
+
"tags": ["conversation", "small", "fast"],
|
|
58
|
+
},
|
|
59
|
+
"sshleifer/tiny-distilbert-base-uncased": {
|
|
60
|
+
"name": "Tiny DistilBERT",
|
|
61
|
+
"description": "Ultra-compact DistilBERT, 22M parameters",
|
|
62
|
+
"model_url": "https://huggingface.co/sshleifer/tiny-distilbert-base-uncased/resolve/main/pytorch_model.bin",
|
|
63
|
+
"tokenizer_url": "https://huggingface.co/sshleifer/tiny-distilbert-base-uncased/resolve/main/tokenizer.json",
|
|
64
|
+
"config_url": "https://huggingface.co/sshleifer/tiny-distilbert-base-uncased/resolve/main/config.json",
|
|
65
|
+
"model_type": "text-classification",
|
|
66
|
+
"parameters": "22M",
|
|
67
|
+
"size_mb": 88,
|
|
68
|
+
"efficiency_score": 10.0,
|
|
69
|
+
"accuracy_score": 5.5,
|
|
70
|
+
"tags": ["classification", "ultra-tiny", "fastest"],
|
|
71
|
+
},
|
|
72
|
+
"microsoft/DialoGPT-tiny": {
|
|
73
|
+
"name": "DialoGPT Tiny",
|
|
74
|
+
"description": "Microsoft's tiny conversational model, 33M parameters",
|
|
75
|
+
"model_url": "https://huggingface.co/microsoft/DialoGPT-tiny/resolve/main/pytorch_model.bin",
|
|
76
|
+
"tokenizer_url": "https://huggingface.co/microsoft/DialoGPT-tiny/resolve/main/tokenizer.json",
|
|
77
|
+
"config_url": "https://huggingface.co/microsoft/DialoGPT-tiny/resolve/main/config.json",
|
|
78
|
+
"model_type": "text-generation",
|
|
79
|
+
"parameters": "33M",
|
|
80
|
+
"size_mb": 132,
|
|
81
|
+
"efficiency_score": 10.0,
|
|
82
|
+
"accuracy_score": 5.0,
|
|
83
|
+
"tags": ["conversation", "ultra-tiny", "fastest"],
|
|
84
|
+
},
|
|
85
|
+
"prajjwal1/bert-tiny": {
|
|
86
|
+
"name": "BERT Tiny",
|
|
87
|
+
"description": "Tiny BERT model, 4.4M parameters, extremely lightweight",
|
|
88
|
+
"model_url": "https://huggingface.co/prajjwal1/bert-tiny/resolve/main/pytorch_model.bin",
|
|
89
|
+
"tokenizer_url": "https://huggingface.co/prajjwal1/bert-tiny/resolve/main/vocab.txt",
|
|
90
|
+
"config_url": "https://huggingface.co/prajjwal1/bert-tiny/resolve/main/config.json",
|
|
91
|
+
"model_type": "text-classification",
|
|
92
|
+
"parameters": "4.4M",
|
|
93
|
+
"size_mb": 18,
|
|
94
|
+
"efficiency_score": 10.0,
|
|
95
|
+
"accuracy_score": 4.5,
|
|
96
|
+
"tags": ["classification", "micro", "lightning-fast"],
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LightweightModelDownloader:
|
|
102
|
+
"""Downloads and manages lightweight models"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, models_dir: str = "./lightweight_models"):
|
|
105
|
+
self.models_dir = Path(models_dir)
|
|
106
|
+
self.models_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
self.session = requests.Session()
|
|
108
|
+
self.session.headers.update({"User-Agent": "MCLI-Lightweight-Model-Server/1.0"})
|
|
109
|
+
|
|
110
|
+
def download_file(self, url: str, filepath: Path, description: str = "file") -> bool:
|
|
111
|
+
"""Download a file with progress tracking"""
|
|
112
|
+
try:
|
|
113
|
+
print(f"š„ Downloading {description}...")
|
|
114
|
+
response = self.session.get(url, stream=True)
|
|
115
|
+
response.raise_for_status()
|
|
116
|
+
|
|
117
|
+
total_size = int(response.headers.get("content-length", 0))
|
|
118
|
+
downloaded = 0
|
|
119
|
+
|
|
120
|
+
with open(filepath, "wb") as f:
|
|
121
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
122
|
+
if chunk:
|
|
123
|
+
f.write(chunk)
|
|
124
|
+
downloaded += len(chunk)
|
|
125
|
+
if total_size > 0:
|
|
126
|
+
percent = (downloaded / total_size) * 100
|
|
127
|
+
print(
|
|
128
|
+
f"\rš„ Progress: {percent:.1f}% ({downloaded}/{total_size} bytes)",
|
|
129
|
+
end="",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
print(f"\nā
Downloaded {description}: {filepath}")
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
print(f"\nā Failed to download {description}: {e}")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def download_model(self, model_key: str) -> Optional[str]:
|
|
140
|
+
"""Download a complete model"""
|
|
141
|
+
model_info = LIGHTWEIGHT_MODELS[model_key]
|
|
142
|
+
|
|
143
|
+
print(f"\nš Downloading {model_info['name']}...")
|
|
144
|
+
print(f" Description: {model_info['description']}")
|
|
145
|
+
print(f" Parameters: {model_info['parameters']}")
|
|
146
|
+
print(f" Size: {model_info['size_mb']} MB")
|
|
147
|
+
print(f" Efficiency Score: {model_info['efficiency_score']}/10")
|
|
148
|
+
|
|
149
|
+
# Create model directory (with parents)
|
|
150
|
+
model_dir = self.models_dir / model_key
|
|
151
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
# Download model files - config and model are required, tokenizer is optional
|
|
154
|
+
required_files = [
|
|
155
|
+
("config", model_info["config_url"], model_dir / "config.json"),
|
|
156
|
+
("model", model_info["model_url"], model_dir / "pytorch_model.bin"),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
# Determine tokenizer filename based on URL
|
|
160
|
+
tokenizer_url = model_info["tokenizer_url"]
|
|
161
|
+
if tokenizer_url.endswith("vocab.txt"):
|
|
162
|
+
tokenizer_filename = "vocab.txt"
|
|
163
|
+
elif tokenizer_url.endswith("tokenizer.json"):
|
|
164
|
+
tokenizer_filename = "tokenizer.json"
|
|
165
|
+
elif tokenizer_url.endswith("tokenizer_config.json"):
|
|
166
|
+
tokenizer_filename = "tokenizer_config.json"
|
|
167
|
+
else:
|
|
168
|
+
tokenizer_filename = "tokenizer.json" # default
|
|
169
|
+
|
|
170
|
+
optional_files = [
|
|
171
|
+
("tokenizer", tokenizer_url, model_dir / tokenizer_filename),
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
# Download required files
|
|
175
|
+
for file_type, url, filepath in required_files:
|
|
176
|
+
if not self.download_file(url, filepath, file_type):
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
# Try to download optional files
|
|
180
|
+
for file_type, url, filepath in optional_files:
|
|
181
|
+
try:
|
|
182
|
+
self.download_file(url, filepath, file_type)
|
|
183
|
+
except Exception:
|
|
184
|
+
print(f"ā ļø Optional file {file_type} not available (this is OK)")
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
print(f"ā
Successfully downloaded {model_info['name']}")
|
|
188
|
+
return str(model_dir)
|
|
189
|
+
|
|
190
|
+
def get_downloaded_models(self) -> List[str]:
|
|
191
|
+
"""Get list of downloaded models"""
|
|
192
|
+
models = []
|
|
193
|
+
# Check for nested structure like prajjwal1/bert-tiny
|
|
194
|
+
for org_dir in self.models_dir.iterdir():
|
|
195
|
+
if org_dir.is_dir() and not org_dir.name.startswith("."):
|
|
196
|
+
for model_dir in org_dir.iterdir():
|
|
197
|
+
if (
|
|
198
|
+
model_dir.is_dir()
|
|
199
|
+
and (model_dir / "pytorch_model.bin").exists()
|
|
200
|
+
and (model_dir / "config.json").exists()
|
|
201
|
+
):
|
|
202
|
+
models.append(f"{org_dir.name}/{model_dir.name}")
|
|
203
|
+
return models
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class LightweightModelServer:
|
|
207
|
+
"""Lightweight model server without heavy dependencies"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, models_dir: str = "./lightweight_models", port: int = 8080):
|
|
210
|
+
self.models_dir = Path(models_dir)
|
|
211
|
+
self.port = port
|
|
212
|
+
self.downloader = LightweightModelDownloader(models_dir)
|
|
213
|
+
self.loaded_models: Dict[str, Dict[str, Any]] = {}
|
|
214
|
+
self.server_thread = None
|
|
215
|
+
self.running = False
|
|
216
|
+
|
|
217
|
+
def start_server(self):
|
|
218
|
+
"""Start the lightweight server"""
|
|
219
|
+
if self.running:
|
|
220
|
+
print("ā ļø Server already running")
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Load any existing downloaded models first
|
|
224
|
+
loaded_count = self.load_existing_models()
|
|
225
|
+
if loaded_count > 0:
|
|
226
|
+
print(f"š Loaded {loaded_count} existing models")
|
|
227
|
+
|
|
228
|
+
self.running = True
|
|
229
|
+
self.server_thread = threading.Thread(target=self._run_server, daemon=True)
|
|
230
|
+
self.server_thread.start()
|
|
231
|
+
|
|
232
|
+
print(f"š Lightweight model server started on port {self.port}")
|
|
233
|
+
print(f"š API available at: http://localhost:{self.port}")
|
|
234
|
+
|
|
235
|
+
def load_existing_models(self):
|
|
236
|
+
"""Load all downloaded models into memory"""
|
|
237
|
+
downloaded_models = self.downloader.get_downloaded_models()
|
|
238
|
+
for model_key in downloaded_models:
|
|
239
|
+
if model_key in LIGHTWEIGHT_MODELS and model_key not in self.loaded_models:
|
|
240
|
+
model_info = LIGHTWEIGHT_MODELS[model_key]
|
|
241
|
+
model_path = str(self.models_dir / model_key)
|
|
242
|
+
self.loaded_models[model_key] = {
|
|
243
|
+
"path": model_path,
|
|
244
|
+
"type": model_info["model_type"],
|
|
245
|
+
"parameters": model_info["parameters"],
|
|
246
|
+
"size_mb": model_info["size_mb"],
|
|
247
|
+
}
|
|
248
|
+
print(f"ā
Loaded existing model: {model_key}")
|
|
249
|
+
return len(self.loaded_models)
|
|
250
|
+
|
|
251
|
+
def _run_server(self):
|
|
252
|
+
"""Run the HTTP server"""
|
|
253
|
+
import urllib.parse
|
|
254
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
255
|
+
|
|
256
|
+
class ModelHandler(BaseHTTPRequestHandler):
|
|
257
|
+
def __init__(self, *args, server_instance=None, **kwargs):
|
|
258
|
+
self.server_instance = server_instance
|
|
259
|
+
super().__init__(*args, **kwargs)
|
|
260
|
+
|
|
261
|
+
def do_GET(self):
|
|
262
|
+
"""Handle GET requests"""
|
|
263
|
+
parsed_path = urllib.parse.urlparse(self.path)
|
|
264
|
+
path = parsed_path.path
|
|
265
|
+
|
|
266
|
+
if path == "/":
|
|
267
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
268
|
+
self._send_response(
|
|
269
|
+
200, {"status": "running", "models": list(loaded_models.keys())}
|
|
270
|
+
)
|
|
271
|
+
elif path == "/models":
|
|
272
|
+
models = []
|
|
273
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
274
|
+
for name, model_info in loaded_models.items():
|
|
275
|
+
models.append(
|
|
276
|
+
{
|
|
277
|
+
"name": name,
|
|
278
|
+
"type": model_info.get("type", "unknown"),
|
|
279
|
+
"parameters": model_info.get("parameters", "unknown"),
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
self._send_response(200, {"models": models})
|
|
283
|
+
elif path == "/health":
|
|
284
|
+
self._send_response(200, {"status": "healthy"})
|
|
285
|
+
elif path == "/api/generate":
|
|
286
|
+
# Ollama-compatible endpoint (GET not typical, but handle it)
|
|
287
|
+
self._send_response(405, {"error": "Method not allowed. Use POST."})
|
|
288
|
+
elif path == "/api/tags":
|
|
289
|
+
# Ollama-compatible model listing endpoint
|
|
290
|
+
self._handle_ollama_tags()
|
|
291
|
+
else:
|
|
292
|
+
self._send_response(404, {"error": "Not found"})
|
|
293
|
+
|
|
294
|
+
def do_POST(self):
|
|
295
|
+
"""Handle POST requests"""
|
|
296
|
+
parsed_path = urllib.parse.urlparse(self.path)
|
|
297
|
+
path = parsed_path.path
|
|
298
|
+
|
|
299
|
+
if path.startswith("/models/") and path.endswith("/generate"):
|
|
300
|
+
model_name = path.split("/")[2]
|
|
301
|
+
self._handle_generate(model_name)
|
|
302
|
+
elif path == "/api/generate":
|
|
303
|
+
# Ollama-compatible endpoint
|
|
304
|
+
self._handle_ollama_generate()
|
|
305
|
+
else:
|
|
306
|
+
self._send_response(404, {"error": "Not found"})
|
|
307
|
+
|
|
308
|
+
def _handle_generate(self, model_name):
|
|
309
|
+
"""Handle text generation requests"""
|
|
310
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
311
|
+
if model_name not in loaded_models:
|
|
312
|
+
self._send_response(404, {"error": f"Model {model_name} not found"})
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
317
|
+
post_data = self.rfile.read(content_length)
|
|
318
|
+
request_data = json.loads(post_data.decode("utf-8"))
|
|
319
|
+
|
|
320
|
+
prompt = request_data.get("prompt", "")
|
|
321
|
+
if not prompt:
|
|
322
|
+
self._send_response(400, {"error": "No prompt provided"})
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Simple text generation (placeholder)
|
|
326
|
+
response_text = f"Generated response for: {prompt[:50]}..."
|
|
327
|
+
|
|
328
|
+
self._send_response(200, {"generated_text": response_text, "model": model_name})
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
self._send_response(500, {"error": str(e)})
|
|
332
|
+
|
|
333
|
+
def _handle_ollama_generate(self):
|
|
334
|
+
"""Handle Ollama-compatible generation requests"""
|
|
335
|
+
try:
|
|
336
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
337
|
+
post_data = self.rfile.read(content_length)
|
|
338
|
+
request_data = json.loads(post_data.decode("utf-8"))
|
|
339
|
+
|
|
340
|
+
model_name = request_data.get("model", "")
|
|
341
|
+
prompt = request_data.get("prompt", "")
|
|
342
|
+
|
|
343
|
+
if not model_name:
|
|
344
|
+
self._send_response(400, {"error": "No model specified"})
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
if not prompt:
|
|
348
|
+
self._send_response(400, {"error": "No prompt provided"})
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
352
|
+
|
|
353
|
+
# If no models loaded, try to auto-load the requested model
|
|
354
|
+
if (
|
|
355
|
+
not loaded_models
|
|
356
|
+
and model_name in LIGHTWEIGHT_MODELS
|
|
357
|
+
and self.server_instance
|
|
358
|
+
):
|
|
359
|
+
print(f"Auto-loading model: {model_name}")
|
|
360
|
+
try:
|
|
361
|
+
success = self.server_instance.download_and_load_model(model_name)
|
|
362
|
+
if success:
|
|
363
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
364
|
+
except Exception as e:
|
|
365
|
+
print(f"Failed to auto-load model: {e}")
|
|
366
|
+
|
|
367
|
+
# Try to find the model (exact match or partial match)
|
|
368
|
+
available_model = None
|
|
369
|
+
for loaded_model in loaded_models.keys():
|
|
370
|
+
if model_name == loaded_model or model_name in loaded_model:
|
|
371
|
+
available_model = loaded_model
|
|
372
|
+
break
|
|
373
|
+
|
|
374
|
+
if not available_model:
|
|
375
|
+
# Use the first available model as fallback
|
|
376
|
+
if loaded_models:
|
|
377
|
+
available_model = list(loaded_models.keys())[0]
|
|
378
|
+
else:
|
|
379
|
+
self._send_response(
|
|
380
|
+
404,
|
|
381
|
+
{
|
|
382
|
+
"error": f"Model '{model_name}' not found and no models loaded. Available models: {list(LIGHTWEIGHT_MODELS.keys())}"
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Generate an intelligent response based on the prompt
|
|
388
|
+
response_text = self._generate_response(prompt, available_model)
|
|
389
|
+
|
|
390
|
+
# Send Ollama-compatible response
|
|
391
|
+
response = {
|
|
392
|
+
"model": available_model,
|
|
393
|
+
"created_at": "2025-01-01T00:00:00.000Z",
|
|
394
|
+
"response": response_text,
|
|
395
|
+
"done": True,
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
self._send_response(200, response)
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
self._send_response(500, {"error": str(e)})
|
|
402
|
+
|
|
403
|
+
def _handle_ollama_tags(self):
|
|
404
|
+
"""Handle Ollama-compatible model listing requests"""
|
|
405
|
+
try:
|
|
406
|
+
loaded_models = getattr(self.server_instance, "loaded_models", {})
|
|
407
|
+
|
|
408
|
+
models = []
|
|
409
|
+
for model_name, model_info in loaded_models.items():
|
|
410
|
+
models.append(
|
|
411
|
+
{
|
|
412
|
+
"name": model_name,
|
|
413
|
+
"model": model_name,
|
|
414
|
+
"modified_at": "2025-01-01T00:00:00.000Z",
|
|
415
|
+
"size": model_info.get("size_mb", 0)
|
|
416
|
+
* 1024
|
|
417
|
+
* 1024, # Convert to bytes
|
|
418
|
+
"digest": f"sha256:{'0' * 64}", # Placeholder digest
|
|
419
|
+
"details": {
|
|
420
|
+
"parent_model": "",
|
|
421
|
+
"format": "gguf",
|
|
422
|
+
"family": "bert",
|
|
423
|
+
"families": ["bert"],
|
|
424
|
+
"parameter_size": model_info.get("parameters", "0M"),
|
|
425
|
+
"quantization_level": "Q8_0",
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
response = {"models": models}
|
|
431
|
+
self._send_response(200, response)
|
|
432
|
+
|
|
433
|
+
except Exception as e:
|
|
434
|
+
self._send_response(500, {"error": str(e)})
|
|
435
|
+
|
|
436
|
+
def _generate_response(self, prompt: str, model_name: str) -> str:
|
|
437
|
+
"""Generate a response based on the prompt and model"""
|
|
438
|
+
# For now, provide intelligent responses based on prompt analysis
|
|
439
|
+
prompt_lower = prompt.lower()
|
|
440
|
+
|
|
441
|
+
# System information requests
|
|
442
|
+
if any(
|
|
443
|
+
keyword in prompt_lower
|
|
444
|
+
for keyword in ["system", "memory", "ram", "disk", "space", "time"]
|
|
445
|
+
):
|
|
446
|
+
return "I'm a lightweight AI assistant running locally. I can help you with system tasks, command management, and general assistance. What would you like to know or do?"
|
|
447
|
+
|
|
448
|
+
# Command-related requests
|
|
449
|
+
elif any(
|
|
450
|
+
keyword in prompt_lower for keyword in ["command", "mcli", "list", "help"]
|
|
451
|
+
):
|
|
452
|
+
return "I can help you discover and manage MCLI commands. Try asking me to list commands, create new ones, or execute existing functionality. I'm running locally for privacy and speed."
|
|
453
|
+
|
|
454
|
+
# General assistance
|
|
455
|
+
elif any(
|
|
456
|
+
keyword in prompt_lower for keyword in ["hello", "hi", "help", "how are you"]
|
|
457
|
+
):
|
|
458
|
+
return f"Hello! I'm your local AI assistant powered by the {model_name} model. I'm running entirely on your machine for privacy and speed. I can help you with system tasks, command management, file operations, and more. What can I help you with today?"
|
|
459
|
+
|
|
460
|
+
# Task and productivity requests
|
|
461
|
+
elif any(
|
|
462
|
+
keyword in prompt_lower
|
|
463
|
+
for keyword in ["schedule", "task", "job", "remind", "automation"]
|
|
464
|
+
):
|
|
465
|
+
return "I can help you schedule tasks, set up automation, and manage your workflow. I have job scheduling capabilities and can help with system maintenance, reminders, and recurring tasks. What would you like to automate?"
|
|
466
|
+
|
|
467
|
+
# File and system operations
|
|
468
|
+
elif any(
|
|
469
|
+
keyword in prompt_lower
|
|
470
|
+
for keyword in ["file", "folder", "directory", "ls", "list"]
|
|
471
|
+
):
|
|
472
|
+
return "I can help you with file operations, directory navigation, and system management. I have access to system control functions for managing applications, files, and processes. What file or system operation do you need help with?"
|
|
473
|
+
|
|
474
|
+
# Default response
|
|
475
|
+
else:
|
|
476
|
+
return f"I'm your local AI assistant running the {model_name} model. I can help with system management, command creation, file operations, task scheduling, and general assistance. I'm designed to be helpful while running entirely on your machine for privacy. How can I assist you today?"
|
|
477
|
+
|
|
478
|
+
def _send_response(self, status_code, data):
|
|
479
|
+
"""Send JSON response"""
|
|
480
|
+
self.send_response(status_code)
|
|
481
|
+
self.send_header("Content-Type", "application/json")
|
|
482
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
483
|
+
self.end_headers()
|
|
484
|
+
self.wfile.write(json.dumps(data).encode("utf-8"))
|
|
485
|
+
|
|
486
|
+
# Create custom handler class with server instance
|
|
487
|
+
def create_handler(*args, **kwargs):
|
|
488
|
+
return ModelHandler(*args, server_instance=self, **kwargs)
|
|
489
|
+
|
|
490
|
+
Handler = create_handler
|
|
491
|
+
|
|
492
|
+
try:
|
|
493
|
+
server = HTTPServer(("localhost", self.port), Handler)
|
|
494
|
+
print(f"ā
Server listening on port {self.port}")
|
|
495
|
+
server.serve_forever()
|
|
496
|
+
except OSError as e:
|
|
497
|
+
if e.errno == 48: # Address already in use
|
|
498
|
+
print(f"ā ļø Port {self.port} already in use - server may already be running")
|
|
499
|
+
else:
|
|
500
|
+
print(f"ā Server error: {e}")
|
|
501
|
+
except Exception as e:
|
|
502
|
+
print(f"ā Server error: {e}")
|
|
503
|
+
|
|
504
|
+
def download_and_load_model(self, model_key: str) -> bool:
|
|
505
|
+
"""Download and load a model"""
|
|
506
|
+
try:
|
|
507
|
+
# Download model
|
|
508
|
+
model_path = self.downloader.download_model(model_key)
|
|
509
|
+
if not model_path:
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
# Add to loaded models
|
|
513
|
+
model_info = LIGHTWEIGHT_MODELS[model_key]
|
|
514
|
+
self.loaded_models[model_key] = {
|
|
515
|
+
"path": model_path,
|
|
516
|
+
"type": model_info["model_type"],
|
|
517
|
+
"parameters": model_info["parameters"],
|
|
518
|
+
"size_mb": model_info["size_mb"],
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
print(f"ā
Model {model_key} loaded successfully")
|
|
522
|
+
return True
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
print(f"ā Error loading model {model_key}: {e}")
|
|
526
|
+
return False
|
|
527
|
+
|
|
528
|
+
def list_models(self):
|
|
529
|
+
"""List available and downloaded models"""
|
|
530
|
+
print("\nš Available Lightweight Models:")
|
|
531
|
+
print("=" * 60)
|
|
532
|
+
|
|
533
|
+
for key, info in LIGHTWEIGHT_MODELS.items():
|
|
534
|
+
status = "ā
Downloaded" if key in self.loaded_models else "ā³ Not downloaded"
|
|
535
|
+
print(f"{status} - {info['name']} ({info['parameters']})")
|
|
536
|
+
print(f" Size: {info['size_mb']} MB | Efficiency: {info['efficiency_score']}/10")
|
|
537
|
+
print(f" Type: {info['model_type']} | Tags: {', '.join(info['tags'])}")
|
|
538
|
+
print()
|
|
539
|
+
|
|
540
|
+
def get_system_info(self) -> Dict[str, Any]:
|
|
541
|
+
"""Get system information"""
|
|
542
|
+
import psutil
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
"cpu_count": psutil.cpu_count(),
|
|
546
|
+
"memory_gb": psutil.virtual_memory().total / (1024**3),
|
|
547
|
+
"disk_free_gb": psutil.disk_usage("/").free / (1024**3),
|
|
548
|
+
"models_loaded": len(self.loaded_models),
|
|
549
|
+
"total_models_size_mb": sum(m.get("size_mb", 0) for m in self.loaded_models.values()),
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
def recommend_model(self) -> str:
|
|
553
|
+
"""Recommend the best model based on system capabilities"""
|
|
554
|
+
system_info = self.get_system_info()
|
|
555
|
+
|
|
556
|
+
print("š System Analysis:")
|
|
557
|
+
print(f" CPU Cores: {system_info['cpu_count']}")
|
|
558
|
+
print(f" RAM: {system_info['memory_gb']:.1f} GB")
|
|
559
|
+
print(f" Free Disk: {system_info['disk_free_gb']:.1f} GB")
|
|
560
|
+
|
|
561
|
+
# Simple recommendation logic
|
|
562
|
+
if system_info["memory_gb"] < 2:
|
|
563
|
+
return "prajjwal1/bert-tiny" # Smallest model
|
|
564
|
+
elif system_info["memory_gb"] < 4:
|
|
565
|
+
return "sshleifer/tiny-distilbert-base-uncased" # Tiny model
|
|
566
|
+
else:
|
|
567
|
+
return "distilbert-base-uncased" # Standard small model
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def create_simple_client():
|
|
571
|
+
"""Create a simple client script for testing"""
|
|
572
|
+
client_script = '''#!/usr/bin/env python3
|
|
573
|
+
"""
|
|
574
|
+
Simple client for the lightweight model server
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
import requests
|
|
578
|
+
import json
|
|
579
|
+
|
|
580
|
+
def test_server():
|
|
581
|
+
"""Test the lightweight model server"""
|
|
582
|
+
base_url = "http://localhost:8080"
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
# Check server health
|
|
586
|
+
response = requests.get(f"{base_url}/health")
|
|
587
|
+
if response.status_code == 200:
|
|
588
|
+
print("ā
Server is healthy")
|
|
589
|
+
else:
|
|
590
|
+
print("ā Server health check failed")
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
# List models
|
|
594
|
+
response = requests.get(f"{base_url}/models")
|
|
595
|
+
if response.status_code == 200:
|
|
596
|
+
models = response.json()
|
|
597
|
+
print(f"š Loaded models: {models}")
|
|
598
|
+
else:
|
|
599
|
+
print("ā Failed to get models")
|
|
600
|
+
return
|
|
601
|
+
|
|
602
|
+
# Test generation (if models are loaded)
|
|
603
|
+
if models.get("models"):
|
|
604
|
+
model_name = models["models"][0]["name"]
|
|
605
|
+
response = requests.post(
|
|
606
|
+
f"{base_url}/models/{model_name}/generate",
|
|
607
|
+
json={"prompt": "Hello, how are you?"}
|
|
608
|
+
)
|
|
609
|
+
if response.status_code == 200:
|
|
610
|
+
result = response.json()
|
|
611
|
+
print(f"š¤ Generated: {result.get('generated_text', 'No response')}")
|
|
612
|
+
else:
|
|
613
|
+
print("ā Generation failed")
|
|
614
|
+
else:
|
|
615
|
+
print("ā ļø No models loaded")
|
|
616
|
+
|
|
617
|
+
except requests.exceptions.ConnectionError:
|
|
618
|
+
print("ā Could not connect to server")
|
|
619
|
+
except Exception as e:
|
|
620
|
+
print(f"ā Error: {e}")
|
|
621
|
+
|
|
622
|
+
if __name__ == "__main__":
|
|
623
|
+
test_server()
|
|
624
|
+
'''
|
|
625
|
+
|
|
626
|
+
with open("lightweight_client.py", "w") as f:
|
|
627
|
+
f.write(client_script)
|
|
628
|
+
|
|
629
|
+
# Make executable
|
|
630
|
+
os.chmod("lightweight_client.py", 0o755)
|
|
631
|
+
print("ā
Created lightweight client: lightweight_client.py")
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@click.command()
|
|
635
|
+
@click.option(
|
|
636
|
+
"--model",
|
|
637
|
+
type=click.Choice(list(LIGHTWEIGHT_MODELS.keys())),
|
|
638
|
+
help="Specific model to download and run",
|
|
639
|
+
)
|
|
640
|
+
@click.option(
|
|
641
|
+
"--auto", is_flag=True, default=True, help="Automatically select best model for your system"
|
|
642
|
+
)
|
|
643
|
+
@click.option("--port", default=8080, help="Port to run server on")
|
|
644
|
+
@click.option("--list-models", is_flag=True, help="List available models")
|
|
645
|
+
@click.option("--create-client", is_flag=True, help="Create simple client script")
|
|
646
|
+
@click.option("--download-only", is_flag=True, help="Only download models, don't start server")
|
|
647
|
+
def main(
|
|
648
|
+
model: Optional[str],
|
|
649
|
+
auto: bool,
|
|
650
|
+
port: int,
|
|
651
|
+
list_models: bool,
|
|
652
|
+
create_client: bool,
|
|
653
|
+
download_only: bool,
|
|
654
|
+
):
|
|
655
|
+
"""Lightweight model server for extremely small and efficient models"""
|
|
656
|
+
|
|
657
|
+
print("š MCLI Lightweight Model Server")
|
|
658
|
+
print("=" * 50)
|
|
659
|
+
|
|
660
|
+
# Create server instance
|
|
661
|
+
server = LightweightModelServer(port=port)
|
|
662
|
+
|
|
663
|
+
if list_models:
|
|
664
|
+
server.list_models()
|
|
665
|
+
return 0
|
|
666
|
+
|
|
667
|
+
if create_client:
|
|
668
|
+
create_simple_client()
|
|
669
|
+
return 0
|
|
670
|
+
|
|
671
|
+
# Get system info and recommend model
|
|
672
|
+
if model:
|
|
673
|
+
selected_model = model
|
|
674
|
+
print(f"šÆ Using specified model: {selected_model}")
|
|
675
|
+
elif auto:
|
|
676
|
+
selected_model = server.recommend_model()
|
|
677
|
+
print(f"šÆ Recommended model: {selected_model}")
|
|
678
|
+
else:
|
|
679
|
+
print("Available models:")
|
|
680
|
+
for key, info in LIGHTWEIGHT_MODELS.items():
|
|
681
|
+
print(f" {key}: {info['name']} ({info['parameters']})")
|
|
682
|
+
selected_model = click.prompt(
|
|
683
|
+
"Select model", type=click.Choice(list(LIGHTWEIGHT_MODELS.keys()))
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# Download and load model
|
|
687
|
+
if not server.download_and_load_model(selected_model):
|
|
688
|
+
print("ā Failed to download model")
|
|
689
|
+
return 1
|
|
690
|
+
|
|
691
|
+
if download_only:
|
|
692
|
+
print("ā
Model downloaded successfully")
|
|
693
|
+
return 0
|
|
694
|
+
|
|
695
|
+
# Start server
|
|
696
|
+
print(f"\nš Starting lightweight server on port {port}...")
|
|
697
|
+
server.start_server()
|
|
698
|
+
|
|
699
|
+
print(f"\nš Usage:")
|
|
700
|
+
print(f" - API: http://localhost:{port}")
|
|
701
|
+
print(f" - Health: http://localhost:{port}/health")
|
|
702
|
+
print(f" - Models: http://localhost:{port}/models")
|
|
703
|
+
print(f" - Test: python lightweight_client.py")
|
|
704
|
+
|
|
705
|
+
try:
|
|
706
|
+
# Keep server running
|
|
707
|
+
while True:
|
|
708
|
+
time.sleep(1)
|
|
709
|
+
except KeyboardInterrupt:
|
|
710
|
+
print("\nš Server stopped")
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
if __name__ == "__main__":
|
|
714
|
+
sys.exit(main())
|