crewlyze 3.1.0

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.
Files changed (48) hide show
  1. package/.dockerignore +12 -0
  2. package/.gitattributes +2 -0
  3. package/CHANGELOG.md +86 -0
  4. package/Dockerfile +21 -0
  5. package/LICENSE +21 -0
  6. package/README.md +139 -0
  7. package/USAGE.md +106 -0
  8. package/agents/__init__.py +0 -0
  9. package/agents/cleaner.py +38 -0
  10. package/agents/insights.py +44 -0
  11. package/agents/relation.py +36 -0
  12. package/agents/visualizer.py +41 -0
  13. package/assets/badge_crewai.svg +4 -0
  14. package/assets/badge_matplotlib.svg +4 -0
  15. package/assets/badge_ollama.svg +4 -0
  16. package/assets/badge_pandas.svg +4 -0
  17. package/assets/badge_seaborn.svg +4 -0
  18. package/assets/branding_image.png +0 -0
  19. package/assets/complete_workflow.svg +216 -0
  20. package/assets/favicon.png +0 -0
  21. package/assets/logo.png +0 -0
  22. package/assets/stars.svg +12 -0
  23. package/bin/crewlyze.js +79 -0
  24. package/config/README.md +129 -0
  25. package/config/__init__.py +1 -0
  26. package/config/context.py +16 -0
  27. package/config/llm_config.py +300 -0
  28. package/config/metrics_tracker.py +70 -0
  29. package/crew.py +870 -0
  30. package/crewlyze-3.1.0.tgz +0 -0
  31. package/fix_syntax.py +54 -0
  32. package/main.py +1279 -0
  33. package/package.json +22 -0
  34. package/pyproject.toml +32 -0
  35. package/requirements.txt +33 -0
  36. package/tools/__init__.py +0 -0
  37. package/tools/dataset_tools.py +803 -0
  38. package/ui/__init__.py +3 -0
  39. package/ui/copilot.py +200 -0
  40. package/ui/export.py +800 -0
  41. package/update_appjs.py +54 -0
  42. package/update_llm.py +21 -0
  43. package/update_main.py +20 -0
  44. package/web/app.js +3142 -0
  45. package/web/index.html +1105 -0
  46. package/web/style.css +2561 -0
  47. package/workflows/__init__.py +0 -0
  48. package/workflows/pipeline.py +254 -0
