ap-client 0.3.0.dev0__tar.gz → 0.3.1.dev2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ap-client
3
- Version: 0.3.0.dev0
3
+ Version: 0.3.1.dev2
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: pyyaml>=6.0
@@ -9,9 +9,9 @@ Requires-Dist: rich>=13.0.0
9
9
  Requires-Dist: typer>=0.9.0
10
10
  Requires-Dist: websockets>=13.0
11
11
  Provides-Extra: all
12
- Requires-Dist: instance-repo[oss]>=0.7.0; extra == 'all'
12
+ Requires-Dist: instance-repo[oss]>=1.1.0; extra == 'all'
13
13
  Provides-Extra: dataset
14
- Requires-Dist: instance-repo[oss]>=0.7.0; extra == 'dataset'
14
+ Requires-Dist: instance-repo[oss]>=1.1.0; extra == 'dataset'
15
15
  Description-Content-Type: text/markdown
16
16
 
17
17
  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.
@@ -15,6 +15,7 @@ from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit
15
15
  import requests
16
16
 
17
17
  from .config import Config, ConfigurationError, get_config, has_cluster_header
18
+ from .managed_ak import require_managed_ak_ack, validate_ak_id, validate_ak_selection
18
19
 
19
20
  _DEFAULT_CONNECT_TIMEOUT = 300
20
21
  _DEFAULT_READ_TIMEOUT = 86400
@@ -293,6 +294,25 @@ def _secret_ws_params(workspace_id: Optional[str]) -> Optional[dict]:
293
294
  return {"workspace_id": workspace_id} if workspace_id else None
294
295
 
295
296
 
297
+ def _split_paged(payload: Any) -> tuple[list[dict], dict]:
298
+ """Split a ``PagedSuccess`` envelope into ``(records, pagination)``.
299
+
300
+ ``PagedSuccess`` is ``{code,message,data:[...],pagination:{total,page,page_size}}``.
301
+ A bare list (or a null ``data``) yields an empty pagination dict so callers
302
+ can render results without probing the response shape.
303
+ """
304
+ if isinstance(payload, list):
305
+ return [item for item in payload if isinstance(item, dict)], {}
306
+ if not isinstance(payload, dict):
307
+ return [], {}
308
+ records = payload.get("data")
309
+ pagination = payload.get("pagination")
310
+ return (
311
+ [item for item in (records or []) if isinstance(item, dict)],
312
+ pagination if isinstance(pagination, dict) else {},
313
+ )
314
+
315
+
296
316
  class APIClient:
297
317
  """Agent Platform API client."""
298
318
 
@@ -459,7 +479,12 @@ class APIClient:
459
479
 
460
480
  def _post(self, path: str, data: Any, timeout: TimeoutType = None) -> Any:
461
481
  """Send a POST request."""
462
- return self._request("POST", path, json_body=data, timeout=timeout)
482
+ from .managed_ak import parameter_ak_ids, require_parameter_ak_ack
483
+
484
+ references = parameter_ak_ids(data) if path == "/jobs" else set()
485
+ result = self._request("POST", path, json_body=data, timeout=timeout)
486
+ require_parameter_ak_ack(result, references)
487
+ return result
463
488
 
464
489
  def _delete(self, path: str, *, params: Optional[dict] = None) -> Any:
465
490
  """Send a DELETE request."""
@@ -708,8 +733,35 @@ class APIClient:
708
733
  return self._get(f"/templates/{quote(name)}", params=params)
709
734
 
710
735
  def list_benchmarks(self) -> list:
711
- """List all active benchmarks from the local benchmark registry."""
712
- return self._get("/benchmarks")
736
+ """List all benchmarks from the central registry, preserving legacy IDs."""
737
+ benchmarks = []
738
+ page = 1
739
+ page_size = 200
740
+ # 与 CLI dataset list 的自动翻页同口径:100 页上限,防分页异常导致死循环。
741
+ max_pages = 100
742
+ while page <= max_pages:
743
+ payload = self._get(
744
+ "/apis/v1/benchmarks", params={"page": page, "page_size": page_size}
745
+ )
746
+ records, pagination = _split_paged(payload)
747
+ for record in records:
748
+ item = dict(record)
749
+ if "source_id" in item:
750
+ item.setdefault("id", item["source_id"])
751
+ benchmarks.append(item)
752
+ if not records or not pagination:
753
+ break
754
+ try:
755
+ total = int(pagination.get("total"))
756
+ except (TypeError, ValueError):
757
+ total = None
758
+ if total is not None:
759
+ if len(benchmarks) >= total:
760
+ break
761
+ elif len(records) < page_size:
762
+ break
763
+ page += 1
764
+ return benchmarks
713
765
 
714
766
  def get_benchmark(self, name: str) -> dict:
715
767
  """Get a single benchmark by exact name."""
@@ -844,7 +896,16 @@ class APIClient:
844
896
  "instance_ids": all_instance_ids,
845
897
  }
846
898
 
847
- # ==================== Meta operations ====================
899
+ # ==================== Dataset series (apiserver dataset domain) ====================
900
+ #
901
+ # 这一节对接 Go apiserver 的 dataset 领域(/apis/v1/datasets/...),与上面
902
+ # ossdata 目录的 /api/datasets 路由完全无关。
903
+ #
904
+ # **dataset 领域的控制面读写全部在 InstanceRepo SDK 上**(见
905
+ # docs/ap-cli-dataset-refactor-design.md §9.5):迁移期加进这里的 registry 方法、
906
+ # 以及最后三条"push 前检查 + publish-status"的旧读,都已随迁移删除。
907
+ # 本文件现在只服务旧 ossdata 路径(`dataset list --legacy`、弃用的
908
+ # `dataset versions/instances`、`job create --dataset`)。
848
909
 
