ap-client 0.5.1.dev5__tar.gz → 0.5.3.dev0__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.
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/PKG-INFO +4 -3
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/api.py +12 -4
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/cli.py +188 -25
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/dataset_commands.py +53 -27
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/dataset_register.py +53 -6
- ap_client-0.5.3.dev0/ap_client/exporter.py +987 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/instance_commands.py +7 -2
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/irepo_sdk.py +183 -37
- ap_client-0.5.3.dev0/ap_client/managed_ak.py +177 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/manifest_view.py +5 -3
- ap_client-0.5.3.dev0/ap_client/ossutil_commands.py +286 -0
- ap_client-0.5.3.dev0/ap_client/ossutil_io.py +1034 -0
- ap_client-0.5.3.dev0/ap_client/repo_read_guard.py +49 -0
- ap_client-0.5.3.dev0/ap_client/share_api.py +229 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/pyproject.toml +4 -3
- ap_client-0.5.1.dev5/ap_client/exporter.py +0 -934
- ap_client-0.5.1.dev5/ap_client/managed_ak.py +0 -86
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/__init__.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/akpool_commands.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/config.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/fs_commands.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/profile_commands.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/split_publish.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/tbb.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.3.dev0}/ap_client/waiter.py +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3.dev0
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.1
|
|
6
7
|
Requires-Dist: pyyaml>=6.0
|
|
7
8
|
Requires-Dist: requests>=2.28.0
|
|
8
9
|
Requires-Dist: rich>=13.0.0
|
|
9
10
|
Requires-Dist: typer>=0.9.0
|
|
10
11
|
Requires-Dist: websockets>=13.0
|
|
11
12
|
Provides-Extra: all
|
|
12
|
-
Requires-Dist: instance-repo[oss]
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.5.dev2; extra == 'all'
|
|
13
14
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.5.dev2; extra == 'dataset'
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
17
18
|
A lightweight Python SDK and command line interface for Agent Platform. It provides helpers for configuring API access and managing templates, datasets, jobs, and groups.
|
|
@@ -539,11 +539,11 @@ class APIClient:
|
|
|
539
539
|
|
|
540
540
|
def _post(self, path: str, data: Any, timeout: TimeoutType = None) -> Any:
|
|
541
541
|
"""Send a POST request."""
|
|
542
|
-
from .managed_ak import
|
|
542
|
+
from .managed_ak import parameter_ak_usage, require_parameter_ak_ack
|
|
543
543
|
|
|
544
|
-
references =
|
|
544
|
+
references, selectors = parameter_ak_usage(data) if path == "/jobs" else (set(), set())
|
|
545
545
|
result = self._request("POST", path, json_body=data, timeout=timeout)
|
|
546
|
-
require_parameter_ak_ack(result, references)
|
|
546
|
+
require_parameter_ak_ack(result, references, selectors)
|
|
547
547
|
return result
|
|
548
548
|
|
|
549
549
|
def _delete(self, path: str, *, params: Optional[dict] = None) -> Any:
|
|
@@ -709,13 +709,16 @@ class APIClient:
|
|
|
709
709
|
request_id: str,
|
|
710
710
|
duration_ms: float,
|
|
711
711
|
exc: Exception,
|
|
712
|
+
*,
|
|
713
|
+
error_detail: str | None = None,
|
|
712
714
|
) -> None:
|
|
715
|
+
detail = str(exc) if error_detail is None else error_detail
|
|
713
716
|
_write_stderr(
|
|
714
717
|
[
|
|
715
718
|
f"!! Error {method} {url}",
|
|
716
719
|
f" request_id={request_id}",
|
|
717
720
|
f" duration_ms={duration_ms:.1f}",
|
|
718
|
-
f" error={type(exc).__name__}: {
|
|
721
|
+
f" error={type(exc).__name__}: {detail}",
|
|
719
722
|
]
|
|
720
723
|
)
|
|
721
724
|
|
|
@@ -1878,9 +1881,12 @@ class APIClient:
|
|
|
1878
1881
|
meta_job_type: Optional[str] = None,
|
|
1879
1882
|
meta_teacher_direction: Optional[str] = None,
|
|
1880
1883
|
meta_train_stage: Optional[str] = None,
|
|
1884
|
+
benchmark: Optional[str] = None,
|
|
1881
1885
|
) -> dict:
|
|
1882
1886
|
"""Update group properties.
|
|
1883
1887
|
|
|
1888
|
+
``benchmark`` is omitted when None; pass an empty string to clear it.
|
|
1889
|
+
|
|
1884
1890
|
``tags``(`key:value` 数组)在客户端先与 Group 现有 tags 合并,再以
|
|
1885
1891
|
全量替换语义提交(服务端 PATCH 是整体替换);同名 key 覆盖,未提及
|
|
1886
1892
|
的 key 保留。服务端会从 tags 同步 meta_* 等派生字段。
|
|
@@ -1903,6 +1909,8 @@ class APIClient:
|
|
|
1903
1909
|
body["model"] = model
|
|
1904
1910
|
if scaffold is not None:
|
|
1905
1911
|
body["scaffold"] = scaffold
|
|
1912
|
+
if benchmark is not None:
|
|
1913
|
+
body["benchmark"] = benchmark
|
|
1906
1914
|
meta_attr_tags = _merge_meta_tags(
|
|
1907
1915
|
None,
|
|
1908
1916
|
meta_model=meta_model,
|
|
@@ -25,7 +25,7 @@ from ap_client.api import (
|
|
|
25
25
|
)
|
|
26
26
|
from ap_client.config import ENV_VAR_SPECS, ConfigurationError, _parse_bool, normalize_output_format
|
|
27
27
|
from ap_client.dataset_commands import register as _register_dataset_commands
|
|
28
|
-
from ap_client.exporter import export_group, export_job
|
|
28
|
+
from ap_client.exporter import export_group, export_job, export_jobs
|
|
29
29
|
from ap_client.fs_commands import register as _register_fs_commands
|
|
30
30
|
from ap_client.instance_commands import register as _register_instance_commands
|
|
31
31
|
from ap_client.managed_ak import (
|
|
@@ -35,6 +35,7 @@ from ap_client.managed_ak import (
|
|
|
35
35
|
validate_ak_id,
|
|
36
36
|
validate_ak_selection,
|
|
37
37
|
)
|
|
38
|
+
from ap_client.ossutil_commands import register as _register_ossutil_commands
|
|
38
39
|
from ap_client.profile_commands import profile_app
|
|
39
40
|
from ap_client.waiter import (
|
|
40
41
|
WaitTimeoutError,
|
|
@@ -167,6 +168,7 @@ app.add_typer(checkpoint_app, name="checkpoint")
|
|
|
167
168
|
# ap dataset version/split/access 嵌套在 dataset 下;instance/fs 是一等资源。
|
|
168
169
|
_register_dataset_commands(dataset_app)
|
|
169
170
|
_register_instance_commands(app)
|
|
171
|
+
_register_ossutil_commands(app)
|
|
170
172
|
_register_fs_commands(app)
|
|
171
173
|
_register_akpool_commands(app)
|
|
172
174
|
|
|
@@ -359,6 +361,22 @@ def _emit_error(message: str) -> None:
|
|
|
359
361
|
error_console.print(f"[red]error: {message}[/]")
|
|
360
362
|
|
|
361
363
|
|
|
364
|
+
def _read_client(share_token: Optional[str] = None, group_id: Optional[str] = None):
|
|
365
|
+
if group_id and not share_token:
|
|
366
|
+
raise typer.BadParameter("--group-id requires --share-token")
|
|
367
|
+
client = get_client()
|
|
368
|
+
if share_token is None:
|
|
369
|
+
return client
|
|
370
|
+
from ap_client.share_api import ShareAPIClient
|
|
371
|
+
|
|
372
|
+
# Typer verbose tracebacks must not render the share token in locals.
|
|
373
|
+
app.pretty_exceptions_show_locals = False
|
|
374
|
+
try:
|
|
375
|
+
return ShareAPIClient.from_client(client, share_token, group_id=group_id)
|
|
376
|
+
except ValueError as exc:
|
|
377
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
378
|
+
|
|
379
|
+
|
|
362
380
|
def _reject_account_pool_template_credential_cli(
|
|
363
381
|
account_pool: Optional[str],
|
|
364
382
|
*credential_types: Any,
|
|
@@ -3564,8 +3582,8 @@ def job_create(
|
|
|
3564
3582
|
if dry_run:
|
|
3565
3583
|
_emit_dry_run_warnings(dry_run_warnings)
|
|
3566
3584
|
body["dry_run"] = True
|
|
3567
|
-
result = client._post("/jobs", body, timeout=timeout)
|
|
3568
3585
|
try:
|
|
3586
|
+
result = client._post("/jobs", body, timeout=timeout)
|
|
3569
3587
|
require_managed_ak_ack(result, ak_id)
|
|
3570
3588
|
except ManagedAKProtocolError as exc:
|
|
3571
3589
|
_emit_error(str(exc))
|
|
@@ -4427,6 +4445,9 @@ def job_create(
|
|
|
4427
4445
|
@job_app.command("get")
|
|
4428
4446
|
def job_get(
|
|
4429
4447
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4448
|
+
share_token: Optional[str] = typer.Option(
|
|
4449
|
+
None, "--share-token", help="Read with a share token"
|
|
4450
|
+
),
|
|
4430
4451
|
include_params: bool = typer.Option(
|
|
4431
4452
|
False,
|
|
4432
4453
|
"--include-params",
|
|
@@ -4463,7 +4484,7 @@ def job_get(
|
|
|
4463
4484
|
include_spec = include_spec or _field_path_requires_spec(field)
|
|
4464
4485
|
if field == "job_spec_data.spec_yaml":
|
|
4465
4486
|
spec_format = "yaml"
|
|
4466
|
-
client =
|
|
4487
|
+
client = _read_client(share_token)
|
|
4467
4488
|
result = client.get_job(
|
|
4468
4489
|
job_id,
|
|
4469
4490
|
include_params=include_params,
|
|
@@ -4489,6 +4510,9 @@ def job_get(
|
|
|
4489
4510
|
@job_app.command("containers")
|
|
4490
4511
|
def job_containers(
|
|
4491
4512
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4513
|
+
share_token: Optional[str] = typer.Option(
|
|
4514
|
+
None, "--share-token", help="Read with a share token"
|
|
4515
|
+
),
|
|
4492
4516
|
output_format: str = typer.Option(
|
|
4493
4517
|
None,
|
|
4494
4518
|
"--format",
|
|
@@ -4497,7 +4521,7 @@ def job_containers(
|
|
|
4497
4521
|
):
|
|
4498
4522
|
"""List container names for a job."""
|
|
4499
4523
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
4500
|
-
client =
|
|
4524
|
+
client = _read_client(share_token)
|
|
4501
4525
|
result = client.get_job_containers(job_id)
|
|
4502
4526
|
if output_format == "table":
|
|
4503
4527
|
_print_records_table(result)
|
|
@@ -4559,6 +4583,9 @@ def _print_tail_logs(logs, tail: int) -> None:
|
|
|
4559
4583
|
@job_app.command("logs")
|
|
4560
4584
|
def job_logs(
|
|
4561
4585
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4586
|
+
share_token: Optional[str] = typer.Option(
|
|
4587
|
+
None, "--share-token", help="Read with a share token"
|
|
4588
|
+
),
|
|
4562
4589
|
container: Optional[str] = typer.Option("main", "--container", "-c", help="Container name"),
|
|
4563
4590
|
tail: Optional[int] = typer.Option(None, "--tail", "-n", help="Show only the last N log lines"),
|
|
4564
4591
|
follow: bool = typer.Option(
|
|
@@ -4570,7 +4597,7 @@ def job_logs(
|
|
|
4570
4597
|
),
|
|
4571
4598
|
):
|
|
4572
4599
|
"""Show job logs."""
|
|
4573
|
-
client =
|
|
4600
|
+
client = _read_client(share_token)
|
|
4574
4601
|
|
|
4575
4602
|
if tail is not None:
|
|
4576
4603
|
if tail <= 0:
|
|
@@ -4634,6 +4661,9 @@ def job_logs(
|
|
|
4634
4661
|
@job_app.command("events")
|
|
4635
4662
|
def job_events(
|
|
4636
4663
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4664
|
+
share_token: Optional[str] = typer.Option(
|
|
4665
|
+
None, "--share-token", help="Read with a share token"
|
|
4666
|
+
),
|
|
4637
4667
|
container: Optional[str] = typer.Option(None, "--container", "-c", help="Container name"),
|
|
4638
4668
|
output_format: str = typer.Option(
|
|
4639
4669
|
None,
|
|
@@ -4643,7 +4673,7 @@ def job_events(
|
|
|
4643
4673
|
):
|
|
4644
4674
|
"""Show job events."""
|
|
4645
4675
|
output_format = _normalize_output_format(output_format)
|
|
4646
|
-
client =
|
|
4676
|
+
client = _read_client(share_token)
|
|
4647
4677
|
result = client.get_job_events(job_id, container=container)
|
|
4648
4678
|
if output_format == "plain":
|
|
4649
4679
|
_print_job_events_plain(result)
|
|
@@ -4654,6 +4684,9 @@ def job_events(
|
|
|
4654
4684
|
@job_app.command("metrics")
|
|
4655
4685
|
def job_metrics(
|
|
4656
4686
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4687
|
+
share_token: Optional[str] = typer.Option(
|
|
4688
|
+
None, "--share-token", help="Read with a share token"
|
|
4689
|
+
),
|
|
4657
4690
|
output_format: str = typer.Option(
|
|
4658
4691
|
None,
|
|
4659
4692
|
"--format",
|
|
@@ -4662,7 +4695,7 @@ def job_metrics(
|
|
|
4662
4695
|
):
|
|
4663
4696
|
"""Show job metrics."""
|
|
4664
4697
|
output_format = _normalize_output_format(output_format)
|
|
4665
|
-
client =
|
|
4698
|
+
client = _read_client(share_token)
|
|
4666
4699
|
result = client.get_job_metrics(job_id)
|
|
4667
4700
|
if output_format == "plain":
|
|
4668
4701
|
_print_json(result)
|
|
@@ -4859,6 +4892,9 @@ def job_retry(
|
|
|
4859
4892
|
@job_app.command("artifacts")
|
|
4860
4893
|
def job_artifacts(
|
|
4861
4894
|
job_ids: str = typer.Argument(..., help="Job IDs (comma-separated for multiple)"),
|
|
4895
|
+
share_token: Optional[str] = typer.Option(
|
|
4896
|
+
None, "--share-token", help="Read with a share token"
|
|
4897
|
+
),
|
|
4862
4898
|
output_format: str = typer.Option(
|
|
4863
4899
|
None,
|
|
4864
4900
|
"--format",
|
|
@@ -4867,7 +4903,7 @@ def job_artifacts(
|
|
|
4867
4903
|
):
|
|
4868
4904
|
"""Show job artifact download links."""
|
|
4869
4905
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
4870
|
-
client =
|
|
4906
|
+
client = _read_client(share_token)
|
|
4871
4907
|
ids = [jid.strip() for jid in job_ids.split(",")]
|
|
4872
4908
|
results = client.get_job_artifacts(ids)
|
|
4873
4909
|
if output_format == "table":
|
|
@@ -4876,10 +4912,50 @@ def job_artifacts(
|
|
|
4876
4912
|
_print_formatted(results, output_format)
|
|
4877
4913
|
|
|
4878
4914
|
|
|
4915
|
+
def _validate_export_timeout(value: float) -> float:
|
|
4916
|
+
import math
|
|
4917
|
+
|
|
4918
|
+
if not math.isfinite(value) or value <= 0:
|
|
4919
|
+
raise typer.BadParameter("Timeout must be a finite number greater than zero")
|
|
4920
|
+
return value
|
|
4921
|
+
|
|
4922
|
+
|
|
4923
|
+
def _export_channel_reporter(emit):
|
|
4924
|
+
"""Report completed Jobs, including callbacks from Group worker threads."""
|
|
4925
|
+
from threading import Lock
|
|
4926
|
+
|
|
4927
|
+
from rich.text import Text
|
|
4928
|
+
|
|
4929
|
+
counts = {"oss": 0, "proxy": 0}
|
|
4930
|
+
lock = Lock()
|
|
4931
|
+
|
|
4932
|
+
def report(job_id, status, channel):
|
|
4933
|
+
with lock:
|
|
4934
|
+
suffix = ""
|
|
4935
|
+
if status == "downloaded" and channel in counts:
|
|
4936
|
+
counts[channel] += 1
|
|
4937
|
+
suffix = f" channel={channel}"
|
|
4938
|
+
emit(Text(f"{job_id} {status}{suffix}"))
|
|
4939
|
+
|
|
4940
|
+
return report, counts
|
|
4941
|
+
|
|
4942
|
+
|
|
4879
4943
|
@job_app.command("export")
|
|
4880
4944
|
def job_export(
|
|
4881
|
-
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4945
|
+
job_id: str = typer.Argument(..., help="Job ID or comma-separated Job IDs"),
|
|
4946
|
+
share_token: Optional[str] = typer.Option(
|
|
4947
|
+
None, "--share-token", help="Export with a share token"
|
|
4948
|
+
),
|
|
4882
4949
|
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Export directory"),
|
|
4950
|
+
workers: int = typer.Option(
|
|
4951
|
+
1, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
4952
|
+
),
|
|
4953
|
+
timeout: float = typer.Option(
|
|
4954
|
+
300,
|
|
4955
|
+
"--timeout",
|
|
4956
|
+
callback=_validate_export_timeout,
|
|
4957
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
4958
|
+
),
|
|
4883
4959
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
4884
4960
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
4885
4961
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -4889,17 +4965,50 @@ def job_export(
|
|
|
4889
4965
|
),
|
|
4890
4966
|
):
|
|
4891
4967
|
"""Export job artifacts to a local directory. Use --logs/--events to include logs and events."""
|
|
4892
|
-
|
|
4968
|
+
ids = list(dict.fromkeys(value.strip() for value in job_id.split(",")))
|
|
4969
|
+
if not all(ids):
|
|
4970
|
+
raise typer.BadParameter("Job IDs must not be empty", param_hint="job_id")
|
|
4971
|
+
client = _read_client(share_token)
|
|
4972
|
+
if len(ids) > 1:
|
|
4973
|
+
report, channels = _export_channel_reporter(print)
|
|
4974
|
+
dest, summary = export_jobs(
|
|
4975
|
+
client,
|
|
4976
|
+
ids,
|
|
4977
|
+
output,
|
|
4978
|
+
workers=workers,
|
|
4979
|
+
result_callback=report,
|
|
4980
|
+
timeout=timeout,
|
|
4981
|
+
include_logs=logs,
|
|
4982
|
+
include_events=events,
|
|
4983
|
+
extract_artifacts=not no_extract_artifacts,
|
|
4984
|
+
)
|
|
4985
|
+
print(
|
|
4986
|
+
f"Jobs exported: downloaded={summary.downloaded} "
|
|
4987
|
+
f"skipped={summary.skipped} failed={len(summary.failures)}"
|
|
4988
|
+
)
|
|
4989
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
4990
|
+
print(f" path: {dest}")
|
|
4991
|
+
for failed_id, error in summary.failures.items():
|
|
4992
|
+
print(f"[red]Failed job {failed_id}:[/] {error}")
|
|
4993
|
+
if summary.failures:
|
|
4994
|
+
raise typer.Exit(code=1)
|
|
4995
|
+
return
|
|
4996
|
+
job_id = ids[0]
|
|
4997
|
+
result = {}
|
|
4893
4998
|
dest = export_job(
|
|
4894
4999
|
client,
|
|
4895
5000
|
job_id,
|
|
4896
5001
|
output,
|
|
5002
|
+
result_callback=lambda jid, status, channel: result.update(status=status, channel=channel),
|
|
5003
|
+
timeout=timeout,
|
|
4897
5004
|
include_logs=logs,
|
|
4898
5005
|
include_events=events,
|
|
4899
5006
|
extract_artifacts=not no_extract_artifacts,
|
|
4900
5007
|
)
|
|
4901
5008
|
print(f"[green]Job exported:[/] {job_id}")
|
|
4902
5009
|
print(f" path: {dest}")
|
|
5010
|
+
if result.get("channel") in {"oss", "proxy"} and result.get("status") == "downloaded":
|
|
5011
|
+
print(f" channel: {result['channel']}")
|
|
4903
5012
|
|
|
4904
5013
|
|
|
4905
5014
|
@job_app.command("wait")
|
|
@@ -5674,6 +5783,9 @@ def group_list(
|
|
|
5674
5783
|
@group_app.command("get")
|
|
5675
5784
|
def group_get(
|
|
5676
5785
|
group_id: str = typer.Argument(..., help="Group ID"),
|
|
5786
|
+
share_token: Optional[str] = typer.Option(
|
|
5787
|
+
None, "--share-token", help="Read with a share token"
|
|
5788
|
+
),
|
|
5677
5789
|
output_format: str = typer.Option(
|
|
5678
5790
|
None,
|
|
5679
5791
|
"--format",
|
|
@@ -5682,7 +5794,7 @@ def group_get(
|
|
|
5682
5794
|
):
|
|
5683
5795
|
"""Show Group details."""
|
|
5684
5796
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
5685
|
-
client =
|
|
5797
|
+
client = _read_client(share_token, group_id if share_token else None)
|
|
5686
5798
|
result = client.get_group(group_id)
|
|
5687
5799
|
if output_format == "table":
|
|
5688
5800
|
_print_detail(result)
|
|
@@ -5956,6 +6068,9 @@ def group_update(
|
|
|
5956
6068
|
scaffold: Optional[str] = typer.Option(
|
|
5957
6069
|
None, "--scaffold", "-s", help="Scaffold/agent name (benchmark perspective)"
|
|
5958
6070
|
),
|
|
6071
|
+
benchmark: Optional[str] = typer.Option(
|
|
6072
|
+
None, "--benchmark", help="Benchmark name; pass an empty string to clear"
|
|
6073
|
+
),
|
|
5959
6074
|
tags: Optional[str] = typer.Option(
|
|
5960
6075
|
None,
|
|
5961
6076
|
"--tags",
|
|
@@ -6017,6 +6132,7 @@ def group_update(
|
|
|
6017
6132
|
dataset,
|
|
6018
6133
|
model_name,
|
|
6019
6134
|
scaffold,
|
|
6135
|
+
benchmark,
|
|
6020
6136
|
tags_list,
|
|
6021
6137
|
)
|
|
6022
6138
|
):
|
|
@@ -6031,6 +6147,7 @@ def group_update(
|
|
|
6031
6147
|
dataset=dataset,
|
|
6032
6148
|
model=model_name,
|
|
6033
6149
|
scaffold=scaffold,
|
|
6150
|
+
**({"benchmark": benchmark} if benchmark is not None else {}),
|
|
6034
6151
|
**({"tags": tags_list} if tags_list is not None else {}),
|
|
6035
6152
|
)
|
|
6036
6153
|
if output_format == "plain":
|
|
@@ -6045,6 +6162,7 @@ def group_update(
|
|
|
6045
6162
|
"dataset",
|
|
6046
6163
|
"model",
|
|
6047
6164
|
"scaffold",
|
|
6165
|
+
"benchmark",
|
|
6048
6166
|
"updated_at",
|
|
6049
6167
|
],
|
|
6050
6168
|
)
|
|
@@ -6162,6 +6280,9 @@ def group_resume(
|
|
|
6162
6280
|
@group_app.command("jobs")
|
|
6163
6281
|
def group_jobs(
|
|
6164
6282
|
group_id: str = typer.Argument(..., help="Group ID"),
|
|
6283
|
+
share_token: Optional[str] = typer.Option(
|
|
6284
|
+
None, "--share-token", help="Read with a share token"
|
|
6285
|
+
),
|
|
6165
6286
|
tag: Optional[list[str]] = typer.Option(
|
|
6166
6287
|
None, "--tag", help="Filter by tag (repeatable; multiple tags are combined with AND)"
|
|
6167
6288
|
),
|
|
@@ -6200,17 +6321,36 @@ def group_jobs(
|
|
|
6200
6321
|
if cursor and skip > 0:
|
|
6201
6322
|
raise typer.BadParameter("--cursor cannot be used with --skip greater than 0")
|
|
6202
6323
|
cursor_request = bool(next_token) or cursor
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6324
|
+
if share_token is not None:
|
|
6325
|
+
unsupported = (
|
|
6326
|
+
("--tag", bool(tag)),
|
|
6327
|
+
("--include-post-process", include_post_process),
|
|
6328
|
+
("--next-token", bool(next_token)),
|
|
6329
|
+
("--cursor", cursor),
|
|
6330
|
+
("--no-total", no_total),
|
|
6331
|
+
)
|
|
6332
|
+
for option, used in unsupported:
|
|
6333
|
+
if used:
|
|
6334
|
+
_emit_error(f"{option} is not supported with --share-token")
|
|
6335
|
+
raise typer.Exit(1)
|
|
6336
|
+
if skip < 0 or not 1 <= limit <= 500:
|
|
6337
|
+
raise typer.BadParameter(
|
|
6338
|
+
"share pagination requires --skip >= 0 and 1 <= --limit <= 500"
|
|
6339
|
+
)
|
|
6340
|
+
client = _read_client(share_token, group_id if share_token else None)
|
|
6341
|
+
result = client.list_jobs(group_id=group_id, skip=skip, limit=limit)
|
|
6342
|
+
else:
|
|
6343
|
+
client = get_client()
|
|
6344
|
+
result = client.list_group_jobs(
|
|
6345
|
+
group_id,
|
|
6346
|
+
tag=tag,
|
|
6347
|
+
include_post_process=include_post_process,
|
|
6348
|
+
skip=skip,
|
|
6349
|
+
limit=limit,
|
|
6350
|
+
**({"next_token": next_token} if next_token else {}),
|
|
6351
|
+
**({"pagination": "cursor"} if cursor_request else {}),
|
|
6352
|
+
include_total=not no_total,
|
|
6353
|
+
)
|
|
6214
6354
|
if output_format == "table":
|
|
6215
6355
|
_print_job_list_plain(result, rich=True)
|
|
6216
6356
|
elif output_format == "plain":
|
|
@@ -6225,6 +6365,9 @@ def group_jobs(
|
|
|
6225
6365
|
@group_app.command("stats")
|
|
6226
6366
|
def group_stats(
|
|
6227
6367
|
group_id: str = typer.Argument(..., help="Group ID"),
|
|
6368
|
+
share_token: Optional[str] = typer.Option(
|
|
6369
|
+
None, "--share-token", help="Read with a share token"
|
|
6370
|
+
),
|
|
6228
6371
|
output_format: str = typer.Option(
|
|
6229
6372
|
None,
|
|
6230
6373
|
"--format",
|
|
@@ -6233,7 +6376,7 @@ def group_stats(
|
|
|
6233
6376
|
):
|
|
6234
6377
|
"""Show Group progress."""
|
|
6235
6378
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
6236
|
-
client =
|
|
6379
|
+
client = _read_client(share_token, group_id if share_token else None)
|
|
6237
6380
|
result = client.get_group_stats(group_id)
|
|
6238
6381
|
if output_format == "table":
|
|
6239
6382
|
_print_detail(result)
|
|
@@ -6244,6 +6387,9 @@ def group_stats(
|
|
|
6244
6387
|
@group_app.command("eval")
|
|
6245
6388
|
def group_eval(
|
|
6246
6389
|
group_id: str = typer.Argument(..., help="Group ID"),
|
|
6390
|
+
share_token: Optional[str] = typer.Option(
|
|
6391
|
+
None, "--share-token", help="Read with a share token"
|
|
6392
|
+
),
|
|
6247
6393
|
summary_only: bool = typer.Option(
|
|
6248
6394
|
False,
|
|
6249
6395
|
"--summary-only",
|
|
@@ -6257,7 +6403,7 @@ def group_eval(
|
|
|
6257
6403
|
):
|
|
6258
6404
|
"""Show Group aggregated evaluation results."""
|
|
6259
6405
|
output_format = _normalize_output_format(output_format)
|
|
6260
|
-
client =
|
|
6406
|
+
client = _read_client(share_token, group_id if share_token else None)
|
|
6261
6407
|
result = (
|
|
6262
6408
|
client.get_group_eval_summary(group_id) if summary_only else client.get_group_eval(group_id)
|
|
6263
6409
|
)
|
|
@@ -6370,10 +6516,19 @@ def group_access_list(group_id: str = typer.Argument(..., help="Group ID")):
|
|
|
6370
6516
|
@group_app.command("export")
|
|
6371
6517
|
def group_export(
|
|
6372
6518
|
group_id: str = typer.Argument(..., help="Group ID"),
|
|
6519
|
+
share_token: Optional[str] = typer.Option(
|
|
6520
|
+
None, "--share-token", help="Export with a share token"
|
|
6521
|
+
),
|
|
6373
6522
|
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Export directory"),
|
|
6374
6523
|
workers: int = typer.Option(
|
|
6375
6524
|
4, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
6376
6525
|
),
|
|
6526
|
+
timeout: float = typer.Option(
|
|
6527
|
+
300,
|
|
6528
|
+
"--timeout",
|
|
6529
|
+
callback=_validate_export_timeout,
|
|
6530
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
6531
|
+
),
|
|
6377
6532
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
6378
6533
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
6379
6534
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -6405,7 +6560,11 @@ def group_export(
|
|
|
6405
6560
|
_emit_error("--include-files/--exclude-files cannot be used with --no-extract-artifacts")
|
|
6406
6561
|
raise typer.Exit(1)
|
|
6407
6562
|
|
|
6408
|
-
|
|
6563
|
+
if share_token is not None and (include_file_selectors or exclude_file_selectors):
|
|
6564
|
+
_emit_error("--include-files/--exclude-files are not supported with --share-token")
|
|
6565
|
+
raise typer.Exit(1)
|
|
6566
|
+
|
|
6567
|
+
client = _read_client(share_token, group_id if share_token else None)
|
|
6409
6568
|
from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn
|
|
6410
6569
|
|
|
6411
6570
|
with Progress(
|
|
@@ -6434,12 +6593,15 @@ def group_export(
|
|
|
6434
6593
|
current_stage=current_stage,
|
|
6435
6594
|
)
|
|
6436
6595
|
|
|
6596
|
+
report, channels = _export_channel_reporter(progress.console.print)
|
|
6437
6597
|
dest, summary = export_group(
|
|
6438
6598
|
client,
|
|
6439
6599
|
group_id,
|
|
6440
6600
|
output,
|
|
6441
6601
|
progress_callback=_on_progress,
|
|
6442
6602
|
workers=workers,
|
|
6603
|
+
result_callback=report,
|
|
6604
|
+
timeout=timeout,
|
|
6443
6605
|
include_logs=logs,
|
|
6444
6606
|
include_events=events,
|
|
6445
6607
|
extract_artifacts=not no_extract_artifacts,
|
|
@@ -6452,6 +6614,7 @@ def group_export(
|
|
|
6452
6614
|
f" [green]{summary.downloaded}[/] jobs downloaded, "
|
|
6453
6615
|
f"[yellow]{summary.skipped}[/] jobs skipped (no artifact)"
|
|
6454
6616
|
)
|
|
6617
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
6455
6618
|
if summary.skipped_job_names:
|
|
6456
6619
|
print(f" Skipped: {', '.join(summary.skipped_job_names)}")
|
|
6457
6620
|
|