hud-python 0.4.15__py3-none-any.whl → 0.4.16__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 hud-python might be problematic. Click here for more details.
- hud/cli/rl/README.md +243 -0
- hud/cli/rl/__init__.py +82 -0
- hud/cli/rl/init.py +370 -0
- hud/cli/rl/pod.py +491 -0
- hud/cli/rl/ssh.py +288 -0
- hud/cli/rl/train.py +421 -0
- hud/cli/rl/utils.py +165 -0
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.15.dist-info → hud_python-0.4.16.dist-info}/METADATA +1 -1
- {hud_python-0.4.15.dist-info → hud_python-0.4.16.dist-info}/RECORD +14 -7
- {hud_python-0.4.15.dist-info → hud_python-0.4.16.dist-info}/WHEEL +0 -0
- {hud_python-0.4.15.dist-info → hud_python-0.4.16.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.15.dist-info → hud_python-0.4.16.dist-info}/licenses/LICENSE +0 -0
hud/cli/rl/train.py
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""Main RL training command implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from hud.settings import settings
|
|
14
|
+
from hud.utils.design import HUDDesign
|
|
15
|
+
|
|
16
|
+
from .pod import run_prime_training
|
|
17
|
+
from .utils import (
|
|
18
|
+
detect_image_name,
|
|
19
|
+
get_primary_dataset,
|
|
20
|
+
validate_dataset_name,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
design = HUDDesign()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def train_command_wrapper(
|
|
27
|
+
model: str,
|
|
28
|
+
dataset: str | None,
|
|
29
|
+
config: Path | None,
|
|
30
|
+
gpus: str,
|
|
31
|
+
provider: str,
|
|
32
|
+
output_dir: Path,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Wrapper to handle interactive prompts before entering async context."""
|
|
35
|
+
# Pre-flight checks for required environment variables
|
|
36
|
+
design.section_title("🔍 Pre-flight Checks")
|
|
37
|
+
|
|
38
|
+
missing_vars = []
|
|
39
|
+
|
|
40
|
+
# Check HUD API key
|
|
41
|
+
if not settings.api_key:
|
|
42
|
+
missing_vars.append("HUD_API_KEY")
|
|
43
|
+
else:
|
|
44
|
+
design.success("✓ HUD_API_KEY configured")
|
|
45
|
+
|
|
46
|
+
# Check WANDB API key (optional but recommended)
|
|
47
|
+
if not getattr(settings, "wandb_api_key", None):
|
|
48
|
+
design.warning("⚠ WANDB_API_KEY not set (optional but recommended for training metrics)")
|
|
49
|
+
else:
|
|
50
|
+
design.success("✓ WANDB_API_KEY configured")
|
|
51
|
+
|
|
52
|
+
# Check PRIME API key (required for remote training)
|
|
53
|
+
if provider == "prime" and not getattr(settings, "prime_api_key", None):
|
|
54
|
+
missing_vars.append("PRIME_API_KEY")
|
|
55
|
+
elif provider == "prime":
|
|
56
|
+
design.success("✓ PRIME_API_KEY configured")
|
|
57
|
+
|
|
58
|
+
if missing_vars:
|
|
59
|
+
design.error(f"Missing required environment variables: {', '.join(missing_vars)}")
|
|
60
|
+
design.info("")
|
|
61
|
+
design.info("Set them using one of these methods:")
|
|
62
|
+
design.info("1. Environment variables:")
|
|
63
|
+
for var in missing_vars:
|
|
64
|
+
design.command_example(f"export {var}=your-{var.lower().replace('_', '-')}")
|
|
65
|
+
design.info("")
|
|
66
|
+
design.info("2. Create a .env file in your project root:")
|
|
67
|
+
design.command_example(
|
|
68
|
+
"\n".join([f"{var}=your-{var.lower().replace('_', '-')}" for var in missing_vars]),
|
|
69
|
+
"env",
|
|
70
|
+
)
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
|
|
73
|
+
# Check for required components
|
|
74
|
+
missing = check_requirements(config, dataset)
|
|
75
|
+
|
|
76
|
+
# Auto-detect config if not specified and exactly one exists
|
|
77
|
+
if not config and "config" not in missing:
|
|
78
|
+
config_dir = Path("configs")
|
|
79
|
+
if config_dir.exists():
|
|
80
|
+
yaml_files = list(config_dir.glob("*.yaml"))
|
|
81
|
+
if len(yaml_files) == 1:
|
|
82
|
+
config = yaml_files[0]
|
|
83
|
+
design.info(f"Using config: {config}")
|
|
84
|
+
|
|
85
|
+
# Store user choice for pod creation
|
|
86
|
+
auto_create_pod = None
|
|
87
|
+
team_id = None
|
|
88
|
+
|
|
89
|
+
if missing:
|
|
90
|
+
# Handle interactive prompts here
|
|
91
|
+
if "config" in missing:
|
|
92
|
+
if missing["config"] == "multiple":
|
|
93
|
+
# Select from multiple configs
|
|
94
|
+
config_dir = Path("configs")
|
|
95
|
+
yaml_files = list(config_dir.glob("*.yaml"))
|
|
96
|
+
config_names = [f.name for f in yaml_files]
|
|
97
|
+
selected_config = design.select(
|
|
98
|
+
"Multiple config files found. Select one:", config_names
|
|
99
|
+
)
|
|
100
|
+
config = config_dir / selected_config
|
|
101
|
+
else:
|
|
102
|
+
# No config found, offer to generate
|
|
103
|
+
generate_config = design.select(
|
|
104
|
+
"No config file found. Would you like to generate one?",
|
|
105
|
+
["Yes, generate config", "No, I'll create it manually"],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if generate_config == "Yes, generate config":
|
|
109
|
+
design.info("Running 'hud rl init' to generate config...")
|
|
110
|
+
design.info("")
|
|
111
|
+
# Import here to avoid circular imports
|
|
112
|
+
from .init import init_command_wrapper
|
|
113
|
+
|
|
114
|
+
init_command_wrapper(".", None, False, False)
|
|
115
|
+
|
|
116
|
+
# Look for generated config
|
|
117
|
+
config_dir = Path("configs")
|
|
118
|
+
if config_dir.exists():
|
|
119
|
+
yaml_files = list(config_dir.glob("*.yaml"))
|
|
120
|
+
if yaml_files:
|
|
121
|
+
config = yaml_files[0]
|
|
122
|
+
design.success(f"Using generated config: {config}")
|
|
123
|
+
else:
|
|
124
|
+
design.error("Config generation failed")
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
else:
|
|
127
|
+
design.info("Please create a config file and try again")
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
|
|
130
|
+
if "dataset" in missing:
|
|
131
|
+
# Check if we have tasks.json
|
|
132
|
+
tasks_file = Path("tasks.json")
|
|
133
|
+
if tasks_file.exists():
|
|
134
|
+
create_dataset = design.select(
|
|
135
|
+
"Found tasks.json. Would you like to upload it as a dataset?",
|
|
136
|
+
["Yes, upload to HuggingFace", "No, I'll handle it manually"],
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if create_dataset == "Yes, upload to HuggingFace":
|
|
140
|
+
dataset_name = typer.prompt("Enter dataset name (e.g., username/dataset-name)")
|
|
141
|
+
|
|
142
|
+
if not validate_dataset_name(dataset_name):
|
|
143
|
+
design.error("Invalid dataset name format. Expected: username/dataset-name")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
|
|
146
|
+
design.info(f"Running 'hud hf tasks.json --name {dataset_name}'...")
|
|
147
|
+
design.info("")
|
|
148
|
+
|
|
149
|
+
# Run hf command
|
|
150
|
+
result = subprocess.run( # noqa: S603
|
|
151
|
+
["hud", "hf", "tasks.json", "--name", dataset_name], # noqa: S607
|
|
152
|
+
capture_output=True,
|
|
153
|
+
text=True,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if result.returncode == 0:
|
|
157
|
+
design.success("Dataset uploaded successfully")
|
|
158
|
+
dataset = dataset_name
|
|
159
|
+
else:
|
|
160
|
+
design.error("Failed to upload dataset")
|
|
161
|
+
if result.stderr:
|
|
162
|
+
design.error(result.stderr)
|
|
163
|
+
raise typer.Exit(1)
|
|
164
|
+
else:
|
|
165
|
+
design.info("Please specify a dataset with --dataset")
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
else:
|
|
168
|
+
design.error("No dataset specified and no tasks.json found")
|
|
169
|
+
design.info("Use --dataset to specify a HuggingFace dataset")
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
|
|
172
|
+
# Ask about pod creation for Prime training
|
|
173
|
+
if provider == "prime":
|
|
174
|
+
# Check if team ID is globally configured
|
|
175
|
+
team_check = subprocess.run(
|
|
176
|
+
["prime", "config", "view"], # noqa: S607
|
|
177
|
+
capture_output=True,
|
|
178
|
+
text=True,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
has_global_team = False
|
|
182
|
+
if team_check.returncode == 0:
|
|
183
|
+
# Parse the table output - look for Team ID row
|
|
184
|
+
for line in team_check.stdout.split("\n"):
|
|
185
|
+
if "team id" in line.lower():
|
|
186
|
+
# Check if there's a value after the | separator
|
|
187
|
+
parts = line.split("|")
|
|
188
|
+
if len(parts) >= 2:
|
|
189
|
+
# Get the value part and check if it's not empty
|
|
190
|
+
value = parts[1].strip()
|
|
191
|
+
if value and value != "None":
|
|
192
|
+
has_global_team = True
|
|
193
|
+
design.info("Using globally configured team ID")
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
if not has_global_team:
|
|
197
|
+
# Only ask if no global team is configured
|
|
198
|
+
auto_create_pod = design.select(
|
|
199
|
+
"How would you like to create the Prime Intellect pod?",
|
|
200
|
+
["Personal account (automated)", "Team account (enter team ID)"],
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# If team account selected, get the team ID
|
|
204
|
+
if auto_create_pod == "Team account (enter team ID)":
|
|
205
|
+
team_id = typer.prompt("Enter your team ID (e.g., team_abc123def456)")
|
|
206
|
+
|
|
207
|
+
# Save it globally automatically
|
|
208
|
+
subprocess.run(["prime", "config", "set-team-id", team_id]) # noqa: S603, S607
|
|
209
|
+
design.success("Team ID saved globally")
|
|
210
|
+
|
|
211
|
+
auto_create_pod = (
|
|
212
|
+
"Personal account (automated)" # Treat as automated after getting team ID
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Now run the async command
|
|
216
|
+
asyncio.run(
|
|
217
|
+
train_command(
|
|
218
|
+
model=model,
|
|
219
|
+
dataset=dataset,
|
|
220
|
+
config=config,
|
|
221
|
+
gpus=gpus,
|
|
222
|
+
provider=provider,
|
|
223
|
+
output_dir=output_dir,
|
|
224
|
+
auto_create_pod=auto_create_pod,
|
|
225
|
+
team_id=team_id,
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def train_command(
|
|
231
|
+
model: str,
|
|
232
|
+
dataset: str | None,
|
|
233
|
+
config: Path | None,
|
|
234
|
+
gpus: str,
|
|
235
|
+
provider: str,
|
|
236
|
+
output_dir: Path,
|
|
237
|
+
auto_create_pod: str | None = None,
|
|
238
|
+
team_id: str | None = None,
|
|
239
|
+
) -> None:
|
|
240
|
+
"""Run RL training on HUD environments."""
|
|
241
|
+
design.header("🤖 HUD RL Training")
|
|
242
|
+
|
|
243
|
+
# Get environment image
|
|
244
|
+
image = detect_image_name()
|
|
245
|
+
if not image:
|
|
246
|
+
design.error("No environment image found")
|
|
247
|
+
design.hint("Run 'hud build' first or specify with 'hud rl init <image>'")
|
|
248
|
+
raise typer.Exit(1)
|
|
249
|
+
|
|
250
|
+
# Validate dataset has sufficient tasks for training
|
|
251
|
+
dataset_size = None
|
|
252
|
+
if dataset:
|
|
253
|
+
design.info(f"Validating dataset: {dataset}")
|
|
254
|
+
try:
|
|
255
|
+
# Try to load dataset info from HuggingFace
|
|
256
|
+
from datasets import load_dataset_builder
|
|
257
|
+
|
|
258
|
+
ds_builder = load_dataset_builder(dataset)
|
|
259
|
+
ds_info = ds_builder.info
|
|
260
|
+
|
|
261
|
+
# Check split sizes
|
|
262
|
+
train_size = ds_info.splits.get("train", None) if ds_info.splits else None
|
|
263
|
+
if train_size and train_size.num_examples < 4:
|
|
264
|
+
design.error(f"Dataset '{dataset}' has only {train_size.num_examples} tasks")
|
|
265
|
+
design.info("RL training requires at least 4 tasks for proper batching")
|
|
266
|
+
design.hint("Consider adding more tasks or duplicating existing ones")
|
|
267
|
+
raise typer.Exit(1)
|
|
268
|
+
elif train_size:
|
|
269
|
+
dataset_size = train_size.num_examples
|
|
270
|
+
design.success(f"✓ Dataset has {dataset_size} tasks")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
# If we can't validate, warn but continue
|
|
273
|
+
design.warning(f"Could not validate dataset size: {e}")
|
|
274
|
+
design.info("Proceeding with training - ensure dataset has at least 4 tasks")
|
|
275
|
+
|
|
276
|
+
# Use dataset from command or lock file
|
|
277
|
+
if not dataset:
|
|
278
|
+
dataset = get_primary_dataset()
|
|
279
|
+
if dataset:
|
|
280
|
+
design.info(f"Using dataset from lock file: {dataset}")
|
|
281
|
+
|
|
282
|
+
# Display configuration
|
|
283
|
+
design.section_title("📋 Training Configuration")
|
|
284
|
+
design.json_config(
|
|
285
|
+
json.dumps(
|
|
286
|
+
{
|
|
287
|
+
"Model": model,
|
|
288
|
+
"Dataset": dataset,
|
|
289
|
+
"Config": str(config) if config else None,
|
|
290
|
+
"Environment": image,
|
|
291
|
+
"GPUs": gpus,
|
|
292
|
+
"Provider": provider,
|
|
293
|
+
"Output": str(output_dir),
|
|
294
|
+
},
|
|
295
|
+
indent=2,
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
if not config:
|
|
300
|
+
design.error("No config file found")
|
|
301
|
+
design.hint("Run 'hud rl init' to generate a config file")
|
|
302
|
+
raise typer.Exit(1)
|
|
303
|
+
|
|
304
|
+
if not dataset:
|
|
305
|
+
design.error("No dataset found")
|
|
306
|
+
design.hint("Run 'hud hf tasks.json' to create a dataset")
|
|
307
|
+
raise typer.Exit(1)
|
|
308
|
+
|
|
309
|
+
# Always run remote training
|
|
310
|
+
await run_remote_training(
|
|
311
|
+
model=model,
|
|
312
|
+
dataset=dataset,
|
|
313
|
+
config=config,
|
|
314
|
+
gpus=gpus,
|
|
315
|
+
provider=provider,
|
|
316
|
+
output_dir=output_dir,
|
|
317
|
+
image=image,
|
|
318
|
+
auto_create_pod=auto_create_pod,
|
|
319
|
+
team_id=team_id,
|
|
320
|
+
dataset_size=dataset_size,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def check_requirements(config: Path | None, dataset: str | None) -> dict[str, Any]:
|
|
325
|
+
"""Check if required components are present."""
|
|
326
|
+
missing = {}
|
|
327
|
+
|
|
328
|
+
# Check config
|
|
329
|
+
if not config:
|
|
330
|
+
config_dir = Path("configs")
|
|
331
|
+
if config_dir.exists():
|
|
332
|
+
yaml_files = list(config_dir.glob("*.yaml"))
|
|
333
|
+
if not yaml_files:
|
|
334
|
+
missing["config"] = "none"
|
|
335
|
+
elif len(yaml_files) > 1:
|
|
336
|
+
missing["config"] = "multiple"
|
|
337
|
+
# If exactly one config, we'll use it
|
|
338
|
+
else:
|
|
339
|
+
missing["config"] = "none"
|
|
340
|
+
|
|
341
|
+
# Check dataset
|
|
342
|
+
if not dataset:
|
|
343
|
+
# Check lock file for dataset
|
|
344
|
+
primary_dataset = get_primary_dataset()
|
|
345
|
+
if not primary_dataset:
|
|
346
|
+
missing["dataset"] = "none"
|
|
347
|
+
|
|
348
|
+
return missing
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def generate_config_interactive() -> Path | None:
|
|
352
|
+
"""Generate config interactively and return the path."""
|
|
353
|
+
from .init import init_command
|
|
354
|
+
|
|
355
|
+
# Run init command
|
|
356
|
+
asyncio.run(init_command(".", None, False, False))
|
|
357
|
+
|
|
358
|
+
# Look for generated config
|
|
359
|
+
config_dir = Path("configs")
|
|
360
|
+
if config_dir.exists():
|
|
361
|
+
yaml_files = list(config_dir.glob("*.yaml"))
|
|
362
|
+
if yaml_files:
|
|
363
|
+
return yaml_files[0]
|
|
364
|
+
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def create_dataset_interactive() -> str | None:
|
|
369
|
+
"""Create dataset interactively and return the name."""
|
|
370
|
+
# Check if tasks.json exists
|
|
371
|
+
tasks_file = Path("tasks.json")
|
|
372
|
+
if not tasks_file.exists():
|
|
373
|
+
design.error("No tasks.json file found")
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
# Prompt for dataset name
|
|
377
|
+
dataset_name = typer.prompt("Enter HuggingFace dataset name (e.g., username/dataset-name)")
|
|
378
|
+
|
|
379
|
+
if not validate_dataset_name(dataset_name):
|
|
380
|
+
design.error("Invalid dataset name format")
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
# Run hf command
|
|
384
|
+
result = subprocess.run( # noqa: S603
|
|
385
|
+
["hud", "hf", "tasks.json", "--name", dataset_name], # noqa: S607
|
|
386
|
+
capture_output=True,
|
|
387
|
+
text=True,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if result.returncode == 0:
|
|
391
|
+
return dataset_name
|
|
392
|
+
else:
|
|
393
|
+
design.error("Failed to create dataset")
|
|
394
|
+
if result.stderr:
|
|
395
|
+
design.error(result.stderr)
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
async def run_remote_training(
|
|
400
|
+
model: str,
|
|
401
|
+
dataset: str,
|
|
402
|
+
config: Path,
|
|
403
|
+
gpus: str,
|
|
404
|
+
provider: str,
|
|
405
|
+
output_dir: Path,
|
|
406
|
+
image: str,
|
|
407
|
+
auto_create_pod: str | None = None,
|
|
408
|
+
team_id: str | None = None,
|
|
409
|
+
dataset_size: int | None = None,
|
|
410
|
+
) -> None:
|
|
411
|
+
"""Run training on remote infrastructure."""
|
|
412
|
+
design.section_title("🚀 Remote Training")
|
|
413
|
+
|
|
414
|
+
if provider == "prime":
|
|
415
|
+
await run_prime_training(
|
|
416
|
+
model, dataset, config, gpus, output_dir, image, auto_create_pod, team_id, dataset_size
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
design.error(f"Provider '{provider}' not yet supported")
|
|
420
|
+
design.info("Currently supported: prime")
|
|
421
|
+
raise typer.Exit(1)
|
hud/cli/rl/utils.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Shared utilities for RL commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from hud.utils.design import HUDDesign
|
|
12
|
+
|
|
13
|
+
design = HUDDesign()
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def read_lock_file() -> dict[str, Any]:
|
|
19
|
+
"""Read and parse hud.lock.yaml file."""
|
|
20
|
+
lock_file = Path("hud.lock.yaml")
|
|
21
|
+
if not lock_file.exists():
|
|
22
|
+
return {}
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
with open(lock_file) as f:
|
|
26
|
+
return yaml.safe_load(f) or {}
|
|
27
|
+
except Exception as e:
|
|
28
|
+
design.warning(f"Could not read hud.lock.yaml: {e}")
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_lock_file(data: dict[str, Any]) -> bool:
|
|
33
|
+
"""Write data to hud.lock.yaml file."""
|
|
34
|
+
lock_file = Path("hud.lock.yaml")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with open(lock_file, "w") as f:
|
|
38
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
39
|
+
return True
|
|
40
|
+
except Exception as e:
|
|
41
|
+
design.warning(f"Could not write hud.lock.yaml: {e}")
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_mcp_config_from_lock() -> dict[str, Any] | None:
|
|
46
|
+
"""Get MCP configuration from lock file."""
|
|
47
|
+
lock_data = read_lock_file()
|
|
48
|
+
|
|
49
|
+
# Check if there's an image reference
|
|
50
|
+
image = lock_data.get("image")
|
|
51
|
+
if image:
|
|
52
|
+
return {
|
|
53
|
+
"hud": {
|
|
54
|
+
"url": "https://mcp.hud.so/v3/mcp",
|
|
55
|
+
"headers": {"Authorization": "Bearer $HUD_API_KEY", "Mcp-Image": image},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_primary_dataset() -> str | None:
|
|
63
|
+
"""Get primary dataset name from lock file."""
|
|
64
|
+
lock_data = read_lock_file()
|
|
65
|
+
return lock_data.get("primary_dataset", {}).get("name")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_image_from_lock() -> str | None:
|
|
69
|
+
"""Get image name from lock file."""
|
|
70
|
+
lock_data = read_lock_file()
|
|
71
|
+
return lock_data.get("image")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def detect_image_name() -> str | None:
|
|
75
|
+
"""Try to detect image name from various sources."""
|
|
76
|
+
# First check lock file
|
|
77
|
+
image = get_image_from_lock()
|
|
78
|
+
if image:
|
|
79
|
+
return image
|
|
80
|
+
|
|
81
|
+
# Check pyproject.toml
|
|
82
|
+
pyproject = Path("pyproject.toml")
|
|
83
|
+
if pyproject.exists():
|
|
84
|
+
try:
|
|
85
|
+
import tomllib
|
|
86
|
+
|
|
87
|
+
with open(pyproject, "rb") as f:
|
|
88
|
+
data = tomllib.load(f)
|
|
89
|
+
|
|
90
|
+
# Check for hud.image_name
|
|
91
|
+
image = data.get("tool", {}).get("hud", {}).get("image_name")
|
|
92
|
+
if image:
|
|
93
|
+
return image
|
|
94
|
+
|
|
95
|
+
# Use project name
|
|
96
|
+
name = data.get("project", {}).get("name")
|
|
97
|
+
if name:
|
|
98
|
+
return f"{name}:latest"
|
|
99
|
+
except Exception:
|
|
100
|
+
logger.warning("Failed to load pyproject.toml")
|
|
101
|
+
|
|
102
|
+
# Use directory name as last resort
|
|
103
|
+
return f"{Path.cwd().name}:latest"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def validate_dataset_name(name: str) -> bool:
|
|
107
|
+
"""Validate HuggingFace dataset name format."""
|
|
108
|
+
if not name:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
if "/" not in name:
|
|
112
|
+
design.error(f"Invalid dataset name: {name}")
|
|
113
|
+
design.info("Dataset name should be in format: org/dataset")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
parts = name.split("/")
|
|
117
|
+
if len(parts) != 2:
|
|
118
|
+
design.error(f"Invalid dataset name: {name}")
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
org, dataset = parts
|
|
122
|
+
if not org or not dataset:
|
|
123
|
+
design.error(f"Invalid dataset name: {name}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# Check for valid characters (alphanumeric, dash, underscore)
|
|
127
|
+
import re
|
|
128
|
+
|
|
129
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", org) or not re.match(r"^[a-zA-Z0-9_-]+$", dataset):
|
|
130
|
+
design.error(f"Invalid characters in dataset name: {name}")
|
|
131
|
+
design.info("Use only letters, numbers, dashes, and underscores")
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def create_tasks_template() -> list[dict[str, Any]]:
|
|
138
|
+
"""Create a template for tasks.json file."""
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
"id": "example-task-001",
|
|
142
|
+
"prompt": "Complete the first TODO item in the list",
|
|
143
|
+
"mcp_config": {
|
|
144
|
+
"# TODO": "Add your MCP configuration here",
|
|
145
|
+
"# Example for remote": {
|
|
146
|
+
"hud": {
|
|
147
|
+
"url": "https://mcp.hud.so/v3/mcp",
|
|
148
|
+
"headers": {
|
|
149
|
+
"Authorization": "Bearer $HUD_API_KEY",
|
|
150
|
+
"Mcp-Image": "your-org/your-env:latest",
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"# Example for local": {
|
|
155
|
+
"local": {"command": "docker", "args": ["run", "--rm", "-i", "your-env:latest"]}
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
"setup_tool": {"name": "setup", "arguments": {"name": "todo_seed", "num_items": 5}},
|
|
159
|
+
"evaluate_tool": {
|
|
160
|
+
"name": "evaluate",
|
|
161
|
+
"arguments": {"name": "todo_completed", "expected_count": 1},
|
|
162
|
+
},
|
|
163
|
+
"metadata": {"difficulty": "easy", "category": "task_completion"},
|
|
164
|
+
}
|
|
165
|
+
]
|
hud/utils/tests/test_version.py
CHANGED
hud/version.py
CHANGED
|
@@ -2,7 +2,7 @@ hud/__init__.py,sha256=BjAhZtsHbGN371Q8t3o4v4jltedkmDE85xW0yOILU9g,397
|
|
|
2
2
|
hud/__main__.py,sha256=YR8Dq8OhINOsVfQ55PmRXXg4fEK84Rt_-rMtJ5rvhWo,145
|
|
3
3
|
hud/settings.py,sha256=q9aZiHjvbL4oLE-N8AttTW4rmzS8zPMnsca-iMGyEGc,2362
|
|
4
4
|
hud/types.py,sha256=gNnyS1G7aYHIR5sT3k3bOfSTFnPylUO6lNGLWbjbeYk,5149
|
|
5
|
-
hud/version.py,sha256=
|
|
5
|
+
hud/version.py,sha256=6t66BWvg9Ogi1zfYHDfU7EW-PaxTj515uBQwruhu3X8,105
|
|
6
6
|
hud/agents/__init__.py,sha256=UoIkljWdbq4bM0LD-mSaw6w826EqdEjOk7r6glNYwYQ,286
|
|
7
7
|
hud/agents/base.py,sha256=rbwYP_a6XTwhY_5CaBlE7SWflnTq1EOuDiNY2XeUWdM,28275
|
|
8
8
|
hud/agents/claude.py,sha256=in-dRByL7wU1gsuwFvscltQ1PGZURb2bIeU3QFhfpQU,14271
|
|
@@ -30,6 +30,13 @@ hud/cli/list_func.py,sha256=ENxLL4X5uuqAASWZdQuI0k-tEzmlhUn5LATgz3QPQqQ,7065
|
|
|
30
30
|
hud/cli/pull.py,sha256=JHwCwUwRO0Nzbgm9mkjsz6EpxbxgwQVhgNSY64nNZ-s,11969
|
|
31
31
|
hud/cli/push.py,sha256=4KrEHj0_i3xJNCB3eRjANmHFhSW4MFfpnld3nfVYENs,17904
|
|
32
32
|
hud/cli/remove.py,sha256=USAvB6pbMA3jd19xUtLEBiMsklVTEfE2Maw9nYcpSAE,6619
|
|
33
|
+
hud/cli/rl/README.md,sha256=3pqRZMrnwD-lJwWGCCNZNhGdZG6zyydLBOer0e8BkLw,5983
|
|
34
|
+
hud/cli/rl/__init__.py,sha256=_C95dDud3-YMpqfuRBbW0RyMboPGWzhT7aVcly0n5N8,3074
|
|
35
|
+
hud/cli/rl/init.py,sha256=GXVOXLrX8CVAgpJ1pHuk6Y6oujbh46Rtz8kG18jGzk8,13789
|
|
36
|
+
hud/cli/rl/pod.py,sha256=ARXEG4RZyI1zOrQnsEjLiQDZuM-gv_yNc6JP7-VPRfI,18691
|
|
37
|
+
hud/cli/rl/ssh.py,sha256=4KrdFQP1uv-RUCVe-Sw01SH84ogk9cwts9i0oyaTFSg,10267
|
|
38
|
+
hud/cli/rl/train.py,sha256=40vzQeDrYa9hxI0HwwcFu-zWfhoH2vM8-AUx_JJVHiA,14702
|
|
39
|
+
hud/cli/rl/utils.py,sha256=ZW3sjl5KaHZaOCjAbut_QIpQvxgzlxjPGuM6fuYkU9I,4836
|
|
33
40
|
hud/cli/tests/__init__.py,sha256=ZrGVkmH7DHXGqOvjOSNGZeMYaFIRB2K8c6hwr8FPJ-8,68
|
|
34
41
|
hud/cli/tests/test_analyze.py,sha256=SwxvRlnw-VaEwKN2nd1FJAxfhieujPjh7PdQh_LYJ5E,11050
|
|
35
42
|
hud/cli/tests/test_analyze_metadata.py,sha256=s-N-ETJECloiNQhdnZrfvxDI4pWBI8hl8edQNSRaw14,9982
|
|
@@ -150,10 +157,10 @@ hud/utils/tests/test_init.py,sha256=2QLQSGgyP9wJhOvPCusm_zjJad0qApOZi1BXpxcdHXQ,
|
|
|
150
157
|
hud/utils/tests/test_mcp.py,sha256=0pUa16mL-bqbZDXp5NHBnt1gO5o10BOg7zTMHZ1DNPM,4023
|
|
151
158
|
hud/utils/tests/test_progress.py,sha256=QSF7Kpi03Ff_l3mAeqW9qs1nhK50j9vBiSobZq7T4f4,7394
|
|
152
159
|
hud/utils/tests/test_telemetry.py,sha256=5jl7bEx8C8b-FfFUko5pf4UY-mPOR-9HaeL98dGtVHM,2781
|
|
153
|
-
hud/utils/tests/test_version.py,sha256=
|
|
160
|
+
hud/utils/tests/test_version.py,sha256=M21hhb0gKclB4LA6y_X7tX7vrZqXbiSo91NgKX0wug4,160
|
|
154
161
|
hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
155
|
-
hud_python-0.4.
|
|
156
|
-
hud_python-0.4.
|
|
157
|
-
hud_python-0.4.
|
|
158
|
-
hud_python-0.4.
|
|
159
|
-
hud_python-0.4.
|
|
162
|
+
hud_python-0.4.16.dist-info/METADATA,sha256=0tx5JSLCozbv8youtq3Cp-HuDe0OMHoRA2afl5RrU28,20287
|
|
163
|
+
hud_python-0.4.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
164
|
+
hud_python-0.4.16.dist-info/entry_points.txt,sha256=jJbodNFg1m0-CDofe5AHvB4zKBq7sSdP97-ohaQ3ae4,63
|
|
165
|
+
hud_python-0.4.16.dist-info/licenses/LICENSE,sha256=yIzBheVUf86FC1bztAcr7RYWWNxyd3B-UJQ3uddg1HA,1078
|
|
166
|
+
hud_python-0.4.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|