849
910
  def list_meta_models(self) -> dict:
850
911
  """模型词表(apiserver 权威源 GET /apis/v1/meta-models,返回 {items:[...]})。"""
@@ -911,9 +972,19 @@ class APIClient:
911
972
  checkpoint: Optional[dict] = None,
912
973
  benchmark: Optional[str] = None,
913
974
  experiment: Optional[str] = None,
975
+ ak_ids: Optional[list[str]] = None,
914
976
  ) -> dict:
915
977
  """Create a group."""
978
+ if ak_ids is not None and not isinstance(ak_ids, list):
979
+ raise ValueError("ak_ids must be a list")
980
+ references = list(ak_ids or [])
981
+ for reference in references:
982
+ if validate_ak_id(reference) is None:
983
+ raise ValueError("ak_ids entries must be nonempty strings")
984
+ references = list(dict.fromkeys(references))
916
985
  body: dict = {}
986
+ if references:
987
+ body["ak_ids"] = references
917
988
  if name:
918
989
  body["name"] = name
919
990
  effective_tags = _merge_meta_tags(
@@ -965,11 +1036,14 @@ class APIClient:
965
1036
  body["benchmark"] = benchmark
966
1037
  if experiment is not None:
967
1038
  body["experiment"] = experiment
968
- return self._post("/groups", body)
1039
+ result = self._post("/groups", body)
1040
+ for reference in references:
1041
+ require_managed_ak_ack(result, reference)
1042
+ return result
969
1043
 
970
1044
  def create_job(
971
1045
  self,
972
- template: str,
1046
+ template: Optional[str] = None,
973
1047
  params: Optional[dict] = None,
974
1048
  params_list: Optional[list] = None,
975
1049
  suite_name: Optional[str] = None,
@@ -1022,6 +1096,7 @@ class APIClient:
1022
1096
  checkpoint: Optional[dict] = None,
1023
1097
  benchmark: Optional[str] = None,
1024
1098
  experiment: Optional[str] = None,
1099
+ ak_id: Optional[str] = None,
1025
1100
  ) -> dict:
1026
1101
  """Create a job."""
1027
1102
  body = self.build_create_job_body(
@@ -1068,13 +1143,16 @@ class APIClient:
1068
1143
  checkpoint=checkpoint,
1069
1144
  benchmark=benchmark,
1070
1145
  experiment=experiment,
1146
+ ak_id=ak_id,
1071
1147
  )
1072
1148
 
1073
- return self._post("/jobs", body, timeout=timeout)
1149
+ result = self._post("/jobs", body, timeout=timeout)
1150
+ require_managed_ak_ack(result, ak_id)
1151
+ return result
1074
1152
 
1075
1153
  def build_create_job_body(
1076
1154
  self,
1077
- template: str,
1155
+ template: Optional[str] = None,
1078
1156
  params: Optional[dict] = None,
1079
1157
  params_list: Optional[list] = None,
1080
1158
  suite_name: Optional[str] = None,
@@ -1126,6 +1204,7 @@ class APIClient:
1126
1204
  checkpoint: Optional[dict] = None,
1127
1205
  benchmark: Optional[str] = None,
1128
1206
  experiment: Optional[str] = None,
1207
+ ak_id: Optional[str] = None,
1129
1208
  ) -> dict:
1130
1209
  """Build the /jobs request body for job creation."""
1131
1210
  _validate_credential_source_selection(
@@ -1134,7 +1213,14 @@ class APIClient:
1134
1213
  params=params,
1135
1214
  params_list=params_list,
1136
1215
  )
1137
- body: dict = {"template": template}
1216
+ if not template and not profile_id:
1217
+ raise ValueError("Either template or profile_id is required")
1218
+ validate_ak_selection(ak_id, params, params_list, overrides)
1219
+ body: dict = {}
1220
+ if template is not None:
1221
+ body["template"] = template
1222
+ if ak_id is not None:
1223
+ body["ak_id"] = ak_id
1138
1224
 
1139
1225
  if params is not None:
1140
1226
  body["params"] = params
@@ -1289,9 +1375,13 @@ class APIClient:
1289
1375
  next_token: Optional[str] = None,
1290
1376
  pagination: Optional[str] = None,
1291
1377
  include_total: bool = True,
1378
+ ak_id: Optional[str] = None,
1292
1379
  ) -> dict:
1293
1380
  """List jobs."""
1381
+ validate_ak_id(ak_id)
1294
1382
  params = {"skip": skip, "limit": limit}
1383
+ if ak_id is not None:
1384
+ params["ak_id"] = ak_id
1295
1385
  if template:
1296
1386
  params["template"] = template
1297
1387
  if group_id:
@@ -1485,9 +1575,13 @@ class APIClient:
1485
1575
  meta_job_type: Optional[str] = None,
1486
1576
  upstream_platform: Optional[str] = None,
1487
1577
  upstream_job_id: Optional[str] = None,
1578
+ ak_id: Optional[str] = None,
1488
1579
  ) -> dict:
1489
1580
  """List groups."""
1581
+ validate_ak_id(ak_id)
1490
1582
  params: dict = {"skip": skip, "limit": limit}
1583
+ if ak_id is not None:
1584
+ params["ak_id"] = ak_id
1491
1585
  if name is not None:
1492
1586
  params["name"] = name
1493
1587
  if group_id is not None: