openrunner-sdk 2.24.0__tar.gz → 2.24.2__tar.gz
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.
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/PKG-INFO +1 -1
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/__init__.py +1 -1
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/api_client.py +1 -1
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cli.py +36 -5
- openrunner_sdk-2.24.2/openrunner/integration/skypilot.py +447 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/mcp_server.py +79 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/pyproject.toml +1 -1
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/.gitignore +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/=6.0 +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/=8.1 +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/README.md +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/CLAUDE.md +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/artifact.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/config.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/api_integration.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/cost_guard.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/gpu_types.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/price_compare.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/provider_base.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/lambda_cloud.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/modal_provider.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/ovhcloud.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/runpod.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/vastai.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/workspace_sync.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/hooks/patent-lab-post-commit +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/install_commands.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/media.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/migrate.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/model.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/patent_scan.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/redact.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/run.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/sender.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/session.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tools/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tools/patent_report.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wer.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/conftest.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/conftest.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/test_artifact_round_trip.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_alert.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_artifact.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_cli.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_config.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/__init__.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/conftest.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_cost_guard.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_gpu_types.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_lambda.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_modal.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_ovhcloud.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_price_compare.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_provider_base.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_runpod.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_vastai.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_workspace_sync.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_init.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_sklearn.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_log.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_mcp_predictions.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_media.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_plot.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_system_metrics.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_wandb_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrunner-sdk
|
|
3
|
-
Version: 2.24.
|
|
3
|
+
Version: 2.24.2
|
|
4
4
|
Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
|
|
5
5
|
Project-URL: Homepage, https://github.com/jqueguiner/openrunner
|
|
6
6
|
Project-URL: Repository, https://github.com/jqueguiner/openrunner
|
|
@@ -121,7 +121,7 @@ launch.from_run = _launch_from_run # type: ignore[attr-defined]
|
|
|
121
121
|
# openrunner.trace.patch_openai() syntax
|
|
122
122
|
trace.patch_openai = _patch_openai # type: ignore[attr-defined]
|
|
123
123
|
|
|
124
|
-
__version__ = "2.24.
|
|
124
|
+
__version__ = "2.24.2"
|
|
125
125
|
|
|
126
126
|
logger = logging.getLogger("openrunner")
|
|
127
127
|
|
|
@@ -89,7 +89,7 @@ class APIClient:
|
|
|
89
89
|
|
|
90
90
|
def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
|
|
91
91
|
"""Make an HTTP request with automatic retry on transient failures."""
|
|
92
|
-
return _retry_request(getattr(self._client, method), path, **kwargs)
|
|
92
|
+
return _retry_request(getattr(self._client, method.lower()), path, **kwargs)
|
|
93
93
|
|
|
94
94
|
def create_run(self, data: dict[str, Any]) -> dict[str, Any] | None:
|
|
95
95
|
"""POST /runs -- create a new run on the server.
|
|
@@ -3294,6 +3294,22 @@ def patent_scan(
|
|
|
3294
3294
|
click.echo("Error: --project required or set in .openrunner/settings.json", err=True)
|
|
3295
3295
|
sys.exit(1)
|
|
3296
3296
|
|
|
3297
|
+
# Resolve project name → UUID if needed
|
|
3298
|
+
project_id = project
|
|
3299
|
+
if project and api_key and not dry_run:
|
|
3300
|
+
try:
|
|
3301
|
+
_client = APIClient(api_key=api_key, base_url=base_url)
|
|
3302
|
+
_resp = _client._request("get", "/projects")
|
|
3303
|
+
if _resp.status_code == 200:
|
|
3304
|
+
for p in _resp.json():
|
|
3305
|
+
org = p.get("org_name", "")
|
|
3306
|
+
name = p.get("name", "")
|
|
3307
|
+
if f"{org}/{name}" == project or name == project:
|
|
3308
|
+
project_id = p["id"]
|
|
3309
|
+
break
|
|
3310
|
+
except Exception:
|
|
3311
|
+
pass # fall back to using project as-is
|
|
3312
|
+
|
|
3297
3313
|
# Determine commits to scan
|
|
3298
3314
|
if commit:
|
|
3299
3315
|
commits = [commit]
|
|
@@ -3350,12 +3366,12 @@ def patent_scan(
|
|
|
3350
3366
|
click.echo(f" CPC: {', '.join(la.cpc_classes)}")
|
|
3351
3367
|
|
|
3352
3368
|
# Upload
|
|
3353
|
-
if not dry_run and api_key and
|
|
3369
|
+
if not dry_run and api_key and project_id:
|
|
3354
3370
|
try:
|
|
3355
3371
|
client = APIClient(api_key=api_key, base_url=base_url)
|
|
3356
3372
|
resp = client._request(
|
|
3357
|
-
"
|
|
3358
|
-
f"/projects/{
|
|
3373
|
+
"post",
|
|
3374
|
+
f"/projects/{project_id}/patent-lab/entries",
|
|
3359
3375
|
json=result.to_api_payload(),
|
|
3360
3376
|
)
|
|
3361
3377
|
if resp.status_code == 201:
|
|
@@ -3395,6 +3411,21 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
|
|
|
3395
3411
|
sys.exit(1)
|
|
3396
3412
|
|
|
3397
3413
|
client = APIClient(api_key=api_key, base_url=base_url)
|
|
3414
|
+
|
|
3415
|
+
# Resolve project name → UUID
|
|
3416
|
+
project_id = project
|
|
3417
|
+
try:
|
|
3418
|
+
_resp = client._request("get", "/projects")
|
|
3419
|
+
if _resp.status_code == 200:
|
|
3420
|
+
for p in _resp.json():
|
|
3421
|
+
org = p.get("org_name", "")
|
|
3422
|
+
name = p.get("name", "")
|
|
3423
|
+
if f"{org}/{name}" == project or name == project:
|
|
3424
|
+
project_id = p["id"]
|
|
3425
|
+
break
|
|
3426
|
+
except Exception:
|
|
3427
|
+
pass
|
|
3428
|
+
|
|
3398
3429
|
params: dict = {"limit": limit}
|
|
3399
3430
|
if min_score:
|
|
3400
3431
|
params["min_score"] = min_score
|
|
@@ -3403,8 +3434,8 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
|
|
|
3403
3434
|
|
|
3404
3435
|
try:
|
|
3405
3436
|
resp = client._request(
|
|
3406
|
-
"
|
|
3407
|
-
f"/projects/{
|
|
3437
|
+
"get",
|
|
3438
|
+
f"/projects/{project_id}/patent-lab/entries",
|
|
3408
3439
|
params=params,
|
|
3409
3440
|
)
|
|
3410
3441
|
data = resp.json()
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""SkyPilot integration for OpenRunner.
|
|
2
|
+
|
|
3
|
+
Launch training jobs on any cloud with automatic cost tracking,
|
|
4
|
+
spot recovery awareness, and experiment run linking.
|
|
5
|
+
|
|
6
|
+
SkyPilot (Apache 2.0) provides multi-cloud GPU orchestration.
|
|
7
|
+
This integration wraps sky.launch() to auto-inject OpenRunner
|
|
8
|
+
env vars and map $SKYPILOT_TASK_ID to run IDs for unified
|
|
9
|
+
tracking across spot preemptions.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
import openrunner
|
|
13
|
+
from openrunner.integration.skypilot import launch, managed_job
|
|
14
|
+
|
|
15
|
+
# Launch a training job on cheapest available GPU
|
|
16
|
+
cluster = launch(
|
|
17
|
+
task_yaml="train.yaml",
|
|
18
|
+
project="my-project",
|
|
19
|
+
name="gpt-finetune",
|
|
20
|
+
gpus="A100:4",
|
|
21
|
+
use_spot=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Or use managed jobs (auto-recovery on preemption)
|
|
25
|
+
job = managed_job(
|
|
26
|
+
task_yaml="train.yaml",
|
|
27
|
+
project="my-project",
|
|
28
|
+
name="long-training",
|
|
29
|
+
gpus="A100:8",
|
|
30
|
+
)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import logging
|
|
37
|
+
import os
|
|
38
|
+
import subprocess
|
|
39
|
+
import tempfile
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger("openrunner.skypilot")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_skypilot() -> None:
|
|
47
|
+
"""Verify SkyPilot is installed."""
|
|
48
|
+
try:
|
|
49
|
+
import sky # noqa: F401
|
|
50
|
+
except ImportError:
|
|
51
|
+
raise ImportError(
|
|
52
|
+
"SkyPilot is not installed. Install with: "
|
|
53
|
+
"pip install 'skypilot[aws]' or pip install 'skypilot[gcp]' etc."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_openrunner_envs(project: str | None = None) -> dict[str, str]:
|
|
58
|
+
"""Build env vars to inject into SkyPilot tasks for OpenRunner tracking."""
|
|
59
|
+
from openrunner.settings import SDKSettings
|
|
60
|
+
|
|
61
|
+
settings = SDKSettings()
|
|
62
|
+
envs: dict[str, str] = {}
|
|
63
|
+
|
|
64
|
+
# API key
|
|
65
|
+
if settings.api_key:
|
|
66
|
+
envs["OPENRUNNER_API_KEY"] = settings.api_key
|
|
67
|
+
elif os.environ.get("OPENRUNNER_API_KEY"):
|
|
68
|
+
envs["OPENRUNNER_API_KEY"] = os.environ["OPENRUNNER_API_KEY"]
|
|
69
|
+
|
|
70
|
+
# Base URL
|
|
71
|
+
if settings.base_url:
|
|
72
|
+
envs["OPENRUNNER_BASE_URL"] = settings.base_url
|
|
73
|
+
|
|
74
|
+
# Project
|
|
75
|
+
if project:
|
|
76
|
+
envs["OPENRUNNER_PROJECT"] = project
|
|
77
|
+
elif settings.project:
|
|
78
|
+
envs["OPENRUNNER_PROJECT"] = settings.project
|
|
79
|
+
|
|
80
|
+
return envs
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _inject_envs_into_yaml(yaml_path: str, envs: dict[str, str]) -> str:
|
|
84
|
+
"""Read a SkyPilot YAML, inject envs, return path to modified YAML."""
|
|
85
|
+
import yaml as pyyaml
|
|
86
|
+
|
|
87
|
+
with open(yaml_path) as f:
|
|
88
|
+
task = pyyaml.safe_load(f)
|
|
89
|
+
|
|
90
|
+
existing = task.get("envs", {}) or {}
|
|
91
|
+
existing.update(envs)
|
|
92
|
+
|
|
93
|
+
# Add setup command to install openrunner-sdk if not present
|
|
94
|
+
setup = task.get("setup", "") or ""
|
|
95
|
+
if "openrunner" not in setup:
|
|
96
|
+
setup = "pip install openrunner-sdk > /dev/null 2>&1\n" + setup
|
|
97
|
+
task["setup"] = setup
|
|
98
|
+
|
|
99
|
+
task["envs"] = existing
|
|
100
|
+
|
|
101
|
+
# Write to temp file
|
|
102
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
103
|
+
mode="w", suffix=".yaml", prefix="openrunner-sky-",
|
|
104
|
+
delete=False,
|
|
105
|
+
)
|
|
106
|
+
pyyaml.dump(task, tmp, default_flow_style=False)
|
|
107
|
+
tmp.close()
|
|
108
|
+
return tmp.name
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def launch(
|
|
112
|
+
task_yaml: str,
|
|
113
|
+
*,
|
|
114
|
+
project: str | None = None,
|
|
115
|
+
name: str | None = None,
|
|
116
|
+
cluster: str | None = None,
|
|
117
|
+
gpus: str | None = None,
|
|
118
|
+
use_spot: bool = False,
|
|
119
|
+
idle_minutes: int | None = 10,
|
|
120
|
+
down: bool = False,
|
|
121
|
+
env: dict[str, str] | None = None,
|
|
122
|
+
dryrun: bool = False,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
"""Launch a SkyPilot task with OpenRunner tracking auto-configured.
|
|
125
|
+
|
|
126
|
+
Injects OPENRUNNER_API_KEY, OPENRUNNER_BASE_URL, and OPENRUNNER_PROJECT
|
|
127
|
+
into the task's environment. The training script should use
|
|
128
|
+
$SKYPILOT_TASK_ID as the run name for unified tracking across
|
|
129
|
+
spot recovery attempts.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
task_yaml: Path to SkyPilot task YAML file.
|
|
133
|
+
project: OpenRunner project name (auto-detected if not set).
|
|
134
|
+
name: Cluster name (auto-generated if not set).
|
|
135
|
+
cluster: Existing cluster to reuse.
|
|
136
|
+
gpus: GPU spec override (e.g., "A100:4", "H100:8").
|
|
137
|
+
use_spot: Use spot/preemptible instances.
|
|
138
|
+
idle_minutes: Auto-stop after N idle minutes (None to disable).
|
|
139
|
+
down: Tear down cluster after job finishes.
|
|
140
|
+
env: Additional environment variables.
|
|
141
|
+
dryrun: If True, print the command without executing.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict with cluster_name, status, and job info.
|
|
145
|
+
"""
|
|
146
|
+
_check_skypilot()
|
|
147
|
+
|
|
148
|
+
# Build env vars
|
|
149
|
+
envs = _get_openrunner_envs(project)
|
|
150
|
+
if env:
|
|
151
|
+
envs.update(env)
|
|
152
|
+
|
|
153
|
+
# Add spot recovery hint
|
|
154
|
+
envs["OPENRUNNER_SPOT_RECOVERY"] = "1" if use_spot else "0"
|
|
155
|
+
|
|
156
|
+
# Inject into YAML
|
|
157
|
+
modified_yaml = _inject_envs_into_yaml(task_yaml, envs)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
import sky
|
|
161
|
+
|
|
162
|
+
task = sky.Task.from_yaml(modified_yaml)
|
|
163
|
+
|
|
164
|
+
if gpus:
|
|
165
|
+
task.set_resources(sky.Resources(accelerators=gpus, use_spot=use_spot))
|
|
166
|
+
elif use_spot:
|
|
167
|
+
resources = task.resources
|
|
168
|
+
if resources:
|
|
169
|
+
res = list(resources)[0]
|
|
170
|
+
task.set_resources(res.copy(use_spot=True))
|
|
171
|
+
|
|
172
|
+
if dryrun:
|
|
173
|
+
logger.info("Dryrun: would launch %s with envs: %s", task_yaml, list(envs.keys()))
|
|
174
|
+
return {"status": "dryrun", "yaml": modified_yaml, "envs": list(envs.keys())}
|
|
175
|
+
|
|
176
|
+
cluster_name = cluster or name
|
|
177
|
+
kwargs: dict[str, Any] = {}
|
|
178
|
+
if cluster_name:
|
|
179
|
+
kwargs["cluster_name"] = cluster_name
|
|
180
|
+
if idle_minutes is not None:
|
|
181
|
+
kwargs["idle_minutes_to_autostop"] = idle_minutes
|
|
182
|
+
if down:
|
|
183
|
+
kwargs["down"] = True
|
|
184
|
+
|
|
185
|
+
request_id = sky.launch(task, **kwargs)
|
|
186
|
+
|
|
187
|
+
result = {
|
|
188
|
+
"status": "launched",
|
|
189
|
+
"cluster_name": cluster_name,
|
|
190
|
+
"request_id": str(request_id) if request_id else None,
|
|
191
|
+
"use_spot": use_spot,
|
|
192
|
+
"envs_injected": list(envs.keys()),
|
|
193
|
+
}
|
|
194
|
+
logger.info("SkyPilot launch: %s", result)
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
finally:
|
|
198
|
+
# Cleanup temp file
|
|
199
|
+
try:
|
|
200
|
+
os.unlink(modified_yaml)
|
|
201
|
+
except OSError:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def managed_job(
|
|
206
|
+
task_yaml: str,
|
|
207
|
+
*,
|
|
208
|
+
project: str | None = None,
|
|
209
|
+
name: str | None = None,
|
|
210
|
+
gpus: str | None = None,
|
|
211
|
+
env: dict[str, str] | None = None,
|
|
212
|
+
dryrun: bool = False,
|
|
213
|
+
) -> dict[str, Any]:
|
|
214
|
+
"""Submit a SkyPilot managed job with auto-recovery and OpenRunner tracking.
|
|
215
|
+
|
|
216
|
+
Managed jobs automatically recover from spot preemptions and hardware
|
|
217
|
+
failures. Use $SKYPILOT_TASK_ID in your training script as the
|
|
218
|
+
OpenRunner run name to unify metrics across recovery attempts.
|
|
219
|
+
|
|
220
|
+
Example training script:
|
|
221
|
+
import os, openrunner
|
|
222
|
+
run = openrunner.init(
|
|
223
|
+
project=os.environ.get("OPENRUNNER_PROJECT", "default"),
|
|
224
|
+
name=os.environ.get("SKYPILOT_TASK_ID", "local"),
|
|
225
|
+
resume="allow", # Resume from checkpoint on recovery
|
|
226
|
+
)
|
|
227
|
+
"""
|
|
228
|
+
_check_skypilot()
|
|
229
|
+
|
|
230
|
+
envs = _get_openrunner_envs(project)
|
|
231
|
+
envs["OPENRUNNER_SPOT_RECOVERY"] = "1"
|
|
232
|
+
if env:
|
|
233
|
+
envs.update(env)
|
|
234
|
+
|
|
235
|
+
modified_yaml = _inject_envs_into_yaml(task_yaml, envs)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
import sky
|
|
239
|
+
|
|
240
|
+
task = sky.Task.from_yaml(modified_yaml)
|
|
241
|
+
|
|
242
|
+
if gpus:
|
|
243
|
+
task.set_resources(sky.Resources(accelerators=gpus, use_spot=True))
|
|
244
|
+
|
|
245
|
+
if dryrun:
|
|
246
|
+
logger.info("Dryrun: would submit managed job %s", task_yaml)
|
|
247
|
+
return {"status": "dryrun", "yaml": modified_yaml}
|
|
248
|
+
|
|
249
|
+
kwargs: dict[str, Any] = {}
|
|
250
|
+
if name:
|
|
251
|
+
kwargs["name"] = name
|
|
252
|
+
|
|
253
|
+
request_id = sky.jobs.launch(task, **kwargs)
|
|
254
|
+
|
|
255
|
+
result = {
|
|
256
|
+
"status": "submitted",
|
|
257
|
+
"name": name,
|
|
258
|
+
"request_id": str(request_id) if request_id else None,
|
|
259
|
+
"envs_injected": list(envs.keys()),
|
|
260
|
+
}
|
|
261
|
+
logger.info("SkyPilot managed job: %s", result)
|
|
262
|
+
return result
|
|
263
|
+
|
|
264
|
+
finally:
|
|
265
|
+
try:
|
|
266
|
+
os.unlink(modified_yaml)
|
|
267
|
+
except OSError:
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_cluster_cost(cluster_name: str) -> dict[str, Any] | None:
|
|
272
|
+
"""Query SkyPilot for cluster cost information.
|
|
273
|
+
|
|
274
|
+
Returns estimated cost data from the cluster's lifecycle.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
import sky
|
|
278
|
+
status = sky.status(cluster_names=[cluster_name])
|
|
279
|
+
if not status:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
info = status[0]
|
|
283
|
+
return {
|
|
284
|
+
"cluster_name": cluster_name,
|
|
285
|
+
"cloud": str(info.get("cloud", "")),
|
|
286
|
+
"instance_type": str(info.get("instance_type", "")),
|
|
287
|
+
"accelerators": str(info.get("accelerators", "")),
|
|
288
|
+
"use_spot": info.get("use_spot", False),
|
|
289
|
+
"region": str(info.get("region", "")),
|
|
290
|
+
"status": str(info.get("status", "")),
|
|
291
|
+
"launched_at": str(info.get("launched_at", "")),
|
|
292
|
+
"last_use": str(info.get("last_use", "")),
|
|
293
|
+
}
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.warning("Failed to get cluster cost for %s: %s", cluster_name, e)
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def launch_with_catalog(
|
|
300
|
+
task_yaml: str,
|
|
301
|
+
*,
|
|
302
|
+
org_id: str,
|
|
303
|
+
table: str = "utterances",
|
|
304
|
+
filter: str = "",
|
|
305
|
+
export_format: str = "jsonl",
|
|
306
|
+
limit: int = 0,
|
|
307
|
+
mount_path: str = "/mnt/catalog",
|
|
308
|
+
project: str | None = None,
|
|
309
|
+
gpus: str | None = None,
|
|
310
|
+
use_spot: bool = False,
|
|
311
|
+
env: dict[str, str] | None = None,
|
|
312
|
+
dryrun: bool = False,
|
|
313
|
+
) -> dict[str, Any]:
|
|
314
|
+
"""Launch a SkyPilot task with OpenRunner catalog data auto-mounted.
|
|
315
|
+
|
|
316
|
+
Queries the catalog API for the storage connection, then injects
|
|
317
|
+
file_mounts and catalog env vars into the SkyPilot YAML. The
|
|
318
|
+
training script can access catalog data at mount_path.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
from openrunner.integration.skypilot import launch_with_catalog
|
|
322
|
+
|
|
323
|
+
launch_with_catalog(
|
|
324
|
+
"train.yaml",
|
|
325
|
+
org_id="my-org-uuid",
|
|
326
|
+
table="utterances",
|
|
327
|
+
filter="primary_language = 'en' AND duration_s > 1.0",
|
|
328
|
+
gpus="A100:4",
|
|
329
|
+
use_spot=True,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
The training script should:
|
|
333
|
+
1. Read the manifest at /tmp/manifest.jsonl (exported via CLI)
|
|
334
|
+
2. Or directly access /mnt/catalog/ for raw LanceDB data
|
|
335
|
+
3. Use $SKYPILOT_TASK_ID as the OpenRunner run name
|
|
336
|
+
"""
|
|
337
|
+
_check_skypilot()
|
|
338
|
+
|
|
339
|
+
from openrunner.api_client import APIClient
|
|
340
|
+
from openrunner.settings import SDKSettings
|
|
341
|
+
|
|
342
|
+
settings = SDKSettings()
|
|
343
|
+
client = APIClient(
|
|
344
|
+
base_url=settings.base_url or "https://openrun.gladia.io/api/v1",
|
|
345
|
+
api_key=settings.api_key or "",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Fetch catalog connection info from the API
|
|
349
|
+
try:
|
|
350
|
+
resp = client._session.get(
|
|
351
|
+
f"{client._base_url}/organizations/{org_id}/catalog/status",
|
|
352
|
+
)
|
|
353
|
+
resp.raise_for_status()
|
|
354
|
+
status = resp.json()
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.error("Failed to fetch catalog status: %s", e)
|
|
357
|
+
raise RuntimeError(f"Cannot access catalog for org {org_id}: {e}")
|
|
358
|
+
finally:
|
|
359
|
+
client.close()
|
|
360
|
+
|
|
361
|
+
if not status.get("configured"):
|
|
362
|
+
raise RuntimeError("No catalog storage connection configured for this organization")
|
|
363
|
+
|
|
364
|
+
bucket = status["bucket"]
|
|
365
|
+
prefix = status.get("prefix", "")
|
|
366
|
+
endpoint = status.get("endpoint", "")
|
|
367
|
+
|
|
368
|
+
# Build S3 source for file_mounts
|
|
369
|
+
s3_source = f"s3://{bucket}"
|
|
370
|
+
if prefix:
|
|
371
|
+
s3_source += f"/{prefix}"
|
|
372
|
+
|
|
373
|
+
# Inject catalog env vars + file_mounts into YAML
|
|
374
|
+
import yaml as pyyaml
|
|
375
|
+
|
|
376
|
+
with open(task_yaml) as f:
|
|
377
|
+
task = pyyaml.safe_load(f)
|
|
378
|
+
|
|
379
|
+
# Add file_mounts
|
|
380
|
+
mounts = task.get("file_mounts", {}) or {}
|
|
381
|
+
mounts[mount_path] = {"source": s3_source, "mode": "MOUNT"}
|
|
382
|
+
task["file_mounts"] = mounts
|
|
383
|
+
|
|
384
|
+
# Add catalog env vars
|
|
385
|
+
existing_envs = task.get("envs", {}) or {}
|
|
386
|
+
existing_envs.update({
|
|
387
|
+
"CATALOG_TABLE": table,
|
|
388
|
+
"CATALOG_FILTER": filter,
|
|
389
|
+
"CATALOG_FORMAT": export_format,
|
|
390
|
+
"CATALOG_LIMIT": str(limit) if limit else "0",
|
|
391
|
+
"CATALOG_S3_ENDPOINT": endpoint,
|
|
392
|
+
"CATALOG_S3_BUCKET": bucket,
|
|
393
|
+
"CATALOG_S3_PREFIX": prefix,
|
|
394
|
+
})
|
|
395
|
+
task["envs"] = existing_envs
|
|
396
|
+
|
|
397
|
+
# Write modified YAML
|
|
398
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
399
|
+
mode="w", suffix=".yaml", prefix="openrunner-catalog-sky-",
|
|
400
|
+
delete=False,
|
|
401
|
+
)
|
|
402
|
+
pyyaml.dump(task, tmp, default_flow_style=False)
|
|
403
|
+
tmp.close()
|
|
404
|
+
|
|
405
|
+
# Delegate to regular launch
|
|
406
|
+
try:
|
|
407
|
+
return launch(
|
|
408
|
+
tmp.name,
|
|
409
|
+
project=project,
|
|
410
|
+
gpus=gpus,
|
|
411
|
+
use_spot=use_spot,
|
|
412
|
+
env=env,
|
|
413
|
+
dryrun=dryrun,
|
|
414
|
+
)
|
|
415
|
+
finally:
|
|
416
|
+
try:
|
|
417
|
+
os.unlink(tmp.name)
|
|
418
|
+
except OSError:
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def list_gpu_prices(accelerator: str = "A100") -> list[dict[str, Any]]:
|
|
423
|
+
"""List GPU prices across clouds using SkyPilot's catalog.
|
|
424
|
+
|
|
425
|
+
Returns sorted list of (cloud, instance, region, price) for the
|
|
426
|
+
given accelerator type.
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
import sky
|
|
430
|
+
gpus = sky.show_gpus(accelerator, return_data=True)
|
|
431
|
+
if not gpus:
|
|
432
|
+
return []
|
|
433
|
+
return [
|
|
434
|
+
{
|
|
435
|
+
"cloud": str(row.get("Cloud", "")),
|
|
436
|
+
"instance": str(row.get("Instance", "")),
|
|
437
|
+
"accelerator": str(row.get("Accelerator", "")),
|
|
438
|
+
"count": row.get("Count", 1),
|
|
439
|
+
"region": str(row.get("Region", "")),
|
|
440
|
+
"price_per_hour": row.get("Price", 0.0),
|
|
441
|
+
"spot_price": row.get("Spot Price", None),
|
|
442
|
+
}
|
|
443
|
+
for row in gpus
|
|
444
|
+
]
|
|
445
|
+
except Exception as e:
|
|
446
|
+
logger.warning("Failed to list GPU prices: %s", e)
|
|
447
|
+
return []
|
|
@@ -517,6 +517,41 @@ def create_server():
|
|
|
517
517
|
"required": ["idea"],
|
|
518
518
|
},
|
|
519
519
|
),
|
|
520
|
+
Tool(
|
|
521
|
+
name="openrunner_patent_agent",
|
|
522
|
+
description=(
|
|
523
|
+
"Run a Claude Code agent on a patent lab entry SERVER-SIDE. "
|
|
524
|
+
"The server spawns a Claude Code agent with patent-creator MCP tools "
|
|
525
|
+
"to perform deep patent analysis. Tasks: analyze (patentability + prior art), "
|
|
526
|
+
"draft-claims, prior-art (comprehensive search), full-disclosure, "
|
|
527
|
+
"review (USPTO compliance). Results are stored in the patent lab entry."
|
|
528
|
+
),
|
|
529
|
+
inputSchema={
|
|
530
|
+
"type": "object",
|
|
531
|
+
"properties": {
|
|
532
|
+
"entry_id": {
|
|
533
|
+
"type": "string",
|
|
534
|
+
"description": "Patent lab entry UUID to analyze",
|
|
535
|
+
},
|
|
536
|
+
"task": {
|
|
537
|
+
"type": "string",
|
|
538
|
+
"description": "Task: analyze, draft-claims, prior-art, full-disclosure, review",
|
|
539
|
+
"enum": ["analyze", "draft-claims", "prior-art", "full-disclosure", "review"],
|
|
540
|
+
"default": "analyze",
|
|
541
|
+
},
|
|
542
|
+
"project": {
|
|
543
|
+
"type": "string",
|
|
544
|
+
"description": "Project name (format: org/project)",
|
|
545
|
+
},
|
|
546
|
+
"max_budget_usd": {
|
|
547
|
+
"type": "number",
|
|
548
|
+
"description": "Max budget for agent run in USD (default: 2.0)",
|
|
549
|
+
"default": 2.0,
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
"required": ["entry_id"],
|
|
553
|
+
},
|
|
554
|
+
),
|
|
520
555
|
Tool(
|
|
521
556
|
name="openrunner_patent_report",
|
|
522
557
|
description=(
|
|
@@ -621,6 +656,8 @@ def create_server():
|
|
|
621
656
|
result = await _tool_patent_lab_list(arguments)
|
|
622
657
|
elif name == "openrunner_patent_analyze":
|
|
623
658
|
result = await _tool_patent_analyze(arguments)
|
|
659
|
+
elif name == "openrunner_patent_agent":
|
|
660
|
+
result = await _tool_patent_agent(arguments)
|
|
624
661
|
elif name == "openrunner_patent_report":
|
|
625
662
|
result = await _tool_patent_report(arguments)
|
|
626
663
|
else:
|
|
@@ -1829,6 +1866,48 @@ def _text_similarity(text1: str, text2: str) -> float:
|
|
|
1829
1866
|
return len(intersection) / len(union)
|
|
1830
1867
|
|
|
1831
1868
|
|
|
1869
|
+
async def _tool_patent_agent(arguments: dict) -> str:
|
|
1870
|
+
"""Run a Claude Code agent on a patent entry via server API."""
|
|
1871
|
+
project = arguments.get("project") or _resolve_project()
|
|
1872
|
+
if not project:
|
|
1873
|
+
return "No project specified."
|
|
1874
|
+
|
|
1875
|
+
entry_id = arguments.get("entry_id")
|
|
1876
|
+
if not entry_id:
|
|
1877
|
+
return "entry_id is required."
|
|
1878
|
+
|
|
1879
|
+
task = arguments.get("task", "analyze")
|
|
1880
|
+
max_budget = arguments.get("max_budget_usd", 2.0)
|
|
1881
|
+
|
|
1882
|
+
project_id = await _resolve_project_id(project)
|
|
1883
|
+
if not project_id:
|
|
1884
|
+
return f"Project '{project}' not found."
|
|
1885
|
+
|
|
1886
|
+
try:
|
|
1887
|
+
resp = await _api_post(
|
|
1888
|
+
f"/projects/{project_id}/patent-lab/entries/{entry_id}/agent",
|
|
1889
|
+
data={
|
|
1890
|
+
"task": task,
|
|
1891
|
+
"max_budget_usd": max_budget,
|
|
1892
|
+
},
|
|
1893
|
+
)
|
|
1894
|
+
status = resp.get("status", "unknown")
|
|
1895
|
+
output = resp.get("output", "")
|
|
1896
|
+
cost = resp.get("cost_usd", 0)
|
|
1897
|
+
duration = resp.get("duration_ms", 0)
|
|
1898
|
+
|
|
1899
|
+
return (
|
|
1900
|
+
f"Patent agent completed.\n"
|
|
1901
|
+
f" Task: {task}\n"
|
|
1902
|
+
f" Status: {status}\n"
|
|
1903
|
+
f" Cost: ${cost:.4f}\n"
|
|
1904
|
+
f" Duration: {duration}ms\n"
|
|
1905
|
+
f" Output:\n{output[:1500]}"
|
|
1906
|
+
)
|
|
1907
|
+
except Exception as e:
|
|
1908
|
+
return f"Error running patent agent: {e}"
|
|
1909
|
+
|
|
1910
|
+
|
|
1832
1911
|
async def _tool_patent_report(arguments: dict) -> str:
|
|
1833
1912
|
"""Create a patent report memo via API."""
|
|
1834
1913
|
project = arguments.get("project") or _resolve_project()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|