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.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. 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())