hud-python 0.4.57__py3-none-any.whl → 0.4.59__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/agents/__init__.py +2 -0
- hud/agents/gemini.py +492 -0
- hud/agents/tests/test_gemini.py +372 -0
- hud/cli/__init__.py +46 -31
- hud/cli/dev.py +111 -1
- hud/cli/eval.py +59 -3
- hud/cli/flows/dev.py +5 -3
- hud/cli/init.py +14 -18
- hud/cli/push.py +2 -2
- hud/cli/rl/__init__.py +1 -1
- hud/cli/rl/celebrate.py +1 -1
- hud/cli/rl/remote_runner.py +3 -3
- hud/cli/tests/test_eval.py +20 -0
- hud/clients/base.py +1 -1
- hud/clients/fastmcp.py +1 -1
- hud/otel/config.py +1 -1
- hud/otel/context.py +2 -2
- hud/server/server.py +283 -36
- hud/settings.py +6 -0
- hud/shared/hints.py +3 -3
- hud/telemetry/job.py +2 -2
- hud/tools/__init__.py +13 -2
- hud/tools/computer/__init__.py +2 -0
- hud/tools/computer/gemini.py +385 -0
- hud/tools/computer/settings.py +21 -0
- hud/tools/playwright.py +17 -2
- hud/tools/types.py +9 -1
- hud/types.py +2 -1
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.57.dist-info → hud_python-0.4.59.dist-info}/METADATA +2 -1
- {hud_python-0.4.57.dist-info → hud_python-0.4.59.dist-info}/RECORD +35 -32
- {hud_python-0.4.57.dist-info → hud_python-0.4.59.dist-info}/WHEEL +0 -0
- {hud_python-0.4.57.dist-info → hud_python-0.4.59.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.57.dist-info → hud_python-0.4.59.dist-info}/licenses/LICENSE +0 -0
hud/cli/dev.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import contextlib
|
|
6
7
|
import importlib
|
|
7
8
|
import importlib.util
|
|
8
9
|
import logging
|
|
@@ -13,6 +14,8 @@ import threading
|
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
17
|
+
import typer
|
|
18
|
+
|
|
16
19
|
from hud.utils.hud_console import HUDConsole
|
|
17
20
|
|
|
18
21
|
hud_console = HUDConsole()
|
|
@@ -26,6 +29,7 @@ def show_dev_server_info(
|
|
|
26
29
|
interactive: bool,
|
|
27
30
|
env_dir: Path | None = None,
|
|
28
31
|
new: bool = False,
|
|
32
|
+
docker_mode: bool = False,
|
|
29
33
|
) -> str:
|
|
30
34
|
"""Show consistent server info for both Python and Docker modes.
|
|
31
35
|
|
|
@@ -54,7 +58,15 @@ def show_dev_server_info(
|
|
|
54
58
|
if transport == "http":
|
|
55
59
|
hud_console.section_title("Quick Links")
|
|
56
60
|
hud_console.info(f"{hud_console.sym.ITEM} Docs: http://localhost:{port}/docs")
|
|
57
|
-
hud_console.info(f"{hud_console.sym.ITEM} Cursor:
|
|
61
|
+
hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
|
|
62
|
+
# Display the Cursor link on its own line to prevent wrapping
|
|
63
|
+
hud_console.link(cursor_deeplink)
|
|
64
|
+
|
|
65
|
+
# Show eval endpoint if in Docker mode
|
|
66
|
+
if docker_mode:
|
|
67
|
+
hud_console.info(
|
|
68
|
+
f"{hud_console.sym.ITEM} Eval API: http://localhost:{port}/eval (POST)"
|
|
69
|
+
)
|
|
58
70
|
|
|
59
71
|
# Check for VNC (browser environment)
|
|
60
72
|
if env_dir and (env_dir / "environment" / "server.py").exists():
|
|
@@ -510,6 +522,9 @@ def run_docker_dev_server(
|
|
|
510
522
|
new: bool = False,
|
|
511
523
|
) -> None:
|
|
512
524
|
"""Run MCP server in Docker with volume mounts, expose via local HTTP proxy."""
|
|
525
|
+
import atexit
|
|
526
|
+
import signal
|
|
527
|
+
|
|
513
528
|
import typer
|
|
514
529
|
import yaml
|
|
515
530
|
|
|
@@ -522,6 +537,69 @@ def run_docker_dev_server(
|
|
|
522
537
|
|
|
523
538
|
cwd = Path.cwd()
|
|
524
539
|
|
|
540
|
+
# Container name will be set later and used for cleanup
|
|
541
|
+
container_name: str | None = None
|
|
542
|
+
cleanup_done = False
|
|
543
|
+
|
|
544
|
+
def cleanup_container() -> None:
|
|
545
|
+
"""Clean up Docker container on exit."""
|
|
546
|
+
nonlocal cleanup_done
|
|
547
|
+
if cleanup_done or not container_name:
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
cleanup_done = True
|
|
551
|
+
hud_console.debug(f"Cleaning up container: {container_name}")
|
|
552
|
+
|
|
553
|
+
# Check if container is still running
|
|
554
|
+
try:
|
|
555
|
+
result = subprocess.run( # noqa: S603
|
|
556
|
+
["docker", "ps", "-q", "-f", f"name={container_name}"], # noqa: S607
|
|
557
|
+
stdout=subprocess.PIPE,
|
|
558
|
+
stderr=subprocess.DEVNULL,
|
|
559
|
+
text=True,
|
|
560
|
+
timeout=5,
|
|
561
|
+
)
|
|
562
|
+
if not result.stdout.strip():
|
|
563
|
+
# Container is not running, just try to remove it
|
|
564
|
+
subprocess.run( # noqa: S603
|
|
565
|
+
["docker", "rm", "-f", container_name], # noqa: S607
|
|
566
|
+
stdout=subprocess.DEVNULL,
|
|
567
|
+
stderr=subprocess.DEVNULL,
|
|
568
|
+
timeout=5,
|
|
569
|
+
)
|
|
570
|
+
return
|
|
571
|
+
except Exception: # noqa: S110
|
|
572
|
+
pass
|
|
573
|
+
|
|
574
|
+
try:
|
|
575
|
+
# First try to stop gracefully
|
|
576
|
+
subprocess.run( # noqa: S603
|
|
577
|
+
["docker", "stop", container_name], # noqa: S607
|
|
578
|
+
stdout=subprocess.DEVNULL,
|
|
579
|
+
stderr=subprocess.DEVNULL,
|
|
580
|
+
timeout=10,
|
|
581
|
+
)
|
|
582
|
+
hud_console.debug(f"Container {container_name} stopped successfully")
|
|
583
|
+
except subprocess.TimeoutExpired:
|
|
584
|
+
# Force kill if stop times out
|
|
585
|
+
hud_console.debug(f"Container {container_name} stop timeout, forcing kill")
|
|
586
|
+
with contextlib.suppress(Exception):
|
|
587
|
+
subprocess.run( # noqa: S603
|
|
588
|
+
["docker", "kill", container_name], # noqa: S607
|
|
589
|
+
stdout=subprocess.DEVNULL,
|
|
590
|
+
stderr=subprocess.DEVNULL,
|
|
591
|
+
timeout=5,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Set up signal handlers for cleanup
|
|
595
|
+
def signal_handler(signum: int, frame: Any) -> None:
|
|
596
|
+
cleanup_container()
|
|
597
|
+
sys.exit(0)
|
|
598
|
+
|
|
599
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
600
|
+
if sys.platform != "win32":
|
|
601
|
+
signal.signal(signal.SIGHUP, signal_handler)
|
|
602
|
+
|
|
525
603
|
# Find environment directory (current or parent with hud.lock.yaml)
|
|
526
604
|
env_dir = cwd
|
|
527
605
|
lock_path = env_dir / "hud.lock.yaml"
|
|
@@ -562,10 +640,14 @@ def run_docker_dev_server(
|
|
|
562
640
|
base_name = image_name.replace(":", "-").replace("/", "-")
|
|
563
641
|
container_name = f"{base_name}-dev-{pid}"
|
|
564
642
|
|
|
643
|
+
# Register cleanup function with atexit
|
|
644
|
+
atexit.register(cleanup_container)
|
|
645
|
+
|
|
565
646
|
# Build docker run command with volume mounts and folder-mode envs
|
|
566
647
|
from .utils.docker import create_docker_run_command
|
|
567
648
|
|
|
568
649
|
base_args = [
|
|
650
|
+
"--rm", # Automatically remove container when it stops
|
|
569
651
|
"--name",
|
|
570
652
|
container_name,
|
|
571
653
|
"-v",
|
|
@@ -643,6 +725,7 @@ def run_docker_dev_server(
|
|
|
643
725
|
interactive=interactive,
|
|
644
726
|
env_dir=env_dir,
|
|
645
727
|
new=new,
|
|
728
|
+
docker_mode=True,
|
|
646
729
|
)
|
|
647
730
|
hud_console.dim_info(
|
|
648
731
|
"",
|
|
@@ -679,6 +762,11 @@ def run_docker_dev_server(
|
|
|
679
762
|
os.environ["_HUD_DEV_DOCKER_CONTAINER"] = container_name
|
|
680
763
|
hud_console.debug(f"Docker container: {container_name}")
|
|
681
764
|
|
|
765
|
+
# Store the docker mcp_config for the eval endpoint
|
|
766
|
+
import json
|
|
767
|
+
|
|
768
|
+
os.environ["_HUD_DEV_DOCKER_MCP_CONFIG"] = json.dumps(mcp_config)
|
|
769
|
+
|
|
682
770
|
# Create FastMCP proxy using the ProxyClient
|
|
683
771
|
fastmcp_proxy = FastMCP.as_proxy(proxy_client)
|
|
684
772
|
|
|
@@ -713,7 +801,15 @@ def run_docker_dev_server(
|
|
|
713
801
|
asyncio.run(run_proxy())
|
|
714
802
|
except KeyboardInterrupt:
|
|
715
803
|
hud_console.info("\n\nStopping...")
|
|
804
|
+
cleanup_container()
|
|
716
805
|
raise typer.Exit(0) from None
|
|
806
|
+
except Exception:
|
|
807
|
+
# Ensure cleanup happens on any exception
|
|
808
|
+
cleanup_container()
|
|
809
|
+
raise
|
|
810
|
+
finally:
|
|
811
|
+
# Final cleanup attempt
|
|
812
|
+
cleanup_container()
|
|
717
813
|
|
|
718
814
|
|
|
719
815
|
def run_mcp_dev_server(
|
|
@@ -732,6 +828,20 @@ def run_mcp_dev_server(
|
|
|
732
828
|
docker_args = docker_args or []
|
|
733
829
|
cwd = Path.cwd()
|
|
734
830
|
|
|
831
|
+
# Find an available port if not using stdio transport
|
|
832
|
+
if not stdio:
|
|
833
|
+
from hud.cli.utils.logging import find_free_port
|
|
834
|
+
|
|
835
|
+
actual_port = find_free_port(port)
|
|
836
|
+
if actual_port is None:
|
|
837
|
+
hud_console.error(f"No available ports found starting from {port}")
|
|
838
|
+
raise typer.Exit(1)
|
|
839
|
+
|
|
840
|
+
if actual_port != port:
|
|
841
|
+
hud_console.info(f"Port {port} is in use, using port {actual_port} instead")
|
|
842
|
+
|
|
843
|
+
port = actual_port
|
|
844
|
+
|
|
735
845
|
# Auto-detect Docker mode if Dockerfile present and no module specified
|
|
736
846
|
if not docker and module is None and should_use_docker_mode(cwd):
|
|
737
847
|
hud_console.note("Detected Dockerfile - using Docker mode with volume mounts")
|
hud/cli/eval.py
CHANGED
|
@@ -53,7 +53,7 @@ def get_available_models() -> list[dict[str, str | None]]:
|
|
|
53
53
|
try:
|
|
54
54
|
from hud.cli.rl import rl_api
|
|
55
55
|
|
|
56
|
-
hud_console.info("Fetching your models from https://hud.
|
|
56
|
+
hud_console.info("Fetching your models from https://hud.ai/models")
|
|
57
57
|
models = rl_api.list_models()
|
|
58
58
|
|
|
59
59
|
# Filter for ready models only and sort by recency
|
|
@@ -188,6 +188,24 @@ def build_agent(
|
|
|
188
188
|
else:
|
|
189
189
|
return OperatorAgent(verbose=verbose)
|
|
190
190
|
|
|
191
|
+
elif agent_type == AgentType.GEMINI:
|
|
192
|
+
try:
|
|
193
|
+
from hud.agents import GeminiAgent
|
|
194
|
+
except ImportError as e:
|
|
195
|
+
hud_console.error(
|
|
196
|
+
"Gemini agent dependencies are not installed. "
|
|
197
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
198
|
+
)
|
|
199
|
+
raise typer.Exit(1) from e
|
|
200
|
+
|
|
201
|
+
gemini_kwargs: dict[str, Any] = {
|
|
202
|
+
"model": model or "gemini-2.5-computer-use-preview-10-2025",
|
|
203
|
+
"verbose": verbose,
|
|
204
|
+
}
|
|
205
|
+
if allowed_tools:
|
|
206
|
+
gemini_kwargs["allowed_tools"] = allowed_tools
|
|
207
|
+
return GeminiAgent(**gemini_kwargs)
|
|
208
|
+
|
|
191
209
|
elif agent_type == AgentType.LITELLM:
|
|
192
210
|
try:
|
|
193
211
|
from hud.agents.lite_llm import LiteAgent
|
|
@@ -344,6 +362,17 @@ async def run_single_task(
|
|
|
344
362
|
agent_config = {"verbose": verbose}
|
|
345
363
|
if allowed_tools:
|
|
346
364
|
agent_config["allowed_tools"] = allowed_tools
|
|
365
|
+
elif agent_type == AgentType.GEMINI:
|
|
366
|
+
from hud.agents import GeminiAgent
|
|
367
|
+
|
|
368
|
+
agent_class = GeminiAgent
|
|
369
|
+
agent_config = {
|
|
370
|
+
"model": model or "gemini-2.5-computer-use-preview-10-2025",
|
|
371
|
+
"verbose": verbose,
|
|
372
|
+
"validate_api_key": False,
|
|
373
|
+
}
|
|
374
|
+
if allowed_tools:
|
|
375
|
+
agent_config["allowed_tools"] = allowed_tools
|
|
347
376
|
elif agent_type == AgentType.LITELLM:
|
|
348
377
|
from hud.agents.lite_llm import LiteAgent
|
|
349
378
|
|
|
@@ -534,6 +563,26 @@ async def run_full_dataset(
|
|
|
534
563
|
if allowed_tools:
|
|
535
564
|
agent_config["allowed_tools"] = allowed_tools
|
|
536
565
|
|
|
566
|
+
elif agent_type == AgentType.GEMINI:
|
|
567
|
+
try:
|
|
568
|
+
from hud.agents import GeminiAgent
|
|
569
|
+
|
|
570
|
+
agent_class = GeminiAgent
|
|
571
|
+
except ImportError as e:
|
|
572
|
+
hud_console.error(
|
|
573
|
+
"Gemini agent dependencies are not installed. "
|
|
574
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
575
|
+
)
|
|
576
|
+
raise typer.Exit(1) from e
|
|
577
|
+
|
|
578
|
+
agent_config = {
|
|
579
|
+
"model": model or "gemini-2.5-computer-use-preview-10-2025",
|
|
580
|
+
"verbose": verbose,
|
|
581
|
+
"validate_api_key": False,
|
|
582
|
+
}
|
|
583
|
+
if allowed_tools:
|
|
584
|
+
agent_config["allowed_tools"] = allowed_tools
|
|
585
|
+
|
|
537
586
|
elif agent_type == AgentType.LITELLM:
|
|
538
587
|
try:
|
|
539
588
|
from hud.agents.lite_llm import LiteAgent
|
|
@@ -641,7 +690,7 @@ def eval_command(
|
|
|
641
690
|
agent: AgentType = typer.Option( # noqa: B008
|
|
642
691
|
AgentType.CLAUDE,
|
|
643
692
|
"--agent",
|
|
644
|
-
help="Agent backend to use (claude, openai, vllm for local
|
|
693
|
+
help="Agent backend to use (claude, gemini, openai, vllm for local servers, or litellm)",
|
|
645
694
|
),
|
|
646
695
|
model: str | None = typer.Option(
|
|
647
696
|
None,
|
|
@@ -757,6 +806,13 @@ def eval_command(
|
|
|
757
806
|
"Set it in your environment or run: hud set ANTHROPIC_API_KEY=your-key-here"
|
|
758
807
|
)
|
|
759
808
|
raise typer.Exit(1)
|
|
809
|
+
elif agent == AgentType.GEMINI:
|
|
810
|
+
if not settings.gemini_api_key:
|
|
811
|
+
hud_console.error("GEMINI_API_KEY is required for Gemini agent")
|
|
812
|
+
hud_console.info(
|
|
813
|
+
"Set it in your environment or run: hud set GEMINI_API_KEY=your-key-here"
|
|
814
|
+
)
|
|
815
|
+
raise typer.Exit(1)
|
|
760
816
|
elif agent == AgentType.OPENAI and not settings.openai_api_key:
|
|
761
817
|
hud_console.error("OPENAI_API_KEY is required for OpenAI agent")
|
|
762
818
|
hud_console.info("Set it in your environment or run: hud set OPENAI_API_KEY=your-key-here")
|
|
@@ -771,7 +827,7 @@ def eval_command(
|
|
|
771
827
|
# Check for HUD_API_KEY if using HUD services
|
|
772
828
|
if not settings.api_key:
|
|
773
829
|
hud_console.warning("HUD_API_KEY not set. Some features may be limited.")
|
|
774
|
-
hud_console.info("Get your API key at: https://hud.
|
|
830
|
+
hud_console.info("Get your API key at: https://hud.ai")
|
|
775
831
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
776
832
|
|
|
777
833
|
# Parse allowed tools
|
hud/cli/flows/dev.py
CHANGED
|
@@ -47,11 +47,11 @@ async def create_dynamic_trace(
|
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
resp = await make_request("POST", url=url, json=payload, api_key=api_key)
|
|
50
|
-
# New API returns an id; construct the URL as https://hud.
|
|
50
|
+
# New API returns an id; construct the URL as https://hud.ai/trace/{id}
|
|
51
51
|
trace_id = resp.get("id")
|
|
52
52
|
|
|
53
53
|
if isinstance(trace_id, str) and trace_id:
|
|
54
|
-
return trace_id, f"https://hud.
|
|
54
|
+
return trace_id, f"https://hud.ai/trace/{trace_id}"
|
|
55
55
|
return None, None
|
|
56
56
|
except Exception as e:
|
|
57
57
|
# Do not interrupt dev flow
|
|
@@ -114,7 +114,9 @@ def show_dev_ui(
|
|
|
114
114
|
label = "Base image" if is_docker else "Server"
|
|
115
115
|
hud_console.info("")
|
|
116
116
|
hud_console.info(f"{hud_console.sym.ITEM} {label}: {server_name}")
|
|
117
|
-
hud_console.info(f"{hud_console.sym.ITEM} Cursor:
|
|
117
|
+
hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
|
|
118
|
+
# Display the Cursor link on its own line to prevent wrapping
|
|
119
|
+
hud_console.link(cursor_deeplink)
|
|
118
120
|
hud_console.info("")
|
|
119
121
|
hud_console.info(f"{hud_console.sym.SUCCESS} Hot-reload enabled")
|
|
120
122
|
if is_docker:
|
hud/cli/init.py
CHANGED
|
@@ -182,17 +182,17 @@ def create_environment(
|
|
|
182
182
|
|
|
183
183
|
hud_console = HUDConsole()
|
|
184
184
|
|
|
185
|
-
# Determine environment name/target directory
|
|
186
|
-
if name is None:
|
|
187
|
-
current_dir = Path.cwd()
|
|
188
|
-
name = current_dir.name
|
|
189
|
-
target_dir = current_dir
|
|
190
|
-
hud_console.info(f"Using current directory name: {name}")
|
|
191
|
-
else:
|
|
192
|
-
target_dir = Path(directory) / name
|
|
193
|
-
|
|
194
185
|
# Choose preset
|
|
195
186
|
preset_normalized = (preset or "").strip().lower() if preset else _prompt_for_preset()
|
|
187
|
+
|
|
188
|
+
# If no name is provided, use the preset name as the environment name
|
|
189
|
+
if name is None:
|
|
190
|
+
name = preset_normalized
|
|
191
|
+
hud_console.info(f"Using preset name as environment name: {name}")
|
|
192
|
+
|
|
193
|
+
# Always create a new directory based on the name
|
|
194
|
+
target_dir = Path.cwd() / name if directory == "." else Path(directory) / name
|
|
195
|
+
|
|
196
196
|
if preset_normalized not in PRESET_MAP:
|
|
197
197
|
hud_console.warning(
|
|
198
198
|
f"Unknown preset '{preset_normalized}', defaulting to 'blank' "
|
|
@@ -263,14 +263,10 @@ def create_environment(
|
|
|
263
263
|
hud_console.status_item(entry, "added")
|
|
264
264
|
|
|
265
265
|
hud_console.section_title("Next steps")
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
hud_console.command_example(f"cd {target_dir}")
|
|
272
|
-
hud_console.info("\n2. Start development server (with MCP inspector):")
|
|
273
|
-
hud_console.command_example("hud dev --inspector")
|
|
274
|
-
|
|
266
|
+
# Since we now almost always create a new directory, show cd command
|
|
267
|
+
hud_console.info("1. Enter the directory:")
|
|
268
|
+
hud_console.command_example(f"cd {target_dir.name}")
|
|
269
|
+
hud_console.info("\n2. Start development server (with MCP inspector):")
|
|
270
|
+
hud_console.command_example("hud dev --inspector")
|
|
275
271
|
hud_console.info("\n3. Review the README in this preset for specific instructions.")
|
|
276
272
|
hud_console.info("\n4. Customize as needed.")
|
hud/cli/push.py
CHANGED
|
@@ -152,7 +152,7 @@ def push_environment(
|
|
|
152
152
|
hud_console.error("No HUD API key found")
|
|
153
153
|
hud_console.warning("A HUD API key is required to push environments.")
|
|
154
154
|
hud_console.info("\nTo get started:")
|
|
155
|
-
hud_console.info("1. Get your API key at: https://hud.
|
|
155
|
+
hud_console.info("1. Get your API key at: https://hud.ai/settings")
|
|
156
156
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
157
157
|
hud_console.command_example("hud push", "Try again")
|
|
158
158
|
hud_console.info("")
|
|
@@ -440,7 +440,7 @@ def push_environment(
|
|
|
440
440
|
elif response.status_code == 401:
|
|
441
441
|
hud_console.error("Authentication failed")
|
|
442
442
|
hud_console.info("Check your HUD_API_KEY is valid")
|
|
443
|
-
hud_console.info("Get a new key at: https://hud.
|
|
443
|
+
hud_console.info("Get a new key at: https://hud.ai/settings")
|
|
444
444
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
445
445
|
elif response.status_code == 403:
|
|
446
446
|
hud_console.error("Permission denied")
|
hud/cli/rl/__init__.py
CHANGED
|
@@ -25,7 +25,7 @@ def rl_command(
|
|
|
25
25
|
),
|
|
26
26
|
model: str | None = typer.Argument(
|
|
27
27
|
None,
|
|
28
|
-
help="Model to train from https://hud.
|
|
28
|
+
help="Model to train from https://hud.ai/models (default: interactive selection)",
|
|
29
29
|
),
|
|
30
30
|
config_file: Path | None = typer.Option( # noqa: B008
|
|
31
31
|
None,
|
hud/cli/rl/celebrate.py
CHANGED
|
@@ -133,7 +133,7 @@ def show_confetti(console: Console, seconds: float = 2.5) -> None:
|
|
|
133
133
|
"""
|
|
134
134
|
# Show celebratory message first
|
|
135
135
|
console.print(
|
|
136
|
-
"[bold green]🎉 Starting training! See your model on https://hud.
|
|
136
|
+
"[bold green]🎉 Starting training! See your model on https://hud.ai/models[/bold green]"
|
|
137
137
|
)
|
|
138
138
|
time.sleep(0.3) # Brief pause to see the message
|
|
139
139
|
|
hud/cli/rl/remote_runner.py
CHANGED
|
@@ -55,7 +55,7 @@ def ensure_vllm_deployed(
|
|
|
55
55
|
hud_console.info("Waiting for vLLM server to be ready...")
|
|
56
56
|
start_time = time.time()
|
|
57
57
|
with hud_console.progress() as progress:
|
|
58
|
-
progress.update("Checking deployment status (see live status on https://hud.
|
|
58
|
+
progress.update("Checking deployment status (see live status on https://hud.ai/models)")
|
|
59
59
|
while True:
|
|
60
60
|
if time.time() - start_time > timeout:
|
|
61
61
|
hud_console.error("Timeout waiting for vLLM deployment")
|
|
@@ -139,7 +139,7 @@ def run_remote_training(
|
|
|
139
139
|
hud_console.section_title("Model Selection")
|
|
140
140
|
|
|
141
141
|
# Fetch existing models
|
|
142
|
-
hud_console.info("Fetching your models from https://hud.
|
|
142
|
+
hud_console.info("Fetching your models from https://hud.ai/models")
|
|
143
143
|
|
|
144
144
|
try:
|
|
145
145
|
models = rl_api.list_models()
|
|
@@ -312,7 +312,7 @@ def run_remote_training(
|
|
|
312
312
|
# gpu_table.add_column("Price/hr", style="yellow")
|
|
313
313
|
|
|
314
314
|
# for gpu, info in GPU_PRICING.items():
|
|
315
|
-
# gpu_table.add_row(gpu, info["memory"], "see pricing on hud.
|
|
315
|
+
# gpu_table.add_row(gpu, info["memory"], "see pricing on hud.ai")
|
|
316
316
|
|
|
317
317
|
# console.print(gpu_table)
|
|
318
318
|
|
hud/cli/tests/test_eval.py
CHANGED
|
@@ -68,6 +68,26 @@ class TestBuildAgent:
|
|
|
68
68
|
)
|
|
69
69
|
assert result == mock_instance
|
|
70
70
|
|
|
71
|
+
def test_builds_gemini_agent(self) -> None:
|
|
72
|
+
"""Test building a Gemini agent."""
|
|
73
|
+
with patch("hud.agents.GeminiAgent") as mock_runner:
|
|
74
|
+
mock_instance = Mock()
|
|
75
|
+
mock_runner.return_value = mock_instance
|
|
76
|
+
|
|
77
|
+
result = build_agent(
|
|
78
|
+
AgentType.GEMINI,
|
|
79
|
+
model="gemini-test",
|
|
80
|
+
allowed_tools=["gemini_computer"],
|
|
81
|
+
verbose=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
mock_runner.assert_called_once_with(
|
|
85
|
+
model="gemini-test",
|
|
86
|
+
verbose=True,
|
|
87
|
+
allowed_tools=["gemini_computer"],
|
|
88
|
+
)
|
|
89
|
+
assert result == mock_instance
|
|
90
|
+
|
|
71
91
|
|
|
72
92
|
class TestRunSingleTask:
|
|
73
93
|
"""Test the run_single_task function."""
|
hud/clients/base.py
CHANGED
|
@@ -140,7 +140,7 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
140
140
|
raise HudAuthenticationError(
|
|
141
141
|
f'Sending authorization "{headers.get("Authorization", "")}", which may'
|
|
142
142
|
" be incomplete. Ensure HUD_API_KEY environment variable is set or send it"
|
|
143
|
-
" as a header. You can get an API key at https://hud.
|
|
143
|
+
" as a header. You can get an API key at https://hud.ai"
|
|
144
144
|
)
|
|
145
145
|
# Subclasses implement connection
|
|
146
146
|
await self._connect(self._mcp_config)
|
hud/clients/fastmcp.py
CHANGED
|
@@ -95,7 +95,7 @@ class FastMCPHUDClient(BaseHUDClient):
|
|
|
95
95
|
raise RuntimeError(
|
|
96
96
|
"Authentication failed for HUD API. "
|
|
97
97
|
"Please ensure your HUD_API_KEY environment variable is set correctly." # noqa: E501
|
|
98
|
-
"You can get an API key at https://hud.
|
|
98
|
+
"You can get an API key at https://hud.ai"
|
|
99
99
|
) from e
|
|
100
100
|
# Generic 401 error
|
|
101
101
|
raise RuntimeError(
|
hud/otel/config.py
CHANGED
|
@@ -113,7 +113,7 @@ def configure_telemetry(
|
|
|
113
113
|
# Error if no exporters are configured
|
|
114
114
|
raise ValueError(
|
|
115
115
|
"No telemetry backend configured. Either:\n"
|
|
116
|
-
"1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.
|
|
116
|
+
"1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.ai)\n"
|
|
117
117
|
"2. Use enable_otlp=True with configure_telemetry() for alternative backends (e.g., Jaeger)\n" # noqa: E501
|
|
118
118
|
)
|
|
119
119
|
elif not settings.telemetry_enabled:
|
hud/otel/context.py
CHANGED
|
@@ -408,7 +408,7 @@ def _print_trace_url(task_run_id: str) -> None:
|
|
|
408
408
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
409
409
|
return
|
|
410
410
|
|
|
411
|
-
url = f"https://hud.
|
|
411
|
+
url = f"https://hud.ai/trace/{task_run_id}"
|
|
412
412
|
header = "🚀 See your agent live at:"
|
|
413
413
|
|
|
414
414
|
# ANSI color codes
|
|
@@ -447,7 +447,7 @@ def _print_trace_complete_url(task_run_id: str, error_occurred: bool = False) ->
|
|
|
447
447
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
448
448
|
return
|
|
449
449
|
|
|
450
|
-
url = f"https://hud.
|
|
450
|
+
url = f"https://hud.ai/trace/{task_run_id}"
|
|
451
451
|
|
|
452
452
|
# ANSI color codes
|
|
453
453
|
GREEN = "\033[92m"
|