Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Henri Bonamy commited on
Commit ·
160da13
1
Parent(s): a82c70a
pass ToolErrors to agent, correct HF token handling, simplify uv job calls
Browse files- agent/core/tools.py +9 -4
- agent/prompts/system_prompt.yaml +1 -0
- agent/tools/jobs_tool.py +65 -16
agent/core/tools.py
CHANGED
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass
|
|
| 8 |
from typing import Any, Awaitable, Callable, Optional
|
| 9 |
|
| 10 |
from fastmcp import Client
|
|
|
|
| 11 |
from lmnr import observe
|
| 12 |
from mcp.types import EmbeddedResource, ImageContent, TextContent
|
| 13 |
|
|
@@ -166,10 +167,14 @@ class ToolRouter:
|
|
| 166 |
|
| 167 |
# Otherwise, use MCP client
|
| 168 |
if self._mcp_initialized:
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
return "MCP client not initialized", False
|
| 175 |
|
|
|
|
| 8 |
from typing import Any, Awaitable, Callable, Optional
|
| 9 |
|
| 10 |
from fastmcp import Client
|
| 11 |
+
from fastmcp.exceptions import ToolError
|
| 12 |
from lmnr import observe
|
| 13 |
from mcp.types import EmbeddedResource, ImageContent, TextContent
|
| 14 |
|
|
|
|
| 167 |
|
| 168 |
# Otherwise, use MCP client
|
| 169 |
if self._mcp_initialized:
|
| 170 |
+
try:
|
| 171 |
+
result = await self.mcp_client.call_tool(tool_name, arguments)
|
| 172 |
+
output = convert_mcp_content_to_string(result.content)
|
| 173 |
+
return output, not result.is_error
|
| 174 |
+
except ToolError as e:
|
| 175 |
+
# Catch MCP tool errors and return them to the agent
|
| 176 |
+
error_msg = f"Tool error: {str(e)}"
|
| 177 |
+
return error_msg, False
|
| 178 |
|
| 179 |
return "MCP client not initialized", False
|
| 180 |
|
agent/prompts/system_prompt.yaml
CHANGED
|
@@ -85,6 +85,7 @@ system_prompt: |
|
|
| 85 |
- Always search Hugging Face Hub for existing resources before suggesting custom implementations
|
| 86 |
- When referencing models, datasets, or papers, include direct links from search results
|
| 87 |
- Never assume a library is available - check documentation first
|
|
|
|
| 88 |
- Follow ML best practices: proper train/val/test splits, reproducibility, evaluation metrics
|
| 89 |
- For training tasks, consider compute requirements and suggest appropriate hardware
|
| 90 |
- Never expose or log API keys, tokens, or secrets
|
|
|
|
| 85 |
- Always search Hugging Face Hub for existing resources before suggesting custom implementations
|
| 86 |
- When referencing models, datasets, or papers, include direct links from search results
|
| 87 |
- Never assume a library is available - check documentation first
|
| 88 |
+
- Before processing any dataset: inspect its actual structure first using the mcp__hf-mcp-server__hub_repo_details tool. Never assume column names: verify them beforehand.
|
| 89 |
- Follow ML best practices: proper train/val/test splits, reproducibility, evaluation metrics
|
| 90 |
- For training tasks, consider compute requirements and suggest appropriate hardware
|
| 91 |
- Never expose or log API keys, tokens, or secrets
|
agent/tools/jobs_tool.py
CHANGED
|
@@ -6,6 +6,7 @@ Refactored to use official huggingface-hub library instead of custom HTTP client
|
|
| 6 |
|
| 7 |
import asyncio
|
| 8 |
import base64
|
|
|
|
| 9 |
from typing import Any, Dict, Literal, Optional
|
| 10 |
|
| 11 |
from huggingface_hub import HfApi
|
|
@@ -60,6 +61,29 @@ OperationType = Literal[
|
|
| 60 |
UV_DEFAULT_IMAGE = "ghcr.io/astral-sh/uv:python3.12-bookworm"
|
| 61 |
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
def _build_uv_command(
|
| 64 |
script: str,
|
| 65 |
with_deps: list[str] | None = None,
|
|
@@ -99,6 +123,20 @@ def _wrap_inline_script(
|
|
| 99 |
return f'echo "{encoded}" | base64 -d | {uv_command_str}'
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
def _resolve_uv_command(
|
| 103 |
script: str,
|
| 104 |
with_deps: list[str] | None = None,
|
|
@@ -332,7 +370,6 @@ Call this tool with:
|
|
| 332 |
**String format (simple cases only):**
|
| 333 |
- Still accepted for backwards compatibility, parsed with POSIX shell semantics
|
| 334 |
- Rejects shell operators and can mis-handle characters such as `&`; switch to arrays when things turn complex
|
| 335 |
-
- `$HF_TOKEN` stays literal—forward it via `secrets: {{ "HF_TOKEN": "$HF_TOKEN" }}`
|
| 336 |
|
| 337 |
### Show command-specific help
|
| 338 |
Call this tool with:
|
|
@@ -344,7 +381,7 @@ Call this tool with:
|
|
| 344 |
|
| 345 |
- Jobs default to non-detached mode (stream logs until completion). Set `detach: true` to return immediately.
|
| 346 |
- Prefer array commands to avoid shell parsing surprises
|
| 347 |
-
- To access private Hub assets,
|
| 348 |
- Before calling a job, think about dependencies (they must be specified), which hardware flavor to run on (choose simplest for task), and whether to include secrets.
|
| 349 |
"""
|
| 350 |
return {"formatted": usage_text, "totalResults": 1, "resultsShared": 1}
|
|
@@ -388,8 +425,8 @@ Call this tool with:
|
|
| 388 |
self.api.run_job,
|
| 389 |
image=args.get("image", "python:3.12"),
|
| 390 |
command=args.get("command"),
|
| 391 |
-
env=args.get("env"),
|
| 392 |
-
secrets=args.get("secrets"),
|
| 393 |
flavor=args.get("flavor", "cpu-basic"),
|
| 394 |
timeout=args.get("timeout", "30m"),
|
| 395 |
namespace=args.get("namespace") or self.namespace,
|
|
@@ -441,12 +478,18 @@ To inspect, call this tool with `{{"operation": "inspect", "args": {{"job_id": "
|
|
| 441 |
if not script:
|
| 442 |
raise ValueError("script is required")
|
| 443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
# Resolve the command based on script type (URL, inline, or file)
|
| 445 |
command = _resolve_uv_command(
|
| 446 |
script=script,
|
| 447 |
-
with_deps=
|
| 448 |
-
or args.get("dependencies")
|
| 449 |
-
or args.get("packages"),
|
| 450 |
python=args.get("python"),
|
| 451 |
script_args=args.get("script_args"),
|
| 452 |
)
|
|
@@ -456,8 +499,8 @@ To inspect, call this tool with `{{"operation": "inspect", "args": {{"job_id": "
|
|
| 456 |
self.api.run_job,
|
| 457 |
image=UV_DEFAULT_IMAGE,
|
| 458 |
command=command,
|
| 459 |
-
env=args.get("env"),
|
| 460 |
-
secrets=args.get("secrets"),
|
| 461 |
flavor=args.get("flavor") or args.get("hardware") or "cpu-basic",
|
| 462 |
timeout=args.get("timeout", "30m"),
|
| 463 |
namespace=args.get("namespace") or self.namespace,
|
|
@@ -645,8 +688,8 @@ To verify, call this tool with `{{"operation": "inspect", "args": {{"job_id": "{
|
|
| 645 |
image=args.get("image", "python:3.12"),
|
| 646 |
command=args.get("command"),
|
| 647 |
schedule=args.get("schedule"),
|
| 648 |
-
env=args.get("env"),
|
| 649 |
-
secrets=args.get("secrets"),
|
| 650 |
flavor=args.get("flavor", "cpu-basic"),
|
| 651 |
timeout=args.get("timeout", "30m"),
|
| 652 |
namespace=args.get("namespace") or self.namespace,
|
|
@@ -680,12 +723,18 @@ To list all, call this tool with `{{"operation": "scheduled ps"}}`"""
|
|
| 680 |
if not schedule:
|
| 681 |
raise ValueError("schedule is required")
|
| 682 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
# Resolve the command based on script type
|
| 684 |
command = _resolve_uv_command(
|
| 685 |
script=script,
|
| 686 |
-
with_deps=
|
| 687 |
-
or args.get("dependencies")
|
| 688 |
-
or args.get("packages"),
|
| 689 |
python=args.get("python"),
|
| 690 |
script_args=args.get("script_args"),
|
| 691 |
)
|
|
@@ -696,8 +745,8 @@ To list all, call this tool with `{{"operation": "scheduled ps"}}`"""
|
|
| 696 |
image=UV_DEFAULT_IMAGE,
|
| 697 |
command=command,
|
| 698 |
schedule=schedule,
|
| 699 |
-
env=args.get("env"),
|
| 700 |
-
secrets=args.get("secrets"),
|
| 701 |
flavor=args.get("flavor") or args.get("hardware") or "cpu-basic",
|
| 702 |
timeout=args.get("timeout", "30m"),
|
| 703 |
namespace=args.get("namespace") or self.namespace,
|
|
|
|
| 6 |
|
| 7 |
import asyncio
|
| 8 |
import base64
|
| 9 |
+
import os
|
| 10 |
from typing import Any, Dict, Literal, Optional
|
| 11 |
|
| 12 |
from huggingface_hub import HfApi
|
|
|
|
| 61 |
UV_DEFAULT_IMAGE = "ghcr.io/astral-sh/uv:python3.12-bookworm"
|
| 62 |
|
| 63 |
|
| 64 |
+
def _substitute_hf_token(params: Dict[str, Any] | None) -> Dict[str, Any] | None:
|
| 65 |
+
"""
|
| 66 |
+
Substitute $HF_TOKEN with actual token value from environment.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
params: Dictionary that may contain "$HF_TOKEN" in values
|
| 70 |
+
|
| 71 |
+
Returns:
|
| 72 |
+
Dictionary with $HF_TOKEN substituted
|
| 73 |
+
"""
|
| 74 |
+
if params is None:
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
result = {}
|
| 78 |
+
for key, value in params.items():
|
| 79 |
+
if value == "$HF_TOKEN":
|
| 80 |
+
result[key] = os.environ.get("HF_TOKEN", "")
|
| 81 |
+
else:
|
| 82 |
+
result[key] = value
|
| 83 |
+
|
| 84 |
+
return result
|
| 85 |
+
|
| 86 |
+
|
| 87 |
def _build_uv_command(
|
| 88 |
script: str,
|
| 89 |
with_deps: list[str] | None = None,
|
|
|
|
| 123 |
return f'echo "{encoded}" | base64 -d | {uv_command_str}'
|
| 124 |
|
| 125 |
|
| 126 |
+
def _ensure_hf_transfer_dependency(deps: list[str] | None) -> list[str]:
|
| 127 |
+
"""Ensure hf-transfer is included in the dependencies list"""
|
| 128 |
+
if deps is None:
|
| 129 |
+
return ["hf-transfer"]
|
| 130 |
+
|
| 131 |
+
if isinstance(deps, list):
|
| 132 |
+
deps_copy = deps.copy() # Don't modify the original
|
| 133 |
+
if "hf-transfer" not in deps_copy:
|
| 134 |
+
deps_copy.append("hf-transfer")
|
| 135 |
+
return deps_copy
|
| 136 |
+
|
| 137 |
+
return ["hf-transfer"]
|
| 138 |
+
|
| 139 |
+
|
| 140 |
def _resolve_uv_command(
|
| 141 |
script: str,
|
| 142 |
with_deps: list[str] | None = None,
|
|
|
|
| 370 |
**String format (simple cases only):**
|
| 371 |
- Still accepted for backwards compatibility, parsed with POSIX shell semantics
|
| 372 |
- Rejects shell operators and can mis-handle characters such as `&`; switch to arrays when things turn complex
|
|
|
|
| 373 |
|
| 374 |
### Show command-specific help
|
| 375 |
Call this tool with:
|
|
|
|
| 381 |
|
| 382 |
- Jobs default to non-detached mode (stream logs until completion). Set `detach: true` to return immediately.
|
| 383 |
- Prefer array commands to avoid shell parsing surprises
|
| 384 |
+
- To access private Hub assets (spaces, private models, datasets, collections), pass `secrets: {{ "HF_TOKEN": "$HF_TOKEN" }}`
|
| 385 |
- Before calling a job, think about dependencies (they must be specified), which hardware flavor to run on (choose simplest for task), and whether to include secrets.
|
| 386 |
"""
|
| 387 |
return {"formatted": usage_text, "totalResults": 1, "resultsShared": 1}
|
|
|
|
| 425 |
self.api.run_job,
|
| 426 |
image=args.get("image", "python:3.12"),
|
| 427 |
command=args.get("command"),
|
| 428 |
+
env=_substitute_hf_token(args.get("env")),
|
| 429 |
+
secrets=_substitute_hf_token(args.get("secrets")),
|
| 430 |
flavor=args.get("flavor", "cpu-basic"),
|
| 431 |
timeout=args.get("timeout", "30m"),
|
| 432 |
namespace=args.get("namespace") or self.namespace,
|
|
|
|
| 478 |
if not script:
|
| 479 |
raise ValueError("script is required")
|
| 480 |
|
| 481 |
+
# Get dependencies and ensure hf-transfer is included
|
| 482 |
+
deps = (
|
| 483 |
+
args.get("with_deps")
|
| 484 |
+
or args.get("dependencies")
|
| 485 |
+
or args.get("packages")
|
| 486 |
+
)
|
| 487 |
+
deps = _ensure_hf_transfer_dependency(deps)
|
| 488 |
+
|
| 489 |
# Resolve the command based on script type (URL, inline, or file)
|
| 490 |
command = _resolve_uv_command(
|
| 491 |
script=script,
|
| 492 |
+
with_deps=deps,
|
|
|
|
|
|
|
| 493 |
python=args.get("python"),
|
| 494 |
script_args=args.get("script_args"),
|
| 495 |
)
|
|
|
|
| 499 |
self.api.run_job,
|
| 500 |
image=UV_DEFAULT_IMAGE,
|
| 501 |
command=command,
|
| 502 |
+
env=_substitute_hf_token(args.get("env")),
|
| 503 |
+
secrets=_substitute_hf_token(args.get("secrets")),
|
| 504 |
flavor=args.get("flavor") or args.get("hardware") or "cpu-basic",
|
| 505 |
timeout=args.get("timeout", "30m"),
|
| 506 |
namespace=args.get("namespace") or self.namespace,
|
|
|
|
| 688 |
image=args.get("image", "python:3.12"),
|
| 689 |
command=args.get("command"),
|
| 690 |
schedule=args.get("schedule"),
|
| 691 |
+
env=_substitute_hf_token(args.get("env")),
|
| 692 |
+
secrets=_substitute_hf_token(args.get("secrets")),
|
| 693 |
flavor=args.get("flavor", "cpu-basic"),
|
| 694 |
timeout=args.get("timeout", "30m"),
|
| 695 |
namespace=args.get("namespace") or self.namespace,
|
|
|
|
| 723 |
if not schedule:
|
| 724 |
raise ValueError("schedule is required")
|
| 725 |
|
| 726 |
+
# Get dependencies and ensure hf-transfer is included
|
| 727 |
+
deps = (
|
| 728 |
+
args.get("with_deps")
|
| 729 |
+
or args.get("dependencies")
|
| 730 |
+
or args.get("packages")
|
| 731 |
+
)
|
| 732 |
+
deps = _ensure_hf_transfer_dependency(deps)
|
| 733 |
+
|
| 734 |
# Resolve the command based on script type
|
| 735 |
command = _resolve_uv_command(
|
| 736 |
script=script,
|
| 737 |
+
with_deps=deps,
|
|
|
|
|
|
|
| 738 |
python=args.get("python"),
|
| 739 |
script_args=args.get("script_args"),
|
| 740 |
)
|
|
|
|
| 745 |
image=UV_DEFAULT_IMAGE,
|
| 746 |
command=command,
|
| 747 |
schedule=schedule,
|
| 748 |
+
env=_substitute_hf_token(args.get("env")),
|
| 749 |
+
secrets=_substitute_hf_token(args.get("secrets")),
|
| 750 |
flavor=args.get("flavor") or args.get("hardware") or "cpu-basic",
|
| 751 |
timeout=args.get("timeout", "30m"),
|
| 752 |
namespace=args.get("namespace") or self.namespace,
|