@@ -0,0 +1,300 @@
1
+ # Crewlyze
2
+ # Copyright (c) 2025 Sowmiyan S
3
+ # Licensed under the MIT License
4
+
5
+ import os
6
+ import requests
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+ def _load_local_config():
12
+ try:
13
+ from pathlib import Path
14
+ import json
15
+ cfg_path = Path.home() / ".crewlyze" / "config.json"
16
+ if cfg_path.exists():
17
+ with open(cfg_path, "r", encoding="utf-8") as f:
18
+ cfg = json.load(f)
19
+ for k, v in cfg.items():
20
+ if k not in os.environ:
21
+ os.environ[k] = str(v)
22
+ except Exception:
23
+ pass
24
+
25
+ _load_local_config()
26
+
27
+ # NVIDIA NIM OpenAI-compatible endpoint (required for LiteLLM / CrewAI)
28
+ NVIDIA_NIM_BASE_URL = "https://integrate.api.nvidia.com/v1"
29
+
30
+ # Keys accepted by crewai.LLM constructor.
31
+ _LLM_VALID_KEYS = {"model", "api_key", "base_url", "temperature", "max_retries", "timeout"}
32
+
33
+
34
+ def _sync_nvidia_env(api_key: str) -> None:
35
+ """Keep NVIDIA env vars in sync for LiteLLM and direct HTTP clients."""
36
+ if api_key:
37
+ os.environ["NVIDIA_API_KEY"] = api_key
38
+ os.environ["NVIDIA_NIM_API_KEY"] = api_key
39
+
40
+
41
+ def get_llm_config() -> dict:
42
+ """Return the raw provider config dict (may contain extra keys)."""
43
+ from config.context import current_llm_provider, current_llm_api_key
44
+ provider = current_llm_provider.get() or os.getenv("LLM_PROVIDER", "nvidia")
45
+
46
+ configs = {
47
+ "nvidia": {
48
+ "model": "nvidia_nim/meta/llama-3.1-8b-instruct",
49
+ "api_key": current_llm_api_key.get() or os.getenv("NVIDIA_API_KEY"),
50
+ "base_url": NVIDIA_NIM_BASE_URL,
51
+ },
52
+ "minimax": {
53
+ "model": "nvidia_nim/minimaxai/minimax-m3",
54
+ "api_key": current_llm_api_key.get() or os.getenv("NVIDIA_API_KEY"),
55
+ "base_url": NVIDIA_NIM_BASE_URL,
56
+ "max_tokens": 8192,
57
+ "temperature": 1.00,
58
+ "top_p": 0.95,
59
+ "multimodal": True,
60
+ },
61
+ "groq": {
62
+ "model": "groq/llama-3.1-8b-instant",
63
+ "api_key": current_llm_api_key.get() or os.getenv("GROQ_API_KEY"),
64
+ },
65
+ "custom": {
66
+ "model": os.getenv("LLM_MODEL", "custom/model"),
67
+ "api_key": current_llm_api_key.get() or os.getenv("CUSTOM_API_KEY", ""),
68
+ "base_url": os.getenv("CUSTOM_BASE_URL"),
69
+ },
70
+ "openai": {
71
+ "model": "gpt-4o-mini",
72
+ "api_key": current_llm_api_key.get() or os.getenv("OPENAI_API_KEY"),
73
+ },
74
+ "ollama": {
75
+ "model": "ollama/llama3",
76
+ "base_url": current_llm_api_key.get() or os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
77
+ },
78
+ "anthropic": {
79
+ "model": "claude-3-5-sonnet-20241022",
80
+ "api_key": current_llm_api_key.get() or os.getenv("ANTHROPIC_API_KEY"),
81
+ },
82
+ "huggingface": {
83
+ "model": "huggingface/HuggingFaceH4/zephyr-7b-beta",
84
+ "api_key": current_llm_api_key.get() or os.getenv("HUGGINGFACE_API_KEY"),
85
+ },
86
+ "mistral": {
87
+ "model": "mistral/mistral-tiny",
88
+ "api_key": current_llm_api_key.get() or os.getenv("MISTRAL_API_KEY"),
89
+ },
90
+ "gemini": {
91
+ "model": "gemini/gemini-pro",
92
+ "api_key": current_llm_api_key.get() or os.getenv("GEMINI_API_KEY"),
93
+ },
94
+ "cohere": {
95
+ "model": "cohere/command-r-plus",
96
+ "api_key": current_llm_api_key.get() or os.getenv("COHERE_API_KEY"),
97
+ },
98
+ "together": {
99
+ "model": "together_ai/meta-llama/Llama-3-70b-chat-hf",
100
+ "api_key": current_llm_api_key.get() or os.getenv("TOGETHER_API_KEY"),
101
+ },
102
+ "openrouter": {
103
+ "model": "openrouter/google/gemma-2-9b-it",
104
+ "api_key": current_llm_api_key.get() or os.getenv("OPENROUTER_API_KEY"),
105
+ },
106
+ "deepseek": {
107
+ "model": "deepseek/deepseek-chat",
108
+ "api_key": current_llm_api_key.get() or os.getenv("DEEPSEEK_API_KEY"),
109
+ },
110
+ "perplexity": {
111
+ "model": "perplexity/llama-3-sonar-large-32k-chat",
112
+ "api_key": current_llm_api_key.get() or os.getenv("PERPLEXITY_API_KEY"),
113
+ },
114
+ }
115
+
116
+ if provider not in configs:
117
+ from config.context import current_llm_model
118
+ model = current_llm_model.get() or os.getenv("LLM_MODEL") or f"{provider}/default"
119
+ api_key = current_llm_api_key.get() or os.getenv(f"{provider.upper()}_API_KEY") or os.getenv("API_KEY") or ""
120
+ configs[provider] = {
121
+ "model": model,
122
+ "api_key": api_key,
123
+ }
124
+
125
+ config = configs[provider]
126
+
127
+ requires_key = {
128
+ "groq", "openai", "anthropic", "huggingface", "mistral", "gemini", "nvidia", "minimax",
129
+ "cohere", "together", "openrouter", "deepseek", "perplexity", "custom"
130
+ }
131
+ if provider in requires_key and not config.get("api_key"):
132
+ raise ValueError(
133
+ f"{provider.upper()}_API_KEY is not set. "
134
+ "Enter your API key in the sidebar and click Test Connection."
135
+ )
136
+
137
+ if provider in ("nvidia", "minimax") and config.get("api_key"):
138
+ _sync_nvidia_env(config["api_key"])
139
+
140
+ return config
141
+
142
+
143
+ def get_llm_params() -> dict:
144
+ """Return keyword args safe to pass directly to crewai.LLM(**...)."""
145
+ from config.context import current_llm_model
146
+ config = get_llm_config()
147
+ model = current_llm_model.get() or os.getenv("LLM_MODEL") or config["model"]
148
+
149
+ params: dict = {
150
+ "model": model,
151
+ "temperature": config.get("temperature", 0.1),
152
+ "max_retries": 5,
153
+ }
154
+
155
+ if config.get("api_key"):
156
+ params["api_key"] = config["api_key"]
157
+
158
+ if config.get("base_url"):
159
+ params["base_url"] = config["base_url"]
160
+
161
+ return {k: v for k, v in params.items() if k in _LLM_VALID_KEYS}
162
+
163
+
164
+ def apply_runtime_llm_settings(
165
+ provider: str,
166
+ model: str,
167
+ api_key: str = "",
168
+ env_key_name: str = "",
169
+ ) -> None:
170
+ """Inject provider/model/key into context variables before agent execution."""
171
+
172
+ if provider == "custom" and api_key and "|" in api_key:
173
+ parts = api_key.split("|", 1)
174
+ os.environ["CUSTOM_BASE_URL"] = parts[0]
175
+ api_key = parts[1]
176
+
177
+ from config.context import current_llm_provider, current_llm_model, current_llm_api_key, current_llm_env_key_name
178
+ current_llm_provider.set(provider)
179
+ current_llm_model.set(model)
180
+ current_llm_api_key.set(api_key)
181
+ current_llm_env_key_name.set(env_key_name)
182
+
183
+
184
+ def validate_llm_connection(provider: str, model: str, api_key: str = "") -> dict:
185
+ """
186
+ Ping the configured LLM with a minimal prompt.
187
+ Returns {"valid": bool, "message": str}.
188
+ """
189
+
190
+ if provider == "custom" and api_key and "|" in api_key:
191
+ parts = api_key.split("|", 1)
192
+ os.environ["CUSTOM_BASE_URL"] = parts[0]
193
+ api_key = parts[1]
194
+
195
+ if provider == "ollama":
196
+ env_key_name = "OLLAMA_BASE_URL"
197
+ elif provider in ("nvidia", "minimax"):
198
+ env_key_name = "NVIDIA_API_KEY"
199
+ else:
200
+ env_key_name = f"{provider.upper()}_API_KEY"
201
+
202
+ if provider != "ollama" and not api_key.strip():
203
+ return {
204
+ "valid": False,
205
+ "message": f"Please enter your {provider.upper()} API key.",
206
+ }
207
+
208
+ apply_runtime_llm_settings(provider, model, api_key.strip(), env_key_name)
209
+
210
+ # Fast path for NVIDIA: direct HTTP avoids spinning up full CrewAI stack
211
+ if provider in ("nvidia", "minimax"):
212
+ try:
213
+ response = requests.post(
214
+ f"{NVIDIA_NIM_BASE_URL}/chat/completions",
215
+ headers={
216
+ "Authorization": f"Bearer {api_key.strip()}",
217
+ "Content-Type": "application/json",
218
+ },
219
+ json={
220
+ "model": model.replace("nvidia_nim/", "") if model.startswith("nvidia_nim/") else model,
221
+ "messages": [{"role": "user", "content": "Reply with exactly: OK"}],
222
+ "max_tokens": 8,
223
+ "temperature": 0.1,
224
+ },
225
+ timeout=30,
226
+ )
227
+ if response.status_code == 401:
228
+ return {"valid": False, "message": "Invalid NVIDIA API key (401 Unauthorized)."}
229
+ if response.status_code == 404:
230
+ return {
231
+ "valid": False,
232
+ "message": (
233
+ f"Model not found on NVIDIA NIM: {model}. "
234
+ "Try another model from the dropdown."
235
+ ),
236
+ }
237
+ response.raise_for_status()
238
+ data = response.json()
239
+ preview = (
240
+ data.get("choices", [{}])[0]
241
+ .get("message", {})
242
+ .get("content", "OK")
243
+ )
244
+ return {
245
+ "valid": True,
246
+ "message": "NVIDIA NIM connection successful.",
247
+ "preview": str(preview)[:120],
248
+ }
249
+ except requests.RequestException as exc:
250
+ detail = str(exc)
251
+ if hasattr(exc, "response") and exc.response is not None:
252
+ try:
253
+ detail = exc.response.json().get("detail", detail)
254
+ except Exception:
255
+ detail = exc.response.text[:200] or detail
256
+ return {"valid": False, "message": f"NVIDIA API error: {detail}"}
257
+
258
+ try:
259
+ from crewai import LLM
260
+
261
+ llm = LLM(**get_llm_params())
262
+ result = llm.call([{"role": "user", "content": "Reply with exactly: OK"}])
263
+ preview = result if isinstance(result, str) else str(result)
264
+ return {
265
+ "valid": True,
266
+ "message": f"{provider.upper()} connection successful.",
267
+ "preview": preview[:120],
268
+ }
269
+ except Exception as exc:
270
+ return {"valid": False, "message": str(exc)}
271
+
272
+
273
+ def call_minimax_m3(messages: list, stream: bool = False, **kwargs) -> dict:
274
+ """
275
+ Direct HTTP client for MiniMax-M3 via NVIDIA NIM.
276
+ """
277
+ api_key = os.getenv("NVIDIA_API_KEY")
278
+ if not api_key:
279
+ raise ValueError("NVIDIA_API_KEY environment variable is not set.")
280
+
281
+ invoke_url = f"{NVIDIA_NIM_BASE_URL}/chat/completions"
282
+ headers = {
283
+ "Authorization": f"Bearer {api_key}",
284
+ "Accept": "text/event-stream" if stream else "application/json",
285
+ }
286
+ payload = {
287
+ "model": "minimaxai/minimax-m3",
288
+ "messages": messages,
289
+ "max_tokens": kwargs.get("max_tokens", 8192),
290
+ "temperature": kwargs.get("temperature", 1.00),
291
+ "top_p": kwargs.get("top_p", 0.95),
292
+ "stream": stream,
293
+ }
294
+
295
+ response = requests.post(invoke_url, headers=headers, json=payload, stream=stream, timeout=60)
296
+ response.raise_for_status()
297
+
298
+ if stream:
299
+ return response
300
+ return response.json()
@@ -0,0 +1,70 @@
1
+ # Crewlyze Metrics Tracker
2
+ # Copyright (c) 2026 Sowmiyan S
3
+ # Licensed under the MIT License
4
+
5
+ import os
6
+ import json
7
+ import time
8
+ from pathlib import Path
9
+
10
+ def get_metrics_file_path() -> Path:
11
+ user_home = Path.home() / ".crewlyze"
12
+ return Path(os.getenv("CREWLYZE_DATA_DIR", str(user_home / "data"))) / "metrics.json"
13
+
14
+ def log_metric(
15
+ session_id: str,
16
+ dataset_name: str,
17
+ rows: int,
18
+ cols: int,
19
+ stages: dict,
20
+ total_time: float,
21
+ success: bool = True,
22
+ token_usage: int = 0,
23
+ estimated_cost: float = 0.0
24
+ ):
25
+ metrics_path = get_metrics_file_path()
26
+ metrics_path.parent.mkdir(parents=True, exist_ok=True)
27
+
28
+ new_entry = {
29
+ "session_id": session_id,
30
+ "dataset_name": dataset_name,
31
+ "rows": rows,
32
+ "columns": cols,
33
+ "timestamp": time.time() * 1000, # Milliseconds since epoch
34
+ "stages": stages,
35
+ "total_time": total_time,
36
+ "token_usage": token_usage,
37
+ "estimated_cost": estimated_cost,
38
+ "success": success
39
+ }
40
+
41
+ metrics = []
42
+ if metrics_path.exists():
43
+ try:
44
+ with open(metrics_path, "r", encoding="utf-8") as f:
45
+ metrics = json.load(f)
46
+ if not isinstance(metrics, list):
47
+ metrics = []
48
+ except Exception:
49
+ metrics = []
50
+
51
+ metrics.append(new_entry)
52
+
53
+ # Cap to last 100 entries to prevent file from growing indefinitely
54
+ metrics = metrics[-100:]
55
+
56
+ try:
57
+ with open(metrics_path, "w", encoding="utf-8") as f:
58
+ json.dump(metrics, f, indent=2)
59
+ except Exception as e:
60
+ print(f"Failed to save metrics: {e}")
61
+
62
+ def get_metrics() -> list:
63
+ metrics_path = get_metrics_file_path()
64
+ if not metrics_path.exists():
65
+ return []
66
+ try:
67
+ with open(metrics_path, "r", encoding="utf-8") as f:
68
+ return json.load(f)
69
+ except Exception:
70
+ return []