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.
- package/.dockerignore +12 -0
- package/.gitattributes +2 -0
- package/CHANGELOG.md +86 -0
- package/Dockerfile +21 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/USAGE.md +106 -0
- package/agents/__init__.py +0 -0
- package/agents/cleaner.py +38 -0
- package/agents/insights.py +44 -0
- package/agents/relation.py +36 -0
- package/agents/visualizer.py +41 -0
- package/assets/badge_crewai.svg +4 -0
- package/assets/badge_matplotlib.svg +4 -0
- package/assets/badge_ollama.svg +4 -0
- package/assets/badge_pandas.svg +4 -0
- package/assets/badge_seaborn.svg +4 -0
- package/assets/branding_image.png +0 -0
- package/assets/complete_workflow.svg +216 -0
- package/assets/favicon.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/stars.svg +12 -0
- package/bin/crewlyze.js +79 -0
- package/config/README.md +129 -0
- package/config/__init__.py +1 -0
- package/config/context.py +16 -0
- package/config/llm_config.py +300 -0
- package/config/metrics_tracker.py +70 -0
- package/crew.py +870 -0
- package/crewlyze-3.1.0.tgz +0 -0
- package/fix_syntax.py +54 -0
- package/main.py +1279 -0
- package/package.json +22 -0
- package/pyproject.toml +32 -0
- package/requirements.txt +33 -0
- package/tools/__init__.py +0 -0
- package/tools/dataset_tools.py +803 -0
- package/ui/__init__.py +3 -0
- package/ui/copilot.py +200 -0
- package/ui/export.py +800 -0
- package/update_appjs.py +54 -0
- package/update_llm.py +21 -0
- package/update_main.py +20 -0
- package/web/app.js +3142 -0
- package/web/index.html +1105 -0
- package/web/style.css +2561 -0
- package/workflows/__init__.py +0 -0
- 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 []
|